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}
]
50
demandé sur the Tin Man 2009-02-20 17:17:21

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.

115
répondu nimrodm 2009-02-20 14:39:02

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.">
65
répondu vladr 2011-09-27 13:47:32

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 }
29
répondu Manish Shrivastava 2017-05-04 21:15:21

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.

12
répondu Kaoru 2017-05-04 21:16:47

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.

10
répondu Carpela 2018-03-23 21:35:04

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.">
7
répondu dan 2012-05-03 22:03:20
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
répondu Milan Novota 2009-02-20 14:56:51

À 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 }
1
répondu Ana María Martínez Gómez 2018-09-24 20:54:04

Implémentation Simple:

(errors_hash = {}).default = 0
array_of_errors.each { |error| errors_hash[error] += 1 }
0
répondu Evan Senter 2009-02-21 02:24:20

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 êtes capable de faire des choses comme:

res['aa'].size 
0
répondu metakungfu 2012-11-13 18:17:50

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
}
0
répondu Arnold Roa 2018-07-28 03:39:49