J'ai ce qui suit Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Comment produire un dé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}
Enumerable#tally
. Plus d'infos ici .Réponses:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, ....
la source
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
vous donne
{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
la source
each_with_object
place deinject
vous n'avez pas à retourner (;total
) au bloc.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
names.tally
.Ruby v2.7 + (dernière)
À partir de ruby v2.7.0 (publié en décembre 2019), le langage de base comprend désormais
Enumerable#tally
- une nouvelle méthode , conçue 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}
Ruby v2.4 + (actuellement pris en charge, mais plus ancien)
Le code suivant n'était pas possible en rubis standard lorsque cette question a été posée pour la première fois (février 2011), car il utilise:
Object#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 (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}
Ruby v2.2 + (obsolète)
Si vous utilisez une ancienne version de Ruby , sans accès à la
Hash#transform_values
méthode mentionnée ci-dessus , vous pouvez utiliser à la placeArray#to_h
, qui a été ajoutée à Ruby v2.1.0 (publiée en décembre 2013):names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Pour les versions ruby encore plus anciennes (
<= 2.1
), il existe plusieurs façons de résoudre ce problème, mais (à mon avis) il n'y a pas de "meilleure" façon claire. Voir les autres réponses à ce post.la source
count
au lieu desize
/length
?Array#size
etArray#length
,Array#count
peut prendre un argument ou un bloc optionnel; mais s'il est utilisé avec ni l'un ni l'autre, son implémentation est identique. Plus précisément, les trois méthodes appellentLONG2NUM(RARRAY_LEN(ary))
sous le capot: nombre / longueur.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
sort_by{ |k, v| -v}
, pasreverse
besoin! ;-)Maintenant, en utilisant Ruby 2.2.0, vous pouvez tirer parti de la
itself
méthode .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}
la source
Hash#transform_values
qui nous permet de simplifier encore plus votre code:names.group_by(&:itself).transform_values(&:count)
Array#to_h
- qui a été ajouté à Ruby v2.1.0 (publié en décembre 2013 - soit près de 3 ans après la question initiale a été demandé!)Il y a en fait une structure de données qui le fait:
MultiSet
.Malheureusement, il n'y a pas d'
MultiSet
implémentation dans la bibliothèque principale de Ruby ou dans la bibliothèque standard, mais quelques implémentations flottent sur le Web.Ceci 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:
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'>
la source
Multiset
est régi par des règles mathématiques strictes et prend en charge les opérations d'ensembles typiques (union, intersection, complément, ...) d'une manière qui est principalement cohérente avec les axiomes, lois et théorèmes de la théorie mathématique des ensembles "normale", bien que certaines lois importantes le fassent ne tient pas lorsque vous essayez de les généraliser en multisets. Mais c'est bien au-delà de ma compréhension de la question. Je les utilise comme une structure de données de programmation, pas comme un concept mathématique.{A, A, B} = {A, B}
. C'est clairement une violation de la définition même des multi-ensembles!Enumberable#each_with_object
vous évite de renvoyer le hachage final.names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }
Retour:
=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
la source
each_with_object
variante est plus lisible pour moi queinject
Ruby 2.7+
Ruby 2.7 introduit
Enumerable#tally
précisément dans ce but. 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 }
La documentation sur les fonctionnalités publiées est ici .
J'espère que cela aide quelqu'un!
la source
Cela marche.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)}
la source
O(n^2)
(qui importera pour certaines valeurs den
) et fait un travail supplémentaire (cela doit compter pour "Juda" 3x, par exemple) !. Je suggérerais égalementeach
au lieu demap
(le résultat de la carte est en cours deVoici 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]]
la source
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
la source
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}
la source
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 étapes que nous avons prises:
names
tableaunames
tableauname
et une valeur en utilisant lecount
Cela peut être légèrement plus détaillé (et en termes de performances, vous ferez un travail inutile avec des touches de priorité), mais à mon avis, plus facile à lire et à comprendre pour ce que vous voulez réaliser
la source
Hash.new(0)
. Le plus proche du pseudocode. Cela peut être une bonne chose pour la lisibilité, mais faire un travail inutile peut également nuire à la lisibilité pour les lecteurs qui le remarquent, car dans des cas plus complexes, ils passeront un peu de temps à penser qu'ils deviennent fous à essayer de comprendre pourquoi cela est fait.C'est plus un commentaire qu'une réponse, mais un commentaire ne lui rendrait pas justice. Si vous le 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 `<main>' C:\Documents and Settings\a.grimm>
C'est parce que
Array
c'est une classe.la source
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, l'implémentation de stupidgeek a comparé:
Temps écoulé 0,041 millisecondes
et la réponse gagnante:
Temps écoulé 0,011 millisecondes
:)
la source