Comment grouper par nombre dans un tableau sans utiliser de boucle

arr = [1,2,1,3,5,2,4] 

Comment puis-je compter le tableau par valeur de groupe avec sorting? J’ai besoin de la sortie suivante:

 x[1] = 2 x[2] = 2 x[3] = 1 x[4] = 1 x[5] = 1 

 x = arr.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h } 

Disponible uniquement sous Ruby 1.9

Fondamentalement, la même chose que la réponse de Michael , mais un peu plus courte:

 x = arr.each_with_object(Hash.new(0)) {|e, h| h[e] += 1} 

Dans des situations similaires,

  • Lorsque l’élément de départ est un object mutable tel qu’un Array , un Hash , une Ssortingng , vous pouvez utiliser each_with_object , comme dans le cas ci-dessus.
  • Lorsque l’élément de départ est un object immuable tel que Numeric , vous devez utiliser inject comme ci-dessous.

    sum = (1..10).inject(0) {|sum, n| sum + n} # => 55

 x = Hash[arr.uniq.map{ |i| [i, arr.count(i)] }] 

Encore une autre – similaire aux autres – approche:

 result=Hash[arr.group_by{|x|x}.map{|k,v| [k,v.size]}] 
  1. Regrouper par valeur de chaque élément.
  2. Mappez le regroupement sur un tableau de paires [valeur, compteur] .
  3. Transformez le tableau de paris en valeurs-clés dans un Hash, c’est-à-dire accessible via le result[1]=2 ...

Chaque fois que vous trouvez quelqu’un affirmant que quelque chose est le plus rapide sur ce type de routine primitive, je trouve toujours intéressant de le confirmer car, sans confirmation, la plupart d’entre nous ne font que deviner. J’ai donc pris toutes les méthodes ici et les ai comparées.

J’ai pris un tableau de 120 liens que j’ai extrait d’une page Web que je devais regrouper par nombre et implémenté tous ces éléments à l’aide de seconds = Benchmark.realtime fait une boucle et obtient tous les temps.

Supposons que les liens sont le nom du tableau que je dois compter:

 #0.00077 seconds = Benchmark.realtime do counted_links = {} links.each { |e| counted_links[e] = links.count(e) if counted_links[e].nil?} end seconds #0.000232 seconds = Benchmark.realtime do counted_links = {} links.sort.group_by {|x|x}.each{|x,y| counted_links[x] = y.size} end #0.00076 seconds = Benchmark.realtime do Hash[links.uniq.map{ |i| [i, links.count(i)] }] end #0.000107 seconds = Benchmark.realtime do links.inject(Hash.new(0)) {|h, v| h[v] += 1; h} end #0.000109 seconds = Benchmark.realtime do links.each_with_object(Hash.new(0)) {|e, h| h[e] += 1} end #0.000143 seconds = Benchmark.realtime do links.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h } end 

Et puis un peu de rbuy pour trouver la réponse:

 times = [0.00077, 0.000232, 0.00076, 0.000107, 0.000109, 0.000143].min ==> 0.000107 

Donc, la méthode la plus rapide, ymmv bien sûr, est la suivante:

 links.inject(Hash.new(0)) {|h, v| h[v] += 1; h} 

Je suis sûr qu’il y a de meilleurs moyens,

 >> arr.sort.group_by {|x|x}.each{|x,y| print "#{x} #{y.size}\n"} 1 2 2 2 3 1 4 1 5 1 

assigne les valeurs x et y à un hachage si nécessaire.

Cela devrait le faire

 arr = [1,2,1,3,5,2,4] puts arr.inject(Hash.new(0)) {|h, v| h[v] += 1; h} #=> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1} 

Juste pour mémoire, j’ai récemment lu à propos de Object#tap ici . Ma solution serait:

Hash.new(0).tap{|h| arr.each{|i| h[i] += 1}}

La méthode #tap transmet l’appelant au bloc puis le renvoie. C’est très pratique lorsque vous devez construire un tableau / hachage de manière incrémentielle.

 arr = [1,2,1,3,5,2,4] r = {} arr.each { |e| r[e] = arr.count(e) if r[e].nil?} 

Les sorties

 pr #==> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}