Potrzebujesz prostego wyjaśnienia metody wstrzykiwania

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

Patrzę na ten kod, ale mój mózg nie rejestruje, jak liczba 10 może stać się wynikiem. Czy ktoś mógłby wyjaśnić, co się tutaj dzieje?


źródło
3
Zobacz Wikipedię: Fold (funkcja wyższego rzędu) : inject to "zagięcie w lewo", chociaż (niestety) często ma efekty uboczne przy używaniu Rubiego.
user2864740

Odpowiedzi:

208

Możesz myśleć o pierwszym argumencie bloku jako o akumulatorze: wynik każdego uruchomienia bloku jest przechowywany w akumulatorze, a następnie przekazywany do następnego wykonania bloku. W przypadku kodu pokazanego powyżej, domyślnie ustawiasz akumulator wynik na 0. Każde uruchomienie bloku dodaje podaną liczbę do bieżącej sumy, a następnie zapisuje wynik z powrotem w akumulatorze. Następne wywołanie bloku ma tę nową wartość, dodaje ją, zapisuje ponownie i powtarza.

Pod koniec procesu inject zwraca akumulator, który w tym przypadku jest sumą wszystkich wartości w tablicy, czyli 10.

Oto kolejny prosty przykład tworzenia skrótu z tablicy obiektów, kluczowanych przez ich reprezentację w postaci ciągu:

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

W tym przypadku domyślnie ustawiamy nasz akumulator na pusty hash, a następnie wypełniamy go za każdym razem, gdy blok jest wykonywany. Zauważ, że musimy zwrócić hash jako ostatnią linię bloku, ponieważ wynik bloku zostanie ponownie zapisany w akumulatorze.

Drew Olson
źródło
świetne wyjaśnienie, jednak w przykładzie podanym przez OP, co jest zwracane (jak hash jest w twoim przykładzie). Kończy się wynikiem + wyjaśnieniem i powinien mieć wartość zwracaną, tak?
Projjol
1
@Projjol result + explanationjest zarówno transformacją do akumulatora, jak i wartością zwracaną. Jest to ostatnia linia w bloku, która jest niejawnym zwrotem.
KA01
87

injectprzyjmuje wartość, od której zaczyna się (w naszym 0przykładzie) oraz blok i uruchamia ten blok raz dla każdego elementu listy.

  1. W pierwszej iteracji przekazuje wartość podaną jako wartość początkową i pierwszy element listy oraz zapisuje wartość, którą zwrócił blok (w tym przypadku result + element).
  2. Następnie ponownie uruchamia blok, przekazując wynik z pierwszej iteracji jako pierwszy argument, a drugi element z listy jako drugi argument, ponownie zapisując wynik.
  3. Działa w ten sposób, dopóki nie zużyje wszystkich elementów listy.

Najłatwiejszym sposobem wyjaśnienia tego może być pokazanie, jak działa każdy krok, na przykład; jest to wyimaginowany zestaw kroków pokazujących, jak można ocenić ten wynik:

