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 il 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}
]
11 réponses
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
Remarque: je viens de remarquer que vous avez dit que le tableau est déjà trié. Le code ci-dessus ne nécessite pas de tri. L'utilisation de cette propriété peut produire du code plus rapide.
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.">
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 d'une ligne est:
result = words.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }
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}
Briser cela dans 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.
Que diriez-vous de ce qui suit:
things = [1, 2, 2, 3, 3, 3, 4]
things.uniq.map{|t| [t,things.count(t)]}.to_h
Il se sent un peu plus propre et plus descriptif de ce que nous essayons réellement de faire.
Je soupçonne qu'il fonctionnerait également mieux avec les 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)
Donc c'est un peu plus rapide.
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
Ensuite, exécutez 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.">
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}]
À Partir de Ruby >= 2.2 vous pouvez utiliser itself
: array.group_by(&:itself).transform_values(&:count)
Avec un peu 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 }
Implémentation Simple:
(errors_hash = {}).default = 0
array_of_errors.each { |error| errors_hash[error] += 1 }
Voici l'exemple de tableau:
a=["aa","bb","cc","bb","bb","cc"]
- , Sélectionnez toutes les clés uniques.
- 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 êtes capable de faire des choses comme:
res['aa'].size
Si vous voulez l'utiliser souvent, je 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 ensuite utiliser 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
}