Как выбрать уникальные элементы
Я хотел бы расширить класс Array
с помощью метода uniq_elements
, который возвращает те элементы с кратким числом. Я также хотел бы использовать закрытие для моего нового метода, как с uniq
. Например:
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements # => [1,3,5,6,8]
Пример с закрытием:
t=[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
t.uniq_elements{|z| z.round} # => [2.0, 5.1]
Ни t-t.uniq
, ни t.to_set-t.uniq.to_set
не работает. Я не забочусь о скорости, я называю ее только один раз в своей программе, поэтому она может быть медленной.
Ответы
Ответ 1
Помощник
В этом методе используется помощник:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Этот метод похож на Array # -. Разница проиллюстрирована в следующем примере:
a = [3,1,2,3,4,3,2,2,4]
b = [2,3,4,4,3,4]
a - b #=> [1]
c = a.difference b #=> [1, 3, 2, 2]
Как вы видите, a
содержит три 3 и b
содержит два, поэтому первые два 3 в a
удаляются при построении c
(a
не мутируется). Если b
содержит как минимум столько же элементов, сколько и a
, c
не содержит экземпляров этого элемента. Чтобы удалить элементы, начинающиеся в конце a
:
a.reverse.difference(b).reverse #=> [3, 1, 2, 2]
Array#difference!
можно было бы определить очевидным образом.
Я нашел много применений для этого метода: здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь here, здесь, здесь, здесь, здесь, здесь, здесь и здесь.
Я предложил, чтобы этот метод был добавлен в ядро Ruby.
При использовании с Array#-
этот метод упрощает извлечение уникальных элементов из массива a
:
a = [1,3,2,4,3,4]
u = a.uniq #=> [1, 2, 3, 4]
u - a.difference(u) #=> [1, 2]
Это работает, потому что
a.difference(u) #=> [3,4]
содержит все неповторимые элементы a
(каждый, возможно, несколько раз).
Проблема под рукой
код
class Array
def uniq_elements(&prc)
prc ||= ->(e) { e }
a = map { |e| prc[e] }
u = a.uniq
uniques = u - a.difference(u)
select { |e| uniques.include?(prc[e]) ? (uniques.delete(e); true) : false }
end
end
Примеры
t = [1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements
#=> [1,3,5,6,8]
t = [1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
t.uniq_elements { |z| z.round }
# => [2.0, 5.1]
Ответ 2
Здесь другой путь.
код
require 'set'
class Array
def uniq_elements(&prc)
prc ||= ->(e) { e }
uniques, dups = {}, Set.new
each do |e|
k = prc[e]
((uniques.key?(k)) ? (dups << k; uniques.delete(k)) :
uniques[k] = e) unless dups.include?(k)
end
uniques.values
end
end
<сильные > Примеры
t = [1,2,2,3,4,4,5,6,7,7,8,9,9,9]
t.uniq_elements #=> [1,3,5,6,8]
t = [1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
t.uniq_elements { |z| z.round } # => [2.0, 5.1]
Объяснение
- если
uniq_elements
вызывается с блоком, он принимается как proc prc
.
- если
uniq_elements
вызывается без блока, prc
- nil
, поэтому первая инструкция метода устанавливает prc
равную умолчанию proc (lambda).
- изначально пустой хэш,
uniques
, содержит представления об уникальных значениях. Значения представляют собой уникальные значения массива self
, ключи - это то, что возвращается, когда proc prc
передается значение массива и вызывается: k = prc[e]
.
- набор
dups
содержит элементы массива, которые, как оказалось, не уникальны. Это набор (а не массив) для ускорения поиска. В качестве альтернативы, может быть хеш с не уникальными значениями в качестве ключей и произвольными значениями.
- для каждого элемента
e
массива self
выполняются следующие шаги:
-
k = prc[e]
.
- Если
dups
содержит k
, e
- это dup, поэтому больше ничего не нужно делать; еще
- Если
uniques
имеет ключ k
, e
является dup, поэтому k
добавляется в набор dups
, а элемент с ключом k
удаляется из uniques
; еще
- элемент
k=>e
добавляется к uniques
в качестве кандидата для уникального элемента.
- возвращаются значения
unique
.
Ответ 3
class Array
def uniq_elements
counts = Hash.new(0)
arr = map do |orig_val|
converted_val = block_given? ? (yield orig_val) : orig_val
counts[converted_val] += 1
[converted_val, orig_val]
end
uniques = []
arr.each do |(converted_val, orig_val)|
uniques << orig_val if counts[converted_val] == 1
end
uniques
end
end
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
p t.uniq_elements
t=[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
p t.uniq_elements { |elmt| elmt.round }
--output:--
[1, 3, 5, 6, 8]
[2.0, 5.1]
Array # uniq не находит не дублированные элементы, а Array # uniq удаляет дубликаты.
Ответ 4
class Array
def uniq_elements
zip( block_given? ? map { |e| yield e } : self )
.each_with_object Hash.new do |(e, v), h| h[v] = h[v].nil? ? [e] : false end
.values.reject( &:! ).map &:first
end
end
[1,2,2,3,4,4,5,6,7,7,8,9,9,9].uniq_elements #=> [1, 3, 5, 6, 8]
[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2].uniq_elements &:round #=> [2.0, 5.1]
Ответ 5
- Создание и вызов метода по умолчанию - это пустая трата времени и
- Приведение всех в одну строку с использованием пыточных конструкций не делает код более эффективным - это просто делает код более сложным для понимания.
- В требовании операторов rubyists не используют имена файлов.
....
require 'set'
class Array
def uniq_elements
uniques = {}
dups = Set.new
each do |orig_val|
converted_val = block_given? ? (yield orig_val) : orig_val
next if dups.include? converted_val
if uniques.include?(converted_val)
uniques.delete(converted_val)
dups << converted_val
else
uniques[converted_val] = orig_val
end
end
uniques.values
end
end
t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9]
p t.uniq_elements
t=[1.0, 1.1, 2.0, 3.0, 3.4, 4.0, 4.2, 5.1, 5.7, 6.1, 6.2]
p t.uniq_elements {|elmt|
elmt.round
}
--output:--
[1, 3, 5, 6, 8]
[2.0, 5.1]