J'ai le Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
suivant
Comment produire un compte pour chaque élément identique ?
Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?
ou produire un hachage 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, avec Ruby 2.2.0, vous pouvez utiliser la méthode itself
.
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}
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 il utilise:
Object#itself
, ajouté à Ruby v2.2.0 (publié en décembre 2014).Hash#transform_values
, ajouté à Ruby v2.4.0 (publié en décembre 2016).Ces ajouts modernes à Ruby permettent l’implémentation 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}
(Février 2019) Edit: Au moment de la rédaction de cet article, cette fonctionnalité est pas encore disponible, alors considérez ce commentaire comme un espace réservé pour l'avenir ...
Mais il y a eu une récente amélioration de la langue . Si tout se passe comme prévu, nous devrions nous attendre à voir une nouvelle méthode, Enumerable#tally
, ajoutée à Ruby v2.7.0 (prévue pour décembre 2019). Cette méthode ajoute une nouvelle syntaxe spécifiquement pour ce problème!
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
names.tally
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Je modifierai ce commentaire lors de la publication de Ruby 2.7.0 afin de confirmer que la méthode est dans le langage de base.
Il y a 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 Ruby Core ou standard, 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 même complètement disparaît. 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)
# => #<Multiset: {"Jason", "Jason", "Teresa", "Judah", "Judah", "Judah", "Michelle", "Allison"}>
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]
# => #<Multiset:#2 'Jason', #1 'Teresa', #3 'Judah', #1 'Michelle', #1 'Allison'>
Enumberable#each_with_object
vous évite de renvoyer 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}
Ce qui suit est 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]]
Un avantage 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]]
Cela marche.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}
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
Ruby 2.7 +
Ruby 2.7 introduit Enumerable#tally
dans ce but précis. Il y a un bon résumé ici .
Dans ce cas d'utilisation:
array.tally
# => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }
Les documents sur les fonctionnalités publiées sont ici .
J'espère que cela aide quelqu'un!
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}
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 prises:
names
names
name
et une valeur avec la variable count
Il est peut-être un peu plus verbeux (et en termes de performances, vous ferez un travail inutile avec des clés prioritaires), mais à mon avis plus facile à lire et à comprendre pour ce que vous voulez réaliser
C'est plus un commentaire qu'une réponse, mais un commentaire ne lui rendrait pas justice. Si vous faites Array = foo
, vous plantez au moins une implémentation de 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 `<main>'
C:\Documents and Settings\a.grimm>
C'est parce que Array
est une classe.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}
Temps écoulé 0.028 milliseconds
fait intéressant, la mise en œuvre de stupidgeek a permis d'évaluer:
Temps écoulé 0,041 millisecondes
et la réponse gagnante:
Temps écoulé 0,011 milliseconde
:)