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

Я хотел бы расширить класс 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 не работают. Меня не волнует скорость, я вызываю ее только один раз в своей программе, поэтому она может быть медленной.


person Konstantin    schedule 28.07.2014    source источник
comment
потому что я пропустил, теперь я исключил это.   -  person sawa    schedule 28.07.2014
comment
спасибо, я использовал этот метод до сих пор, но он не получает блок: _1_   -  person Konstantin    schedule 28.07.2014


Ответы (6)


Вспомогательный метод

Этот метод использует помощник:

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 содержит три тройки, а b содержит две, поэтому первые две тройки в a удаляются при построении c (a не изменяется). Когда b содержит столько же экземпляров элемента, сколько и a, c не содержит экземпляров этого элемента. Чтобы удалить элементы, начинающиеся с конца a:

a.reverse.difference(b).reverse #=> [3, 1, 2, 2]

Array#difference! можно определить очевидным образом.

Я предложил добавить этот метод в ядро ​​Ruby.

При использовании с Array#- этот метод упрощает извлечение уникальных элементов из массива a:

Это работает, потому что

a = [1,3,2,4,3,4]
u = a.uniq          #=> [1, 2, 3, 4]
u - a.difference(u) #=> [1, 2]

содержит все неуникальные элементы a (каждый, возможно, более одного раза).

a.difference(u)     #=> [3,4]    

Проблема под рукой

Код

Примеры

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]
person Cary Swoveland    schedule 28.07.2014

Код

Примеры

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 вызывается с блоком, он принимается как процесс prc.

  • если uniq_elements вызывается без блока, prc равно nil, поэтому первый оператор метода устанавливает prc равным процедуре по умолчанию (лямбда).
  • изначально пустой хэш uniques содержит представления уникальных значений. Значения — это уникальные значения массива self, ключи — это то, что возвращается, когда процедуре prc передается значение массива и вызывается: k = prc[e].
  • набор dups содержит элементы массива, признанные неуникальными. Это набор (а не массив) для ускорения поиска. В качестве альтернативы, if может быть хэшем с неуникальными значениями в качестве ключей и произвольными значениями.
  • если dups содержит k, e является дубликатом, поэтому больше ничего делать не нужно; еще
  • the following steps are performed for each element e of the array self:
    • k = prc[e] is computed.
    • если uniques имеет ключ k, e является дубликатом, поэтому k добавляется в набор dups, а элемент с ключом k удаляется из uniques; еще
    • элемент k=>e добавляется к uniques как кандидат на уникальный элемент.
    • возвращаются значения unique.
  • Array#uniq не находит неповторяющиеся элементы, а Array#uniq удаляет дубликаты.
person Cary Swoveland    schedule 28.07.2014
comment
VII, после блока def uelements(a) t=a.sort u=[] u.push t1[0] if t1[0] != t1[1] for i in 1..t.size-2 do u.push t[i] if t[i] != t[i+1] && t[i] != t[i-1] end u.push t[-1] if t[-2] != t[-1] return u end рассмотрим _2_. - person Konstantin; 28.07.2014

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]

Создание и вызов процедуры по умолчанию — пустая трата времени, и

person 7stud    schedule 28.07.2014
comment
each_with_index() не требуется. Вы можете просто вставлять 1 каждый раз. Также обратите внимание: вы проходите массив дважды (минимальное количество раз), но затем вам дополнительно нужно вызывать keys(). - person Cary Swoveland; 28.07.2014

  1. Втискивание всего в одну строку с использованием вымученных конструкций не делает код более эффективным — это просто делает код более трудным для понимания.
  2. В операторах require рубисты не пишут имена файлов с заглавной буквы.
  3. Используйте
    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]
    
    :

....

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]
person 7stud    schedule 28.07.2014
comment
next if... Достаточно честно, но синтаксис ruby ​​был создан для того, чтобы вы могли передавать метод в другой метод без указания аргумента. Кроме того, необоснованные вызовы методов не бесплатны. unless Я следую "Perl Best Practices" в том, что касается разве что... это мерзость. В любом случае, хорошее решение, использующее только один обход массива. - person Cary Swoveland; 28.07.2014
comment
Я нашел много применений для этого метода: здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь, здесь и здесь. - person 7stud; 29.07.2014


person    schedule
comment
Теперь избавьтесь от своего хэша и вместо этого используйте Hash.new(0); нет необходимости создавать все эти массивы. - person 7stud; 28.07.2014
comment
Я сделал это до того, как был написан ваш комментарий, но спасибо за внимательность. - person 7stud; 28.07.2014
comment
Да, но я подумал об этом, когда впервые прочитал твой пост! Я все еще думаю, что мины более эффективны... ну, вы хорошо обходите вызов map(), если не указан блок. Ах... но ваш пример с округлением не будет работать, потому что вы не сохраняете сопоставление между исходным значением и преобразованным значением. - person Boris Stitnicky; 28.07.2014
comment
Спасибо, 7. Сначала у меня был _1_, как вы предлагаете, но я перешел на _2_, потому что код был коротким, хотя _3_ может читаться лучше. Я предпочитаю использовать proc, отчасти потому, что _4_ сразу сообщает читателю, что параметр может быть передан, и первая строка поясняет это; голый _5_ предполагает обратное, пока читатель не увидит _6_. Я сократил _7_. - person 7stud; 28.07.2014