Comment compter des éléments de chaîne identiques dans un tableau Ruby

J’ai le Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] suivant Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

Comment puis-je produire un compte pour chaque élément identique ?

 Where: "Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1? 

ou produire un hash Où:

Où: hash = {“Jason” => 2, “Judah” => 3, “Allison” => 1, “Teresa” => 1, “Michelle” => 1}

 names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, .... 
 names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total} 

vous donne

 {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Maintenant, en utilisant Ruby 2.2.0, vous pouvez utiliser la méthode itselfitself .

 names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = {} names.group_by(&:itself).each { |k,v| counts[k] = v.length } # counts > {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Il existe en fait une structure de données qui fait ceci: MultiSet .

Malheureusement, il n’y a pas d’implémentation MultiSet dans la bibliothèque principale ou la bibliothèque standard de Ruby, mais quelques implémentations flottent sur le Web.

C’est un excellent exemple de la façon dont le choix d’une structure de données peut simplifier un algorithme. En fait, dans cet exemple particulier, l’algorithme disparaît même complètement . C’est littéralement juste:

 Multiset.new(*names) 

Et c’est tout. Exemple, en utilisant https://GitHub.Com/Josh/Multimap/ :

 require 'multiset' names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison] histogram = Multiset.new(*names) # => # histogram.multiplicity('Judah') # => 3 

Exemple, en utilisant http://maraigue.hhiro.net/multiset/index-en.php :

 require 'multiset' names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison] histogram = Multiset[*names] # => # 

Le code suivant n’était pas possible dans Ruby standard lorsque cette question a été posée pour la première fois (février 2011), car elle utilise:

  • Object#itselfObject#itself , qui a été ajouté à Ruby v2.2.0 (publié en décembre 2014).
  • Hash#transform_values , qui a été ajouté à Ruby v2.4.0 (sorti en décembre 2016).

Ces ajouts modernes à Ruby permettent la mise en œuvre suivante:

 names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] names.group_by(&:itself).transform_values(&:count) #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Enumberable#each_with_object vous évite de retourner le hachage final.

 names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 } 

Résultats:

 => {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Cela marche.

 arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)} 

Voici un style de programmation légèrement plus fonctionnel:

 array_with_lower_case_a = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] hash_grouped_by_name = array_with_lower_case_a.group_by {|name| name} hash_grouped_by_name.map{|name, names| [name, names.length]} => [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]] 

L’un des avantages de group_by est que vous pouvez l’utiliser pour regrouper des éléments équivalents mais pas exactement identiques:

 another_array_with_lower_case_a = ["Jason", "jason", "Teresa", "Judah", "Michelle", "Judah Ben-Hur", "JUDAH", "Allison"] hash_grouped_by_first_name = another_array_with_lower_case_a.group_by {|name| name.split(" ").first.capitalize} hash_grouped_by_first_name.map{|first_name, names| [first_name, names.length]} => [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]] 
 a = [1, 2, 3, 2, 5, 6, 7, 5, 5] a.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 } # => {1=>1, 2=>2, 3=>1, 5=>3, 6=>1, 7=>1} 

Crédit Frank Wambutt

 names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] Hash[names.group_by{|i| i }.map{|k,v| [k,v.size]}] # => {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

C’est plus un commentaire qu’une réponse, mais un commentaire ne rendrait pas justice. Si vous faites Array = foo , vous plantez au moins une implémentation d’IRB:

 C:\Documents and Settings\a.grimm>irb irb(main):001:0> Array = nil (irb):1: warning: already initialized constant Array => nil C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3177:in `rl_redisplay': undefined method `new' for nil:NilClass (NoMethodError) from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3873:in `readline_internal_setup' from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4704:in `readline_internal' from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4727:in `readline' from C:/Ruby19/lib/ruby/site_ruby/1.9.1/readline.rb:40:in `readline' from C:/Ruby19/lib/ruby/1.9.1/irb/input-method.rb:115:in `gets' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:139:in `block (2 levels) in eval_input' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:271:in `signal_status' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:138:in `block in eval_input' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `call' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `buf_input' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:103:in `getc' from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:205:in `match_io' from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:75:in `match' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:287:in `token' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:263:in `lex' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:234:in `block (2 levels) in each_top_level_statement' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `loop' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `block in each_top_level_statement' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `catch' from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `each_top_level_statement' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:153:in `eval_input' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:70:in `block in start' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `catch' from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `start' from C:/Ruby19/bin/irb:12:in `
' C:\Documents and Settings\a.grimm>

C’est parce que Array est une classe.

Beaucoup de bonnes implémentations ici.

Mais en tant que débutant, je considérerais cela comme le plus facile à lire et à mettre en œuvre.

 names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] name_frequency_hash = {} names.each do |name| count = names.count(name) name_frequency_hash[name] = count end #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

Les mesures que nous avons sockets:

  • nous avons créé le hash
  • nous avons bouclé le tableau de names
  • nous avons compté combien de fois chaque nom est apparu dans le tableau des names
  • nous avons créé une clé en utilisant le name et une valeur en utilisant le count

Cela peut être légèrement plus verbeux (et sur le plan de la performance, vous allez faire un travail inutile avec des clés prépondérantes), mais à mon avis plus facile à lire et à comprendre pour ce que vous voulez atteindre

 arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})} 

Temps écoulé 0.028 millisecondes

Fait intéressant, la mise en œuvre de stupidgeek a été évaluée:

Temps écoulé 0.041 millisecondes

et la réponse gagnante:

Temps écoulé 0.011 millisecondes

🙂