Потрібно просте пояснення методу ін'єкцій


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

Я дивлюся на цей код, але мій мозок не реєструє, як число 10 може стати результатом. Хтось міг би пояснити, що тут відбувається?

ruby  syntax 

3
Дивіться Вікіпедію: Складка (функція вищого порядку) : ін'єкція - "складе ліворуч", хоча (на жаль) часто має побічні ефекти при використанні Ruby.
користувач2864740

Відповіді:


208

Ви можете вважати перший аргумент блоку як акумулятор: результат кожного запуску блоку зберігається в акумуляторі і потім переходить до наступного виконання блоку. У випадку наведеного вище коду ви дефолтуєте акумулятор, результат - 0. Кожен запуск блоку додає задане число до поточного загального, а потім зберігає результат назад у акумулятор. Наступний виклик блоку має це нове значення, додає до нього, зберігає його знову і повторює.

В кінці процесу впорскування повертає акумулятор, який у цьому випадку є сумою всіх значень масиву, або 10.

Ось ще один простий приклад створення хешу з масиву об’єктів, що вводиться за допомогою їх рядкового подання:

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

У цьому випадку ми дефолтуємо наш акумулятор до порожнього хешу, а потім заповнюємо його щоразу, коли блок виконує. Зауважте, що ми повинні повернути хеш як останній рядок блоку, оскільки результат блоку буде зберігатися назад у акумуляторі.


чудове пояснення, однак, у прикладі, поданому ОП, що повертається (як хеш є у вашому прикладі). Він закінчується результатом + поясненням і повинен мати повернене значення, так?
Projjol

1
@Projjol - result + explanationце як перетворення в акумулятор, так і повернене значення. Це останній рядок у блоці, що робить його неявним поверненням.
KA01

87

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

Дякуємо за написання кроків. Це дуже допомогло. Хоча я трохи заплутався у тому, чи маєте ви на увазі, що на наведеній нижче схемі реалізовано метод введення внизу з точки зору того, що передано як аргументи для введення.

2
Наведена нижче схема базується на тому, як це можна було б реалізувати; це не обов'язково реалізовано саме таким чином. Тому я сказав, що це уявний набір кроків; вона демонструє основну структуру, але не точну реалізацію.
Брайан Кемпбелл

27

Синтаксис методу ін'єкції такий:

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

Розв’яжемо наведений вище приклад, тобто

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

що дає 10 як вихід.

Отже, перш ніж починати, давайте розберемося, які значення зберігаються в кожній змінній:

result = 0 Нуль прийшов від введення (значення), яке дорівнює 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| }

Тут значення Bold-Italic - це елементи, отримані з масиву, а значення Bold - Bold .

Я сподіваюся, що ви розумієте, як працює #injectметод методу #ruby.


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

Спробуйте, я зачекаю.

Коли жоден аргумент не передається для введення, перші два елементи передаються в першу ітерацію. У наведеному вище прикладі результат дорівнює 1, а елемент - 2 вперше, тож один виклик менше блоку робиться.


14

Число, яке ви введете всередину () введення, являє собою початкове місце, воно може бути 0 або 1000. Усередині труб у вас є два тримачі | x, y |. x = яке коли-небудь число, яке ви мали у .inject ('x'), а secound представляє кожну ітерацію вашого об'єкта.

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

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


6

Ін'єкт застосовує блок

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працювати.


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). Метод введення має 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

і це як, введіть +між 1,2,3,4, зробивши його 1 + 2 + 3 + 4, і це 10. Це :+спосіб вказати Рубі +у вигляді символу.

Це досить легко зрозуміти та зрозуміти. І якщо ви хочете проаналізувати, як це працює поетапно, це так: виберіть 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

Це просто reduceабо fold, якщо ви знайомі з іншими мовами.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.