2013-10-05 10 views
5

reduziert habe ich ein Array wie soHash.new als Anfangswert verwenden, wenn ein Array

[1,1,2,3,3,3,4,5,5] 

und ich möchte die Anzahl der Vorkommen von jeder Zahl zählen, die ich wie so zu tun, ich versucht,

[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |hash,number| hash[number] += 1 } 

Das Problem ist, ich die folgende Fehlermeldung erhalten, wenn ich versuche, es zu laufen

NoMethodError: undefined method `[]=' for 1:Fixnum 
    from (irb):6:in `block in irb_binding' 
    from (irb):6:in `each' 
    from (irb):6:in `reduce' 
    from (irb):6 

kann mich den Anfangswert setzen wie das, oder bekomme ich das falsch?

Antwort

7

Sie können each_with_object für sauberere Syntax

[1,1,2,3,3,3,4,5,5].each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 } 

Beachten Sie, dass die Reihenfolge der Argumente ist umgekehrt das heißt |number, hash|

+0

ich immer vergessen, dass es 'each_with_object'. Ich finde es sauberer als den Hash in jeder Iteration zurückgeben. –

+0

Reiniger war das Wort, das ich suchte danke :) – tihom

+0

Nice Verwendung von ewo! –

6

können Sie reduce verwenden, aber wenn Sie so wollen, müssen Sie erneut den Hash zurück:

[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) do |hash,number| 
    hash[number] += 1 
    hash 
end 

, dass ohne den Wert von hash[number] zurückgegeben werden würde. Der Wert einer Hash-Zuweisung ist der Wert selbst. Der Block baut auf dem Wert auf, den Sie zuvor zurückgegeben haben.

Das bedeutet, dass nach dem ersten es so etwas versuchen würde: 1[1] += 1, was natürlich nicht funktioniert, weil Fixnums die Methode []= nicht implementieren.

4

Ich mag reduce mit hash.update verwenden. Es gibt das Array und brauche ich nicht den ; hash Teil:

[1,1,2,3,3,3,4,5,5].reduce(Hash.new(0)) { |h, n| h.update(n => h[n].next) } 
+1

Schön! Ich war nicht mit 'update' vertraut. Habe es versteckt. (Leser: Beachten Sie, dass er '+ 1' statt' next' hätte verwenden können, wenn das nicht offensichtlich ist). –

2

Ihre Frage beantwortet wurde, aber hier ist eine andere Art und Weise die Katze Haut: [. Herausgegeben @ David Vorschlag zu verabschieden]

a = [1,1,2,3,3,3,4,5,5] 
Hash[a.group_by(&:to_i).map {|g| [g.first, g.last.size]}] 

Dies funktioniert auch:

a.group_by{|e| e}.inject({}) {|h, (k,v)| h[k] = v.size; h} 

und kann durch die Annahme verbessert werden @ Spickermann die Nutzung von update (oder es ist Synonym, merge!), dieser irritierenden ; h am Ende loszuwerden:

a.group_by{|e| e}.inject({}) {|h, (k,v)| h.merge!(k => v.size)} 

Früher hatte ich:

Hash[*a.group_by(&:to_i).to_a.map {|g| [g.first, g.last.size]}.flatten] 

ich hier nicht mögen to_i . Ich wollte to_proc anstelle von ...group_by {|x| x|} (wie in einer der obigen Lösungen verwendet) verwenden und suchte nach einer Methode m, die den Empfänger oder seinen Wert zurückgibt. to_i war das Beste, was ich tun konnte (z. B. 2.to_i => 2), aber nur für Ganzzahlen. Kann jemand eine Methode vorschlagen, die den Empfänger für eine breite Palette von Objekten zurückgibt, deren Verwendung mit to_proc ihren Zweck offensichtlich macht?

+0

Overcomplicated;) Sie können Ihren Code vereinfachen, um nur 'Hash [a.group_by (&: to_i) .map {| g | [g.first, g.last.size]}] zumindest. Hash erbt "map" von "Enumerable", so dass es nicht in ein 'Array' konvertiert werden muss. Außerdem vermeiden Sie möglicherweise, das resultierende Array und damit den Splat-Operator zu verflachen. Ganz zu schweigen davon, dass Sie Objekte, die von 'map' übergeben werden, an Blockargumenten teilen können. Ich würde zustimmen Ruby fehlt '.id' Methode, die sich selbst zurückgibt (verdorben mit Haskell;)) –

+0

Vielen Dank, David, sowohl für die Anregung und Erklärung. –

1

so einfach ist:

[1,1,2,3,3,3,4,5,5].group_by {|e| e}.collect {|k,v| [k,v.count]} 
#=> [[1, 2], [2, 1], [3, 3], [4, 1], [5, 2]] 

, wenn das Ergebnis in einem Hash-Objekt erforderlich

Hash[[1,1,2,3,3,3,4,5,5].group_by {|e| e}.collect {|k,v| [k,v.count]}] 
#=> {1=>2, 2=>1, 3=>3, 4=>1, 5=>2}