Как выбрать уникальные элементы

Я хотел бы расширить класс 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]