web-dev-qa-db-fra.com

Comment compter les éléments en double dans un tableau Ruby

J'ai un tableau trié:

[
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="There is insufficient system memory to run this query.">'
]

Je voudrais obtenir quelque chose comme ça, mais cela ne doit pas être un hachage:

[
  {:error => 'FATAL <error title="Request timed out.">', :count => 2},
  {:error => 'FATAL <error title="There is insufficient system memory to run this query.">', :count => 1}
]
65
Željko Filipin

Le code suivant imprime ce que vous avez demandé. Je vous laisse décider comment utiliser réellement pour générer le hachage que vous recherchez:

# 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

Note: Je viens de remarquer que vous avez dit que le tableau était déjà trié. Le code ci-dessus ne nécessite pas de tri. L'utilisation de cette propriété peut produire un code plus rapide.

124
nimrodm

Vous pouvez le faire très succinctement (une ligne) en utilisant inject :

a = ['FATAL <error title="Request timed out.">',
      'FATAL <error title="Request timed out.">',
      'FATAL <error title="There is insufficient ...">']

b = a.inject(Hash.new(0)) {|h,i| h[i] += 1; h }

b.to_a.each {|error,count| puts "#{count}: #{error}" }

Produira:

1: FATAL <error title="There is insufficient ...">
2: FATAL <error title="Request timed out.">
68
vladr

Si vous avez un tableau comme celui-ci:

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

où vous devez compter les éléments en double, une solution sur une ligne est:

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

Une approche différente des réponses ci-dessus, en utilisant 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}

Décomposer cela en ses différents appels de méthode:

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 a été ajouté dans Ruby 1.8.7.

16
Kaoru

Qu'en est-il des éléments suivants:

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

Cela semble plus propre et plus descriptif de ce que nous essayons réellement de faire.

Je soupçonne qu'il fonctionnerait également mieux avec de grandes collections que celles qui itèrent sur chaque valeur.

Test de performance de référence:

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)

C'est donc un peu plus rapide.

14
Carpela

Personnellement, je le ferais de cette façon:

# myprogram.rb
a = ['FATAL <error title="Request timed out.">',
'FATAL <error title="Request timed out.">',
'FATAL <error title="There is insufficient system memory to run this query.">']
puts a

Exécutez ensuite le programme et dirigez-le vers uniq -c:

Ruby myprogram.rb | uniq -c

Sortie:

 2 FATAL <error title="Request timed out.">
 1 FATAL <error title="There is insufficient system memory to run this query.">
8
dan

Depuis Ruby> = 2.2, vous pouvez utiliser itself : array.group_by(&:itself).transform_values(&:count)

Avec plus de détails:

array = [
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="There is insufficient system memory to run this query.">'
];

array.group_by(&:itself).transform_values(&:count)
 => { "FATAL <error title=\"Request timed out.\">"=>2,
      "FATAL <error title=\"There is insufficient system memory to run this query.\">"=>1 }
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}]
3
Milan Novota

Si vous souhaitez l'utiliser souvent, je vous suggère de le faire:

# 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

Chargez-le avec

Array.include CoreExtensions::Array::DuplicatesCounter

Et puis utilisez de n'importe où avec juste:

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

Les versions Ruby> = 2.7 auront Enumerable # tally .

par exemple:

["a", "b", "c", "b"].tally 
# => {"a"=>1, "b"=>2, "c"=>1}
0
Santhosh

Voici l'exemple de tableau:

a=["aa","bb","cc","bb","bb","cc"]
  1. Sélectionnez toutes les clés uniques.
  2. Pour chaque clé, nous les accumulerons dans un hachage pour obtenir quelque chose comme ceci: {'bb' => ['bb', 'bb']}
 res = a.uniq.inject ({}) {| accu, uni | accu.merge ({uni => a.select {| i | i == uni}})} 
 {"aa" => ["aa"], "bb" => ["bb", "bb", "bb"], "cc" => ["cc", "cc"]} 

Maintenant, vous pouvez faire des choses comme:

res['aa'].size 
0
metakungfu

Implémentation simple:

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