Enjeksiyon yönteminin basit bir açıklamasına ihtiyaç var


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

Bu koda bakıyorum ama beynim 10 numara nasıl sonuç olabilir kayıt değil. Burada neler olduğunu açıklayan biri olur mu?

ruby  syntax 

3
Wikipedia'ya bakın : Katlama (üst düzey işlev) : Enjeksiyon Ruby kullanımında yan etkileri olmasına rağmen (ne yazık ki) sık sık "sola katlama" dır.
user2864740

Yanıtlar:


208

İlk blok argümanını bir akümülatör olarak düşünebilirsiniz: bloğun her çalışmasının sonucu akümülatörde saklanır ve daha sonra bloğun bir sonraki yürütülmesine geçirilir. Yukarıda gösterilen kod durumunda, akümülatörü varsayılan olarak 0 olarak ayarlarsınız. Bloğun her çalıştırılması, verilen sayıyı geçerli toplama ekler ve ardından sonucu tekrar akümülatöre kaydeder. Bir sonraki blok çağrısı bu yeni değere sahiptir, ekler, tekrar saklar ve tekrar eder.

İşlemin sonunda, enjekte edin, bu durumda dizideki veya 10'daki tüm değerlerin toplamı olan akümülatörü döndürür.

Aşağıda, dize temsilleriyle anahtarlanan bir nesne dizisinden karma oluşturmak için başka bir basit örnek verilmiştir:

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

Bu durumda, akümülatörümüzü varsayılan olarak boş bir karmaya ayarlıyoruz, ardından blok her yürütüldüğünde dolduruyoruz. Karmanın bloğun son satırı olarak geri dönmesi gerektiğine dikkat edin, çünkü bloğun sonucu akümülatörde saklanacaktır.


bununla birlikte, OP tarafından verilen örnekte, ne döndürülüyorsa (karma örneğinizde olduğu gibi). Sonuç + açıklamasıyla biter ve bir dönüş değeri olmalıdır, değil mi?
Projjol

1
@Projjol result + explanationhem akümülatöre dönüşüm hem de dönüş değeridir. Bloktaki son satır, onu örtük bir dönüş haline getirir.
KA01

87

injectile başlamak için bir değer ( 0örneğin örnekte) ve bir blok alır ve bu bloğu listenin her öğesi için bir kez çalıştırır.

  1. İlk yinelemede, başlangıç ​​değeri ve listenin ilk öğesi olarak sağladığınız değeri iletir ve bloğunuzun döndürdüğü değeri kaydeder (bu durumda result + element).
  2. Daha sonra bloğu tekrar çalıştırır, ilk yinelemenin sonucunu ilk argüman olarak ve ikinci öğeyi listeden ikinci argüman olarak ileterek sonucu tekrar kaydeder.
  3. Listenin tüm unsurlarını tüketene kadar bu şekilde devam eder.

Bunu açıklamanın en kolay yolu, örneğin her adımın nasıl çalıştığını göstermek olabilir; bu, bu sonucun nasıl değerlendirilebileceğini gösteren hayali bir dizi adımdır:

