Como selecionar elementos exclusivos

Eu gostaria de estender a class Array com um método uniq_elements que retorna esses elementos com a multiplicidade de um. Eu também gostaria de usar closures para o meu novo método como com o uniq . Por exemplo:

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

Exemplo com fechamento:

 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] 

Nem tt.uniq nem t.to_set-t.uniq.to_set funcionam. Eu não me importo com velocidade, eu chamo isso apenas uma vez no meu programa, então pode ser lento.

Método auxiliar

Este método usa o auxiliar:

 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 

Este método é semelhante ao Array # – . A diferença é ilustrada no exemplo a seguir:

 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] 

Como você vê, a contém três 3’s e b contém dois, então os dois primeiros 3’s em a são removidos na construção de c ( a não é mutada). Quando b contém pelo menos tantas instâncias de um elemento como a , c não contém instâncias desse elemento. Para remover elementos que começam no final de a :

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

Array#difference! poderia ser definido da maneira óbvia.

Eu encontrei muitos usos para este método: aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui , aqui aqui e aqui

Eu propus que esse método fosse adicionado ao núcleo do Ruby.

Quando usado com Array#- , esse método facilita a extração dos elementos exclusivos de um array a :

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

Isso funciona porque

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

contém todos os elementos não exclusivos de a (cada um possivelmente mais de uma vez).

Problema à mão

Código

 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 

Exemplos

 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] 

Aqui está outro jeito.

Código

 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 

Exemplos

 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] 

Explicação

  • Se uniq_elements é chamado com um bloco, ele é recebido como o proc prc .
  • se uniq_elements é chamado sem um bloco, prc é nil , então a primeira instrução do método define prc igual ao proc (lambda) padrão.
  • um hash inicialmente vazio, uniques , contém representações dos valores únicos. Os valores são os valores únicos da matriz self , as chaves são o que é retornado quando o proc prc é passado o valor da matriz e chamado: k = prc[e] .
  • o conjunto de dups contém os elementos da matriz que não são exclusivos. É um conjunto (em vez de um array) para acelerar as pesquisas. Alternativamente, se poderia ser um hash com os valores não exclusivos como chaves e valores arbitrários.
  • as etapas a seguir são executadas para cada elemento e da matriz self :
    • k = prc[e] é calculado.
    • se dups contiver k , e é um dup, então nada mais precisa ser feito; outro
    • se uniques tem uma chave k , e é um dup, então k é adicionado ao conjunto de dups e o elemento com a chave k é removido de uniques ; outro
    • o elemento k=>e é adicionado aos uniques como candidato a um elemento único.
  • os valores de unique são retornados.
 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 não encontra elementos não duplicados, mas Array # uniq remove duplicados.

 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. Criar e chamar um proc padrão é uma perda de tempo e
  2. Colocar tudo em uma linha usando construções torturadas não torna o código mais eficiente – apenas torna o código mais difícil de entender.
  3. Em exigir instruções, os rubystas não capitalizam nomes de arquivos.

….

 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]