web-dev-qa-db-fra.com

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

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}

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

vous donne

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

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}
24
Ahmed Fahmy

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:

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.

20
Tom Lord

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'>
16
Jörg W Mittag

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}
12
Anconia

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]]
6
Andrew Grimm

Cela marche.

arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}
5
Shreyas
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

4
narzero

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!

3
SRack
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}
2
Arup Rakshit

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:

  • nous avons créé le hash
  • nous avons bouclé sur le tableau names
  • nous avons compté combien de fois chaque nom est apparu dans le tableau names
  • nous avons créé une clé avec la variable 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

1
Sami Birnbaum

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.

1
Andrew Grimm
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

:)

0
Alex Moore-Niemi