Как я могу эффективно извлекать повторяющиеся элементы в массиве Ruby?

У меня есть массив, например [1,1,1,2,4,6,3,3], и я хотел бы получить список повторяющихся элементов, в данном случае [1,3]. Я написал это:

my_array.select{|obj|my_array.count(obj)>1}.uniq

Но это трагически неэффективно (o (n²)). У вас есть идея? Если возможно, краткий.

Спасибо

Ответ 1

Вдохновленный Илья Хейкинсон отвечает:

def repeated(array)
  counts = Hash.new(0)
  array.each{|val|counts[val]+=1}
  counts.reject{|val,count|count==1}.keys
end

Ответ 2

Использование Ruby Set библиотека:

require 'set'

ary = [1,1,1,2,4,6,3,3]
dups = Set.new
test_set = Set.new
ary.each {|val| dups.add(val) unless test_set.add?(val)}
dups.to_a # [1, 3]

Я считаю, что это должно быть O (n), потому что Set # add и Set # add? насколько мне известно.

Ответ 3

Как насчет чего-то подобного? Он будет работать в O (n).

a = [1,1,1,2,4,6,3,3]
b = {}
a.each { |v| if b.has_key? v then b[v] = b[v]+1 else b[v]=1 end }
b.reject { |k,v| if v > 1 then false else true end }.keys

Ответ 4

A O (n) решение (измените << x на + [x] и update на merge, чтобы сделать его чисто функциональным):

rs = xs.inject([[], {}]) do |(out, seen), x| 
  [(seen[x] == 1 ? (out << x) : out), seen.update(x => (seen[x] || 0)+1)]
end[0]

Более простой и менее экономичный подход:

rs = xs.group_by { |x| x }.select { |y, ys| ys.size > 1 }.keys

Такая же идея избегает промежуточного хэша с использованием "понимания списка":

rs = xs.group_by { |x| x }.map { |y, ys| y if ys.size > 1 }.compact

Ответ 5

Используя inject

[1,1,1,2,4,6,3,3].inject({}){ |ele, n| ele[n] = nil; ele }.keys 
# => [1, 2, 4, 6, 3] 

ОБЪЯСНЕНИЕ:

ele hash, который он инициализируется значением {}, каждая итерация добавляется ключом к значению n и nil в хеш <<22 > . В конце ele возвращается как:

{1=>nil, 2=>nil, 4=>nil, 6=>nil, 3=>nil}

Нам нужны только ключи, поэтому .keys завершает задание.

Ответ 6

Некоторые идеи: вам нужно выяснить правильные структуры данных библиотеки:

1 Сортировка массива O (nlogn), затем пробег через массив

2 Создайте набор, найдите текущий элемент массива в наборе и, если он не найден, вставьте и продолжите для всех элементов - O (nlogn) снова.

Ответ 7

Я думал о том, сколько раз в массиве появляется уникальный элемент. Это может быть действительно неэффективно, как оригинальное предложение, но было интересно посмотреть на проблему. Я не делал никаких тестов на больших массивах, поэтому это всего лишь упражнение.

a = [1,1,1,2,4,6,3,3]

dupes = []
a.uniq.each do |u|
  c = a.find_all {|e| e == u}.size
  dupes << [u, c] unless c == 1
end

puts dupes.inspect

# dupes = [[1, 3], [3, 2]]
# 1 appears 3 times
# 3 appears twice


# to extract just the elment a bit cleaner
dupes = a.uniq.select do |u|
  a.find_all {|e| e == u}.size != 1
end
puts dupes.inspect
# returns [1,3]

Ответ 8

Это будет работать, если дублированные записи всегда будут последовательно, как в вашем примере; иначе вам придется сортировать в первую очередь. each_cons анализирует текущее окно указанного размера.

require 'set'

my_array = [1,1,1,2,4,6,3,3]
dups = Set.new
my_array.each_cons(2) {|a,b| dups.add(a) if (a == b)}
p dups.to_a