Требуется простое объяснение метода инъекции

142
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

Я смотрю на этот код, но мой мозг не понимает, как число 10 может стать результатом. Не мог бы кто-нибудь объяснить, что здесь происходит?


источник
3
См. Википедию: Fold (функция высшего порядка) : inject - это «складывание влево», хотя (к сожалению) часто с побочными эффектами при использовании Ruby.
user2864740 09

Ответы:

209

Вы можете думать о первом аргументе блока как об аккумуляторе: результат каждого запуска блока сохраняется в аккумуляторе, а затем передается следующему выполнению блока. В случае кода, показанного выше, вы устанавливаете аккумулятор result по умолчанию на 0. Каждый запуск блока добавляет данное число к текущему итоговому значению, а затем сохраняет результат обратно в аккумулятор. Следующий вызов блока имеет это новое значение, добавляет к нему, сохраняет его снова и повторяется.

В конце процесса inject возвращает аккумулятор, который в данном случае представляет собой сумму всех значений в массиве, или 10.

Вот еще один простой пример создания хэша из массива объектов, привязанных к их строковому представлению:

[1,"a",Object.new,:hi].inject({}) do |hash, item|
  hash[item.to_s] = item
  hash
end

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

Дрю Олсон
источник
отличное объяснение, однако, в примере, приведенном OP, что возвращается (например, хеш в вашем примере). Он заканчивается результатом + объяснение и должен иметь возвращаемое значение, да?
Projjol
1
@Projjol - result + explanationэто и преобразование в аккумулятор, и возвращаемое значение. Это последняя строка в блоке, которая делает неявный возврат.
KA01
88

injectпринимает значение для начала ( 0в вашем примере) и блок, и он запускает этот блок один раз для каждого элемента списка.

  1. На первой итерации он передает значение, которое вы указали в качестве начального значения и первый элемент списка, и сохраняет значение, возвращенное вашим блоком (в данном случае result + element).
  2. Затем он снова запускает блок, передавая результат первой итерации в качестве первого аргумента, а второй элемент из списка в качестве второго аргумента, снова сохраняя результат.
  3. Так продолжается до тех пор, пока не будут израсходованы все элементы списка.

Самый простой способ объяснить это - показать, как работает каждый шаг на вашем примере; это воображаемый набор шагов, показывающих, как можно оценить этот результат:

[1, 2, 3, 4].inject(0) { |result, element| result + element }
[2, 3, 4].inject(0 + 1) { |result, element| result + element }
[3, 4].inject((0 + 1) + 2) { |result, element| result + element }
[4].inject(((0 + 1) + 2) + 3) { |result, element| result + element }
[].inject((((0 + 1) + 2) + 3) + 4) { |result, element| result + element }
(((0 + 1) + 2) + 3) + 4
10
Брайан Кэмпбелл
источник
Спасибо, что выписали шаги. Это очень помогло. Хотя меня немного смутило, имеете ли вы в виду, что на диаграмме ниже показано, как ниже реализован метод inject с точки зрения того, что передается в качестве аргументов для инъекции.
2
Схема ниже основана на том, как это можно реализовать; это не обязательно реализуется именно так. Вот почему я сказал, что это воображаемый набор шагов; он демонстрирует базовую структуру, но не точную реализацию.
Брайан Кэмпбелл,
28

Синтаксис метода инъекции следующий:

inject (value_initial) { |result_memo, object| block }

Давайте решим приведенный выше пример, т.е.

[1, 2, 3, 4].inject(0) { |result, element| result + element }

что дает 10 на выходе.

Итак, перед тем как начать, давайте посмотрим, какие значения хранятся в каждой переменной:

result = 0 Ноль получен из inject (value), который равен 0

element = 1 Это первый элемент массива.

Окей !!! Итак, приступим к пониманию приведенного выше примера.

Шаг 1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }

Шаг 2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }

Шаг 3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

Шаг: 4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