[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
Brian Campbell
źródło
Dziękuję za napisanie instrukcji. To bardzo pomogło. Chociaż byłem trochę zdezorientowany, czy masz na myśli, że poniższy diagram przedstawia sposób implementacji metody wstrzykiwania pod względem tego, co jest przekazywane jako argumenty do wstrzyknięcia.
2
Poniższy diagram przedstawia sposób, w jaki można go wdrożyć; niekoniecznie jest implementowane dokładnie w ten sposób. Dlatego powiedziałem, że to wyimaginowany zestaw kroków; pokazuje podstawową strukturę, ale nie pokazuje dokładnej implementacji.
Brian Campbell
27

Składnia metody wstrzykiwania jest następująca:

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

Rozwiążmy powyższy przykład tj

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

co daje 10 jako wyjście.

Tak więc, zanim zaczniemy, zobaczmy, jakie wartości są przechowywane w każdej zmiennej:

wynik = 0 Zero pochodzi z wstrzyknięcia (wartość), która wynosi 0

element = 1 Jest to pierwszy element tablicy.

Okey !!! Zacznijmy więc rozumieć powyższy przykład

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

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

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

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

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

Tutaj wartości pogrubione-kursywa to elementy pobrane z tablicy, a wartości po prostu pogrubione są wartościami wynikowymi.

Mam nadzieję, że rozumiesz działanie #injectmetody #ruby.

Vishal Nagda
źródło
19

Kod iteruje po czterech elementach w tablicy i dodaje poprzedni wynik do bieżącego elementu:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10
John Topley
źródło
15

Co powiedzieli, ale pamiętaj również, że nie zawsze musisz podawać „wartość początkową”:

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

jest taki sam jak

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

Spróbuj, poczekam.

Gdy żaden argument nie jest przekazywany do wstrzyknięcia, pierwsze dwa elementy są przekazywane do pierwszej iteracji. W powyższym przykładzie wynik to 1, a element to 2 za pierwszym razem, więc do bloku zostanie wysłane jedno wywołanie mniej.

Mike Woodhouse
źródło
14

Liczba, którą umieścisz w swojej () in inject, reprezentuje miejsce początkowe, może wynosić 0 lub 1000. Wewnątrz rur masz dwa miejsca na pozycje | x, y |. x = jaka kiedykolwiek liczba miałaś wewnątrz .inject ('x'), a druga reprezentuje każdą iterację twojego obiektu.

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

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

Stuart G.
źródło
6

Wstrzyknąć stosuje blok

result + element

do każdego elementu w tablicy. Dla następnego elementu („element”) wartością zwracaną z bloku jest „wynik”. Sposób, w jaki to nazwałeś (z parametrem), „wynik” zaczyna się od wartości tego parametru. Efektem jest więc sumowanie elementów.

Jonathan Adelson
źródło
6

tldr; injectróżni się od mapw jeden ważny sposób: injectzwraca wartość ostatniego wykonania bloku, a mapzwraca tablicę, po której był iterowany.

Co więcej, wartość każdego wykonania bloku przekazana do następnego wykonania za pośrednictwem pierwszego parametru ( resultw tym przypadku) i można zainicjować tę wartość ((0) część).

Twój powyższy przykład można zapisać w mapnastępujący sposób:

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

Ten sam efekt, ale inject jest bardziej zwięzły.

Często zdarza się map, że przypisanie odbywa się w bloku, podczas gdy ocena odbywa się winject bloku.

Wybór metody zależy od wybranego zakresu result. Kiedy nie używać tego byłoby coś takiego:

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

Możesz być taki jak wszyscy: „Słuchaj, właśnie połączyłem to wszystko w jedną linię”, ale tymczasowo przydzieliłeś pamięć xjako zmienną podstawową, która nie była konieczna, ponieważ już musiałeś resultz nią pracować.

IAmNaN
źródło
4
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

jest równoważne z następującym:

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
Fred Willmore
źródło
3

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

Mówiąc prostym językiem, przechodzisz przez tę tablicę ( [1,2,3,4]). Będziesz iterować tę tablicę 4 razy, ponieważ są 4 elementy (1, 2, 3 i 4). Metoda iniekcji ma 1 argument (liczbę 0) i dodasz ten argument do pierwszego elementu (0 + 1. To równa się 1). 1 jest zapisywany w „wyniku”. Następnie dodajesz ten wynik (czyli 1) do następnego elementu (1 + 2. To jest 3). Ten zostanie zapisany jako wynik. Kontynuuj: 3 + 3 równa się 6. I na koniec 6 + 4 równa się 10.

Maddie
źródło
2

Ten kod nie dopuszcza możliwości nieprzekazywania wartości początkowej, ale może pomóc wyjaśnić, co się dzieje.

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
Andrew Grimm
źródło
1

Zacznij tutaj, a następnie przejrzyj wszystkie metody, które pobierają bloki. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Czy to blok, który cię dezorientuje, czy też dlaczego masz wartość w metodzie? Dobre pytanie. Jaka jest tam metoda operatora?

result.+

Od czego to się zaczyna?

#inject(0)

Możemy to zrobić?

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

czy to działa?

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

Widzisz, opieram się na idei, że po prostu sumuje wszystkie elementy tablicy i zwraca liczbę w notatce, którą widzisz w dokumentach.

Zawsze możesz to zrobić

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

aby zobaczyć wyliczalną tablicę, przez którą przechodzi iteracja. To podstawowa idea.

Po prostu wstrzykuj lub zmniejszaj, aby otrzymać notatkę lub akumulator, który zostanie wysłany.

Moglibyśmy spróbować uzyskać wynik

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

ale nic nie wraca, więc działa tak samo jak wcześniej

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

w bloku inspektora elementów.

Douglas G. Allen
źródło
1

To jest proste i dość łatwe do zrozumienia wyjaśnienie:

Zapomnij o „wartości początkowej”, ponieważ na początku jest ona nieco myląca.

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

Możesz to rozumieć jako: wstrzykuję „maszynę dodającą” pomiędzy 1,2,3,4. Oznacza to, że jest to 1 ♫ 2 ♫ 3 ♫ 4, a ♫ jest maszyną sumującą, więc jest to to samo, co 1 + 2 + 3 + 4, a to jest 10.

Możesz faktycznie wstrzyknąć +między nimi:

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

i to jest tak, jakby wstawić a +pomiędzy 1, 2, 3, 4, co daje 1 + 2 + 3 + 4 i jest to 10. Jest :+to sposób określania przez Rubiego +w formie symbolu.

Jest to dość łatwe do zrozumienia i intuicyjne. A jeśli chcesz przeanalizować krok po kroku, jak to działa, to jest tak: biorąc 1 i 2, a teraz dodaj je, a kiedy masz wynik, najpierw zapisz go (czyli 3), a teraz następny jest zapisany wartość 3 i element tablicy 3 przechodzą przez proces a + b, czyli 6 i teraz przechowują tę wartość, a teraz 6 i 4 przechodzą przez proces a + b i wynosi 10. Zasadniczo robisz

((1 + 2) + 3) + 4

i wynosi 10. „Wartość początkowa” 0jest po prostu „podstawą”. W wielu przypadkach nie jest to potrzebne. Wyobraź sobie, że potrzebujesz 1 * 2 * 3 * 4 i tak jest

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

i gotowe. Nie potrzebujesz „wartości początkowej”, 1aby pomnożyć całość 1.

brak biegunowości
źródło
0

Istnieje inna forma metody .inject (), która jest bardzo pomocna [4,5] .inject (&: +) Która zsumuje cały element obszaru

Hasanin Alsabounchi
źródło
0

Po prostu reducelub fold, jeśli znasz inne języki.

Nacięcie
źródło
-1

Jest taki sam jak ten:

[1,2,3,4].inject(:+)
=> 10
Powiedział Maadan
źródło
Chociaż jest to faktyczne, nie odpowiada na pytanie.
Mark Thomas,