[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

Adımları yazdığınız için teşekkür ederiz. Bu çok yardımcı oldu. Her ne kadar aşağıdaki diyagramın enjekte etme yönteminin enjekte etmek için argüman olarak geçirilen şekliyle nasıl uygulandığı anlamına gelip gelmediği konusunda biraz kafam karışmış olsa da.

2
Aşağıdaki şemada nasıl dayanır olabilir uygulanacak; tam olarak bu şekilde uygulanması gerekmez. Bu yüzden bunun hayali bir dizi adım olduğunu söyledim; temel yapıyı gösterir, ama kesin uygulamayı göstermez.
Brian Campbell

27

İnject yönteminin sözdizimi aşağıdaki gibidir:

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

Yukarıdaki örneği çözelim yani

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

bu da 10'u çıktı olarak verir .

Başlamadan önce, her bir değişkente depolanan değerlerin neler olduğunu görelim:

sonuç = 0 Sıfır , 0 olan enjekte (değer) geldi

element = 1 Dizinin ilk elemanıdır.

Tamam mı!!! Şimdi yukarıdaki örneği anlamaya başlayalım

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

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

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

Adım: 4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

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

Burada Kalın-İtalik değerler diziden getirilen öğelerdir ve basitçe Kalın değerler elde edilen değerlerdir.

Umarım #injectyönteminin çalışmasını anlarsınız #ruby.


19

Kod, dizideki dört öğe üzerinde yinelenir ve önceki öğeyi geçerli öğeye ekler:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10

15

Ne söylediler, ancak her zaman bir "başlangıç ​​değeri" sağlamanız gerekmediğini unutmayın:

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

aynıdır

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

Deneyin, bekleyeceğim.

Enjekte etmek için hiçbir argüman geçirilmediğinde, ilk iki öğe ilk yinelemeye geçirilir. Yukarıdaki örnekte, sonuç 1 ve eleman ilk kez 2'dir, böylece bloğa daha az bir çağrı yapılır.


14

Enjekte () içine koyduğunuz sayı bir başlangıç ​​noktasını temsil eder, 0 veya 1000 olabilir. Boruların içinde iki yer tutucunuz vardır | x, y |. x = .inject ('x') içinde herhangi bir sayıya sahip olduğunuzda ve ikincil, nesnenizin her yinelemesini temsil eder.

[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 bloğu uygular

result + element

dizideki her öğeye. Sonraki öğe için ("eleman"), bloktan döndürülen değer "sonuç" dur. Onu "parametre" olarak adlandırdığınız şekilde, "sonuç" o parametrenin değeriyle başlar. Sonuç olarak efekt unsurları arttırıyor.


6

TLDR; önemli bir şekilde injectfarklılık gösterir map: injectbloğun son yürütme değerini mapdöndürürken, yinelediği diziyi döndürür.

Bundan daha fazla her blok yürütme değeri ilk parametre ( resultbu durumda) aracılığıyla bir sonraki yürütmeye geçti ve bu değeri ( (0)parça) başlatabilirsiniz .

Yukarıdaki örneğiniz şu şekilde yazılabilir map:

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

Aynı etki ama injectburada daha özlü.

Genellikle ödevin mapblokta gerçekleştiğini görürsünüz, öte yandan blokta bir değerlendirme gerçekleşir inject.

Hangi yöntemi seçtiğiniz, istediğiniz kapsama bağlıdır result. Ne zaman için değil böyle bir şey olurdu kullanın:

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

Hepiniz gibi olabilirsiniz, "Lookie me, hepsini tek bir satırda birleştirdim", ama aynı zamanda geçici xolarak zaten resultçalışmak zorunda olduğunuz için gerekli olmayan bir sıfırdan değişken olarak bellek ayırdınız .


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

aşağıdakine eşdeğerdir:

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

Düz İngilizce'de, bu dizi ( [1,2,3,4]) üzerinden (yineleme) geçiyorsunuz . 4 dizi (1, 2, 3 ve 4) olduğu için bu diziyi 4 kez yineleyeceksiniz. Enject yönteminde 1 bağımsız değişken (0 sayısı) vardır ve bu bağımsız değişkeni 1. öğeye (0 + 1 eklersiniz. Bu 1'e eşittir). 1, "sonuca" kaydedilir. Daha sonra bu sonucu (1 olan) sonraki öğeye (1 + 2) eklersiniz. Bu 3'tür). Bu artık sonuç olarak kaydedilecektir. Devam et: 3 + 3 6'ya eşit ve son olarak 6 + 4 10'a eşit.


2

Bu kod bir başlangıç ​​değeri geçmeme olasılığına izin vermez, ancak neler olduğunu açıklamaya yardımcı olabilir.

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

Buradan başlayın ve blok alan tüm yöntemleri gözden geçirin. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Sizi şaşırtan blok mu yoksa yöntemde neden bir değeriniz var? İyi soru olsa. Orada operatör yöntemi nedir?

result.+

Ne olarak başlar?

#inject(0)

Bunu yapabiliriz?

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

Bu çalışıyor mu?

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

Gördüğünüz gibi, sadece dizinin tüm öğelerini topladığını ve dokümanlarda gördüğünüz notta bir sayı verdiğini düşünüyorum.

Bunu her zaman yapabilirsin

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

dizi numaralandırılması görmek için yinelenen. Temel fikir budur.

Sadece enjekte veya azaltmak size gönderilen bir not veya akümülatör verir.

Sonuç almaya çalışabiliriz

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

ama hiçbir şey geri gelmediği için bu eskisi gibi değil

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

eleman denetçi bloğunda.


1

Bu basit ve anlaşılması oldukça kolay bir açıklamadır:

Başlangıçta biraz kafa karıştırıcı olduğu için "başlangıç ​​değerini" unutun.

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

Yukarıdakileri şu şekilde anlayabilirsiniz: 1,2,3,4 arasında bir "ekleme makinesi" enjekte ediyorum. Yani, 1 ♫ 2 ♫ 3 ♫ 4 ve ♫ bir ekleme makinesidir, bu yüzden 1 + 2 + 3 + 4 ile aynıdır ve 10'dur.

Aslında +aralarına bir enjekte edebilirsiniz:

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

ve +1, 2, 3 + 4 yaparak 1,2,3,4 arasında bir enjeksiyon yapın ve 10'dur. :+Ruby'nin +bir sembol şeklinde belirtme şeklidir.

Bu anlaşılması oldukça kolay ve sezgisel. Ve nasıl adım adım çalıştığını analiz etmek istiyorsanız, şuna benzer: 1 ve 2 almak ve şimdi onları ekleyin ve bir sonucunuz olduğunda önce saklayın (3'tür) ve şimdi, saklanır değer 3 ve dizi öğesi 3, 6 olan a + b işleminden geçer ve şimdi bu değeri depolar ve şimdi 6 ve 4, a + b işleminden geçer ve 10'dur.

((1 + 2) + 3) + 4

"Başlangıç ​​değeri" 0sadece başlangıç ​​için bir "temel" dir. Çoğu durumda, buna ihtiyacınız yoktur. 1 * 2 * 3 * 4'e ihtiyacınız varsa hayal edin ve

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

ve yapılır. Her 1şeyi ile çarpmak için "başlangıç ​​değerine" ihtiyacınız yoktur 1.


0

Başka bir .inject () yöntemi biçimi var. Çok yararlıdır [4,5] .inject (&: +) Bu alanın tüm öğelerini toplar


0

Sadece reduceya foldda diğer dillere aşina iseniz.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.