Ich habe folgendes Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Wie erstelle ich eine Zählung für jedes identische Element ?
Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?
oder einen Hash erstellen Wo:
Wobei: Hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}
Enumerable#tally
. Mehr Infos hier .Antworten:
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] counts = Hash.new(0) names.each { |name| counts[name] += 1 } # => {"Jason" => 2, "Teresa" => 1, ....
quelle
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}
gibt Ihnen
{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
quelle
each_with_object
stattdessen verwenden, müsseninject
Sie;total
am Block nicht ( ) zurückgeben.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
names.tally
.Ruby v2.7 + (aktuell)
Ab Ruby v2.7.0 (veröffentlicht im Dezember 2019) enthält die Kernsprache jetzt
Enumerable#tally
- eine neue Methode , die speziell für dieses Problem entwickelt wurde:names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] names.tally #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Ruby v2.4 + (derzeit unterstützt, aber älter)
Der folgende Code war in Standard-Ruby nicht möglich, als diese Frage zum ersten Mal gestellt wurde (Februar 2011), da sie verwendet wird:
Object#itself
, das zu Ruby v2.2.0 hinzugefügt wurde (veröffentlicht im Dezember 2014).Hash#transform_values
, das zu Ruby v2.4.0 hinzugefügt wurde (veröffentlicht im Dezember 2016).Diese modernen Ergänzungen zu Ruby ermöglichen die folgende Implementierung:
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 + (veraltet)
Wenn Sie eine ältere Ruby-Version ohne Zugriff auf die oben genannte
Hash#transform_values
Methode verwenden, können Sie stattdessenArray#to_h
Folgendes verwenden , die Ruby v2.1.0 (veröffentlicht im Dezember 2013) hinzugefügt wurde:names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h #=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
Für noch ältere Ruby-Versionen (
<= 2.1
) gibt es mehrere Möglichkeiten, dies zu lösen, aber (meiner Meinung nach) gibt es keinen eindeutigen "besten" Weg. Siehe die anderen Antworten auf diesen Beitrag.quelle
count
anstelle vonsize
/length
?Array#size
undArray#length
,Array#count
kann ein optionales Argument oder Block nehmen; Wenn es jedoch mit keinem verwendet wird, ist seine Implementierung identisch. Genauer gesagt, alle drei Methoden rufenLONG2NUM(RARRAY_LEN(ary))
unter der Haube: Anzahl / Länge.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
sort_by{ |k, v| -v}
, nichtreverse
nötig! ;-)Mit Ruby 2.2.0 können Sie jetzt die
itself
Methode nutzen .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}
quelle
Hash#transform_values
können wir Ihren Code noch weiter vereinfachen:names.group_by(&:itself).transform_values(&:count)
Array#to_h
- was Ruby v2.1.0 hinzugefügt wurde (veröffentlicht im Dezember 2013 - dh fast 3 Jahre nach der ursprünglichen Frage) wurde gefragt!)Es gibt tatsächlich eine Datenstruktur, die dies tut :
MultiSet
.Leider gibt es keine
MultiSet
Implementierung in der Ruby-Kernbibliothek oder der Standardbibliothek, aber es gibt einige Implementierungen im Web.Dies ist ein großartiges Beispiel dafür, wie die Auswahl einer Datenstruktur einen Algorithmus vereinfachen kann. In diesem speziellen Beispiel verschwindet der Algorithmus sogar vollständig . Es ist buchstäblich nur:
Und das ist es. Beispiel mit 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
Beispiel mit 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'>
quelle
Multiset
unterliegt strengen mathematischen Regeln und unterstützt die typischen Mengenoperationen (Vereinigung, Schnittmenge, Komplement, ...) auf eine Weise, die größtenteils mit den Axiomen, Gesetzen und Theoremen der "normalen" mathematischen Mengenlehre übereinstimmt, obwohl einige wichtige Gesetze dies tun Nicht gedrückt halten, wenn Sie versuchen, sie auf Multisets zu verallgemeinern. Aber das geht weit über mein Verständnis der Sache hinaus. Ich benutze sie als Programmierdatenstruktur, nicht als mathematisches Konzept.{A, A, B} = {A, B}
. Dies ist eindeutig ein Verstoß gegen die Definition von Multi-Sets!Enumberable#each_with_object
erspart Ihnen die Rückgabe des endgültigen Hashs.names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }
Kehrt zurück:
=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}
quelle
each_with_object
Variante ist für mich besser lesbar alsinject
Ruby 2.7+
Ruby 2.7 wird
Enumerable#tally
genau für diesen Zweck eingeführt. Es gibt eine gute Zusammenfassung hier .In diesem Anwendungsfall:
array.tally # => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }
Dokumente zu den veröffentlichten Funktionen finden Sie hier .
Hoffe das hilft jemandem!
quelle
Das funktioniert.
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] result = {} arr.uniq.each{|element| result[element] = arr.count(element)}
quelle
O(n^2)
(was für einige Werte von Bedeutung sein wirdn
) und zusätzliche Arbeit leistet (es muss zum Beispiel für "Judah" 3x zählen)!. Ich würde auch vorschlagen,each
anstattmap
(das Kartenergebnis wird verworfen)Das Folgende ist ein etwas funktionalerer Programmierstil:
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]]
Ein Vorteil von
group_by
ist, dass Sie damit äquivalente, aber nicht genau identische Elemente gruppieren können: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]]
quelle
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}
Kredit Frank Wambutt
quelle
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}
quelle
Viele tolle Implementierungen hier.
Aber als Anfänger würde ich dies als am einfachsten zu lesen und zu implementieren betrachten
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}
Die Schritte, die wir unternommen haben:
names
Array durchlaufennames
Array vorkommtname
und einen Wert mit dem erstelltcount
Es mag etwas ausführlicher sein (und in Bezug auf die Leistung werden Sie unnötige Arbeit mit überschreibenden Schlüsseln leisten), aber meiner Meinung nach ist es einfacher zu lesen und zu verstehen, was Sie erreichen möchten
quelle
Hash.new(0)
. Der Pseudocode am nächsten. Das kann eine gute Sache für die Lesbarkeit sein, aber auch unnötige Arbeit kann die Lesbarkeit für Leser beeinträchtigen, die es bemerken, weil sie in komplexeren Fällen ein wenig Zeit damit verbringen, zu denken, dass sie verrückt werden, um herauszufinden, warum es getan wird.Dies ist eher ein Kommentar als eine Antwort, aber ein Kommentar würde dem nicht gerecht. Wenn Sie dies tun
Array = foo
, stürzen Sie mindestens eine Implementierung von IRB ab: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>
Das liegt daran, dass
Array
es eine Klasse ist.quelle
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"] arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}
Die verstrichene Zeit betrug 0,028 Millisekunden
Interessanterweise wurde die Implementierung von stupidgeek bewertet:
Die verstrichene Zeit betrug 0,041 Millisekunden
und die gewinnende Antwort:
Die verstrichene Zeit betrug 0,011 Millisekunden
:) :)
quelle