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?

ruby  syntax 

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.


ś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

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.


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

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.


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


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.


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ć.


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

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.


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

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.


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.


0

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



Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.