Como contar elementos duplicados em um array Ruby

Eu tenho uma matriz ordenada:

[ 'FATAL ', 'FATAL ', 'FATAL ' ] 

Eu gostaria de obter algo assim, mas não precisa ser um hash:

 [ {:error => 'FATAL ', :count => 2}, {:error => 'FATAL ', :count => 1} ] 

O código a seguir imprime o que você pediu. Eu vou deixar você decidir como realmente usar para gerar o hash que você está procurando:

 # sample array a=["aa","bb","cc","bb","bb","cc"] # make the hash default to 0 so that += will work correctly b = Hash.new(0) # iterate over the array, counting duplicate entries a.each do |v| b[v] += 1 end b.each do |k, v| puts "#{k} appears #{v} times" end 

Nota: Eu notei que você disse que a matriz já está classificada. O código acima não requer sorting. Usando essa propriedade pode produzir código mais rápido.

Você pode fazer isso de maneira muito sucinta (uma linha) usando inject :

 a = ['FATAL ', 'FATAL ', 'FATAL '] b = a.inject(Hash.new(0)) {|h,i| h[i] += 1; h } b.to_a.each {|error,count| puts "#{count}: #{error}" } 

Vai produzir:

 1: FATAL  2: FATAL  

Se você tem um array como este:

 words = ["aa","bb","cc","bb","bb","cc"] 

onde você precisa contar elementos duplicados, uma solução de uma linha é:

 result = words.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 } 

Uma abordagem diferente para as respostas acima, usando Enumerable # group_by .

 [1, 2, 2, 3, 3, 3, 4].group_by(&:itself).map { |k,v| [k, v.count] }.to_h # {1=>1, 2=>2, 3=>3, 4=>1} 

Quebrando isso em seu método diferente chama:

 a = [1, 2, 2, 3, 3, 3, 4] a = a.group_by(&:itself) # {1=>[1], 2=>[2, 2], 3=>[3, 3, 3], 4=>[4]} a = a.map { |k,v| [k, v.count] } # [[1, 1], [2, 2], [3, 3], [4, 1]] a = a.to_h # {1=>1, 2=>2, 3=>3, 4=>1} 

Enumerable#group_by foi adicionado no Ruby 1.8.7.

Como sobre o seguinte:

 things = [1, 2, 2, 3, 3, 3, 4] things.uniq.map{|t| [t,things.count(t)]}.to_h 

Parece mais limpo e mais descritivo do que estamos realmente tentando fazer.

Eu suspeito que também funcionaria melhor com collections grandes do que as que se repetem em cada valor.

Teste de desempenho de benchmark:

 a = (1...1000000).map { rand(100)} user system total real inject 7.670000 0.010000 7.680000 ( 7.985289) array count 0.040000 0.000000 0.040000 ( 0.036650) each_with_object 0.210000 0.000000 0.210000 ( 0.214731) group_by 0.220000 0.000000 0.220000 ( 0.218581) 

Então é um pouco mais rápido.

Pessoalmente eu faria assim:

 # myprogram.rb a = ['FATAL ', 'FATAL ', 'FATAL '] puts a 

Em seguida, execute o programa e canalize-o para uniq -c:

 ruby myprogram.rb | uniq -c 

Saída:

  2 FATAL  1 FATAL  
 a = [1,1,1,2,2,3] a.uniq.inject([]){|r, i| r << { :error => i, :count => a.select{ |b| b == i }.size } } => [{:count=>3, :error=>1}, {:count=>2, :error=>2}, {:count=>1, :error=>3}] 

Se você quiser usar isso, eu sugiro fazer isso:

 # lib/core_extensions/array/duplicates_counter module CoreExtensions module Array module DuplicatesCounter def count_duplicates self.each_with_object(Hash.new(0)) { |element, counter| counter[element] += 1 }.sort_by{|k,v| -v}.to_h end end end end 

Carregue com

 Array.include CoreExtensions::Array::DuplicatesCounter 

E então use de qualquer lugar com apenas:

 the_ar = %w(aaaaaaa chao chao chao hola hola mundo hola chao cachacho hola) the_ar.duplicates_counter { "a" => 7, "chao" => 4, "hola" => 4, "mundo" => 1, "cachacho" => 1 } 

Implementação simples:

 (errors_hash = {}).default = 0 array_of_errors.each { |error| errors_hash[error] += 1 } 

Aqui está o array de amostra:

 a=["aa","bb","cc","bb","bb","cc"] 
  1. Selecione todas as chaves exclusivas.
  2. Para cada chave, vamos acumulá-los em um hash para obter algo assim: {'bb' => ['bb', 'bb']}
     res = a.uniq.inject ({}) {| accu, uni |  accu.merge ({uni => a.selecionar {| i | i == uni}})}
     {"aa" => ["aa"], "bb" => ["bb", "bb", "bb"], "cc" => ["cc", "cc"]}

Agora você é capaz de fazer coisas como:

 res['aa'].size