2013-10-05 1 views
5

У меня есть массив, как такИспользование Hash.new в качестве начального значения при уменьшении массива

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

и я хочу, чтобы подсчитать количество вхождений каждого числа, которые я пытаюсь сделать так, как

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

проблема в том, я получаю следующее сообщение об ошибке при попытке запустить его

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 

Могу ли я установить начальное значение, как это, или я получаю это неправильно?

ответ

7

Вы можете использовать each_with_object для чистого синтаксиса

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

Обратите внимание, что порядок аргументов является обратным, т.е. |number, hash|

+0

Я всегда забывают, что есть 'each_with_object'. Я нахожу его более чистым, чем возвращение хэша на каждой итерации. –

+0

уборщица было словом, которое я искал благодаря :) :) – tihom

+0

Хорошее использование ewo! –

6

Вы можете использовать reduce, но если вы хотите так, вы должны вернуть хэш снова:

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

Без этого значение hash[number] будет возвращено. Значение хэш-присваивания - это значение. Блок основывается на значении, которое вы вернули ранее.

Это означает, что после первого попробует что-то вроде этого: 1[1] += 1, что, конечно же, не работает, потому что Fixnums не реализуют метод []=.

4

Мне нравится использовать reduce с hash.update. Она возвращает массив, и я не нужна ; hash части:

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

Ницца! Я не был знаком с 'update'. Спрятал его. (Читатели: заметьте, он мог бы использовать '+ 1' вместо' next', если это не очевидно). –

2

Ваш вопрос был дан ответ, но вот еще один способ кожи кошки: [. Ред принять @ предложение Давида]

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

Это также работает:

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

и может быть улучшена путем применения @ Использование Spickermann о update (или его синоним, merge!), чтобы избавиться от этого раздражающего ; h в конце:

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

Раньше у меня было:

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

Мне не нравится to_i здесь , Я хотел использовать to_proc вместо ...group_by {|x| x|} (как использовано в одном из решений выше) и искал метод m, который возвращает приемник или его значение. to_i было лучшим, что я мог сделать (например, 2.to_i => 2), но только для целых чисел. Может ли кто-нибудь предложить метод, который возвращает приемник для широкого круга объектов, использование которых с to_proc делает его цель очевидной?

+0

Overcomplicated;) Вы можете упростить свой код до «Hash [a.group_by (&: to_i) .map {| g | [g.first, g.last.size]}] 'по крайней мере. Hash наследует 'map' из' Enumerable', поэтому не нужно преобразовывать его в 'Array'. Также вы можете избежать сглаживания результирующего массива и, следовательно, оператора splat. Не говоря уже о том, что вы можете разделить объект, переданный 'map' в блочных аргументах. Я согласен с тем, что Ruby не имеет метода '.id', возвращающегося (испорченный Haskell;)) –

+0

Большое спасибо, Дэвид, за предложение и объяснение. –

1

Это просто:

[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]] 

, если требуемый результат в хэш-объекта

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} 
Смежные вопросы