Шаг: 5 [1, 2, 3, 4].inject(0) { |10, Now no elements left in the array, so it'll return 10 from this step| }

Здесь значения, выделенные жирным курсивом, представляют собой элементы, полученные из массива, а значения, выделенные жирным шрифтом, являются результирующими значениями.

Я надеюсь, что вы понимаете принцип работы #injectметода #ruby.

Vishal Nagda
источник
19

Код выполняет итерацию по четырем элементам в массиве и добавляет предыдущий результат к текущему элементу:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10
Джон Топли
источник
15

Что они сказали, но обратите внимание, что вам не всегда нужно указывать «начальное значение»:

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

такой же как

[1, 2, 3, 4].inject { |result, element| result + element } # => 10

Попробуй, подожду.

Если для inject не передается аргумент, первые два элемента передаются в первую итерацию. В приведенном выше примере результат равен 1, а element - 2 в первый раз, поэтому на один вызов блока меньше.

Майк Вудхаус
источник
14

Число, которое вы помещаете внутри вашего () of inject, представляет собой начальную точку, это может быть 0 или 1000. Внутри каналов у вас есть два заполнителя | x, y |. x = любое число, которое у вас было внутри .inject ('x'), а второй результат представляет каждую итерацию вашего объекта.

[1, 2, 3, 4].inject(5) { |result, element| result + element } # => 15

1 + 5 = 6 2 + 6 = 8 3 + 8 = 11 11 + 4 = 15

Стюарт Дж.
источник
6

Inject применяет блок

result + element

к каждому элементу в массиве. Для следующего элемента («элемент») значение, возвращаемое из блока, - «результат». То, как вы его назвали (с параметром), «результат» начинается со значения этого параметра. Итак, эффект складывается из элементов.

Джонатан Адельсон
источник
6

tldr; injectотличается от mapодного важным способом: injectвозвращает значение последнего выполнения блока, тогда как mapвозвращает массив, по которому он проходил итерацию.

Более того, значение каждого выполнения блока передается в следующее выполнение через первый параметр ( resultв данном случае), и вы можете инициализировать это значение ( (0)часть).

Ваш пример выше может быть написан mapследующим образом:

result = 0 # initialize result
[1, 2, 3, 4].map { |element| result += element }
# result => 10

Тот же эффект, но injectздесь более краткий.

Вы часто обнаружите, что присваивание происходит в mapблоке, тогда как оценка происходит в injectблоке.

Какой метод вы выберете, зависит от желаемого объема result. Когда его не использовать, это будет примерно так:

result = [1, 2, 3, 4].inject(0) { |x, element| x + element }

Вы могли бы сказать: «Послушайте, я просто объединил все это в одну строку», но вы также временно выделили память для временной xпеременной, в которой не было необходимости, поскольку вам уже приходилось resultработать.

IAmNaN
источник
4
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

эквивалентно следующему:

def my_function(r, e)
  r+e
end

a = [1, 2, 3, 4]
result = 0

a.each do |value|
  result = my_function(result, value)
end
Фред Уиллмор
источник
3

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

Проще говоря, вы просматриваете (повторяете) этот массив ([1,2,3,4] ). Вы будете перебирать этот массив 4 раза, потому что в нем 4 элемента (1, 2, 3 и 4). Метод inject имеет 1 аргумент (число 0), и вы добавите этот аргумент к 1-му элементу (0 + 1. Это равно 1). 1 сохраняется в «результат». Затем вы добавляете этот результат (который равен 1) к следующему элементу (1 + 2. Это 3). Это будет сохранено в качестве результата. Продолжайте: 3 + 3 равно 6. И, наконец, 6 + 4 равно 10.

Мэдди
источник
2

Этот код не допускает возможности не передать начальное значение, но может помочь объяснить, что происходит.

def incomplete_inject(enumerable, result)
  enumerable.each do |item|
    result = yield(result, item)
  end
  result
end

incomplete_inject([1,2,3,4], 0) {|result, item| result + item} # => 10
Эндрю Гримм
источник
1

Начните здесь, а затем просмотрите все методы, которые принимают блоки. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Вас смущает блокировка или почему у вас есть ценность в методе? Но хороший вопрос. Какой там метод оператора?

result.+

С чего это начинается?

#inject(0)

Мы можем это сделать?

[1, 2, 3, 4].inject(0) { |result, element| result.+ element }

Это работает?

[1, 2, 3, 4].inject() { |result = 0, element| result.+ element }

Видите ли, я основываюсь на идее, что он просто суммирует все элементы массива и дает число в памятке, которую вы видите в документации.

Вы всегда можете это сделать

 [1, 2, 3, 4].each { |element| p element }

чтобы увидеть, как перечислимый массив проходит итерацию. Это основная идея.

Просто вставьте или уменьшите, чтобы получить памятку или аккумулятор.

Мы могли бы попытаться получить результат

[1, 2, 3, 4].each { |result = 0, element| result + element }

но ничего не возвращается, поэтому он действует так же, как и раньше

[1, 2, 3, 4].each { |result = 0, element| p result + element }

в блоке инспектора элементов.

Дуглас Г. Аллен
источник
1

Это простое и довольно легкое для понимания объяснение:

Забудьте о «начальном значении», поскольку оно вначале несколько сбивает с толку.

> [1,2,3,4].inject{|a,b| a+b}
=> 10

Вы можете понять сказанное выше так: я ввожу «суммирующую машину» между 1,2,3,4. Это означает, что это 1 ♫ 2 ♫ 3 ♫ 4, а ♫ - это суммирующая машина, поэтому это то же самое, что 1 + 2 + 3 + 4, и это 10.

Фактически вы можете вставить +между ними:

> [1,2,3,4].inject(:+)
=> 10

и это похоже на вставку a +между 1,2,3,4, что делает его 1 + 2 + 3 + 4, и это 10. Это :+способ определения Ruby+ в в форме символа.

Это довольно легко понять и интуитивно понятно. И если вы хотите проанализировать, как это работает, шаг за шагом, это похоже на: взять 1 и 2, а теперь добавить их, и когда у вас есть результат, сначала сохраните его (это 3), а теперь, затем, сохраненный значение 3 и элемент массива 3 проходят через процесс a + b, который равен 6, и теперь сохраняют это значение, и теперь 6 и 4 проходят через процесс a + b, и это 10. Вы, по сути, делаете

((1 + 2) + 3) + 4

и равно 10. «Начальное значение» 0- это просто «база» для начала. Во многих случаях вам это не нужно. Представьте, если вам нужно 1 * 2 * 3 * 4 и это

[1,2,3,4].inject(:*)
=> 24

и это сделано. Вам не нужно «начальное значение», 1чтобы все это умножать 1.

неполярность
источник
0

Существует еще одна форма метода .inject (), которая очень полезна [4,5] .inject (&: +), которая суммирует все элементы области

Хасанин Альсабунчи
источник
0

Это просто reduceили fold, если вы знакомы с другими языками.

Ник
источник
-1

То же самое:

[1,2,3,4].inject(:+)
=> 10
Саид Маадан
источник
Хотя это факт, это не дает ответа на вопрос.
Марк Томас