2013-09-28 2 views
23

Новый рубин, я пытаюсь выяснить, как предполагается использовать each_with_object.Как должен работать каждый_with_object?

Я пытаюсь использовать его для очень простой суммы, так что я писал:

> (1..3).each_with_object(0) {|i,sum| sum+=i} 
=> 0 

Эй, я бы предположить, что результат будет 6! Где моя ошибка?

+1

Кстати. Может кто-то сказать мне, почему Ruby не бросает какую-то ошибку при выполнении 'sum + = i', т.е. пытаясь изменить неизменяемый объект? Весь смысл здесь в том, что бедный пользователь остался один, не имея понятия, что его команда просто бесшумно ... –

ответ

42

each_with_object не работает с неизменяемыми объектами, такими как integer.

(1..3).each_with_object(0) {|i,sum| sum += i} #=> 0 

Это потому, что each_with_object перебирает коллекции, передавая каждый элемент и данный объект к блоку. Он не обновляет значение объекта после каждой итерации и возвращает исходный заданный объект.

Он будет работать с хэшем, так как изменение значения хэш-ключа изменяет его для оригинального объекта.

(1..3).each_with_object({:sum => 0}) {|i,hsh| hsh[:sum] += i} 
#=> {:sum => 6} 

String Объекты интересны. Они изменчивы, так что вы могли бы ожидать следующее вернуться «ABC»

("a".."c").each_with_object("") {|i,str| str += i} # => "" 

, но это не так. Это связано с тем, что str += "a" возвращает новый объект, и исходный объект остается прежним. Однако, если мы делаем

("a".."c").each_with_object("") {|i,str| str << i} # => "abc" 

это работает, потому что str << "a" изменяет исходный объект.

Для получения дополнительной информации см рубина документов для each_with_object

Для ваших целей, используйте inject

(1..3).inject(0) {|sum,i| sum += i} #=> 6 
# or 
(1..3).inject(:+) #=> 6 
+1

Ну, спасибо. В самом деле, я читал документ здесь: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-each_with_object и нет упоминания о таких деталях. Благодарю. –

+0

Проблема состоит в том, что 'sum + = i' является' sum = sum + i' и 'sum = ...' не изменяет переданную сумму, она просто назначит новое значение локальной переменной , 'inject' работает, потому что' inject' возвращает возвращаемое значение блока обратно в следующую итерацию, вам, вероятно, лучше писать '(1..3) .inject (0) {| sum, i | sum + i} 'или даже' (1..3) .inject (: +) 'в этом случае' sum + = i' внутри блока 'inject' вводит в заблуждение и запутывает. –

+0

Я бы предпочел 'sum + i', поскольку это более понятно, но' each_with_object' не будет работать с этим. Мне хотелось показать, что код OP работал с 'inject', но не с' each_with_object' – tihom

6

Один простые, но общий пример each_with_object есть, когда вам нужно построить хэш в зависимости от элементов массив. Очень часто вы видите что-то вроде:

hash = {} 
[1, 2, 3, 4].each do |number| 
    hash[number] = number**2 
end 
hash 

С each_with_object гораздо проще:

[1,2,3,4].each_with_object({}) { |number, hash| hash[number] = number**2 } 

Я советую прочитать документацию для inject, tap и each_with_index. Они очень полезны, когда вам нужен короткий и читаемый код.

+0

Серьезно, вы обнаружите, что «намного проще»? :) Может быть, «короче», но вот и все ... –

+0

Да, три с половиной года назад я чувствовал себя так :-) – spickermann

10

В документации Enumerable#each_with_object очень ясно:

Итерации данный блок для каждого элемента с произвольным объектом данного, а возвращает первоначально заданный объект.

В вашем случае, (1..3).each_with_object(0) {|i,sum| sum+=i}, вы передаете 0, который является неизменным объектом. Таким образом, здесь основным объектом является метод each_with_object - 0, поэтому метод возвращает 0.Он работает на него рекламируется. Ниже хау each_with_object работ,

(1..3).each_with_object(0) do |e,mem| 
    p "#{mem} and #{e} before change" 
    mem = mem + e 
    p mem 
end 

# >> "0 and 1 before change" 
# >> 1 
# >> "0 and 2 before change" 
# >> 2 
# >> "0 and 3 before change" 
# >> 3 

Это означает, что в каждом проходе, mem устанавливается на начальной Переданный объект. Возможно, вы думаете о первом проходе mem - 0, затем в следующий проход mem - результат mem+=e, т. Е. mem будет 1 .НЕТ НЕТ, в каждом проходе mem - это ваш первоначальный объект, который является 0.

+0

Хорошо, я только что узнал, что целое число неизменно, о чем я не знал. Благодарю. –

Смежные вопросы