Come selezionare elementi unici

Vorrei estendere la class Array con un metodo uniq_elements che restituisce quegli elementi con molteplicità di uno. Vorrei anche usare chiusure per il mio nuovo metodo come con uniq . Per esempio:

 t=[1,2,2,3,4,4,5,6,7,7,8,9,9,9] t.uniq_elements # => [1,3,5,6,8] 

Esempio con chiusura:

 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] 

tt.uniqt.to_set-t.uniq.to_set funziona. Non mi interessa la velocità, la chiamo solo una volta nel mio programma, quindi può essere un po ‘lento.

Metodo di supporto

Questo metodo utilizza l’helper:

 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 

Questo metodo è simile a Array # – . La differenza è illustrata nel seguente esempio:

 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] 

Come vedi, a contiene tre 3 e b contiene due, quindi i primi due 3 in a vengono rimossi nella costruzione di c ( a non è mutato). Quando b contiene almeno tante istanze di un elemento come a , c non contiene istanze di quell’elemento. Per rimuovere elementi che iniziano alla fine di a :

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

Array#difference! potrebbe essere definito in modo ovvio.

Ho trovato molti usi per questo metodo: qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui , qui e qui .

Ho proposto di aggiungere questo metodo al core di Ruby.

Se utilizzato con l’ Array#- , questo metodo semplifica l’estrazione degli elementi univoci da un array a :

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

Funziona perché

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

contiene tutti gli elementi non univoci di a (ognuno eventualmente più di una volta).

Problema a portata di mano

Codice

 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 

Esempi

 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] 

Ecco un altro modo.

Codice

 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 

Esempi

 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] 

Spiegazione

  • se uniq_elements viene chiamato con un blocco, viene ricevuto come proc prc .
  • se uniq_elements viene chiamato senza un blocco, prc è nil , quindi la prima istruzione del metodo imposta prc uguale al proc predefinito (lambda).
  • un hash inizialmente vuoto, uniques , contiene rappresentazioni dei valori univoci. I valori sono i valori univoci dell'array self , le chiavi sono ciò che viene restituito quando il prc proc è passato al valore dell'array e chiamato: k = prc[e] .
  • il set dups contiene gli elementi dell'array che hanno trovato non essere unici. È un set (piuttosto che un array) per velocizzare le ricerche. In alternativa, se potrebbe essere un hash con i valori non univoci come chiavi e valori arbitrari.
  • i seguenti passaggi vengono eseguiti per ciascun elemento e dell'array self :
    • k = prc[e] è calcolato.
    • se dups contiene k , e è un dup, quindi non c'è bisogno di fare altro; altro
    • se uniques ha una chiave k , e è un dup, quindi k viene aggiunto al set dups e l'elemento con la chiave k viene rimosso da uniques ; altro
    • l'elemento k=>e viene aggiunto agli uniques come candidato per un elemento unico.
  • i valori di unique vengono restituiti.
 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 non trova elementi non duplicati, piuttosto l'array # uniq rimuove i duplicati.

 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] 
  1. Creare e chiamare un proc predefinito è una perdita di tempo, e
  2. Cramming tutto in una sola riga usando costrutti torturati non rende il codice più efficiente, ma rende il codice più difficile da capire.
  3. Nelle dichiarazioni require, i rubyists non inseriscono in maiuscolo i nomi dei file.

….

 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]