2014-11-09 8 views
7

Pisałam jakiś kod jak następuje:Jak korzystać dalej z inject w Ruby

[1,2,3,4,5].inject([]) do |res, a| 
    res << a*a and next if a == 2 
    res << a 
end 

To daje następujący błąd:

NoMethodError: undefined method `<<' for nil:NilClass 

Jak przez następne to sprawia res zmienna jak nil, Jak obejść ten problem?

Próbowałem na różne sposoby, ale nie mogłem przejść do pracy z ruby, wiem, że podany przeze mnie fragment można zrobić bez następnego (a == 4 ? res << a*a : res << a), ale w moim rzeczywistym przypadku mam trochę skomplikowanej logiki i mogę " Zrobić to po prostu.

Odpowiedz

9

Wymień

res << a*a and next if a == 2 

z

next res << a*a if a == 2 

Teraz to będzie działać.

Przykład: -

#!/usr/bin/env ruby 

ar = [1,2,3,4,5].inject([]) do |res, a| 
    next res << a*a if a == 2 
    res << a 
end 

p ar 
# >> [1, 4, 3, 4, 5] 

Read the documentation of next

next can take a value, which will be the value returned for the current iteration of the block....

+0

Dzięki @Arup, to nie wiedziałem, jak znaleźć takie szczegóły? – Saurabh

+0

@saurabh Zaktualizowano ... –

+0

ok, sprawdzałem dokumentację wstrzyknięcia, moje złe :) – Saurabh

2

Uh. Nie używaj w tym celu warunkowego warunku. Zamiast tego jest łatwo zrobić za pomocą standardowego if/else:

[1,2,3,4,5].inject([]) do |res, a| 
    if a == 2 
    res << a*a 
    else 
    res << a 
    end 
end 
# => [1, 4, 3, 4, 5] 

każdym razem czujesz jakbyś kodowania sobie w kącie, nie patrz na drodze, zamiast z powrotem w górę i patrzeć na to, co próbujesz osiągnąć, czy istnieje bardziej prosty sposób.

Prawdopodobnie jednak poprawię nieco kod, aby zapewnić czytelność. konserwacja długoterminowe opiera się na szybko zrozumieć, co się dzieje, i kod, który jest zwinięty lub nieoczywisty może odcisnąć swoje piętno później:

[1,2,3,4,5].inject([]) do |res, a| 
    if a == 2 
    res << a*a 
    else 
    res << a 
    end 

    res # return it for clarity in what the block is returning 
end 
# => [1, 4, 3, 4, 5] 

inject jest podobna do each_with_object, tylko opiera się na akumulatorze jest zwracana na koniec bloku, dlatego chciałbym dodać na końcu bloku res ze względu na blok if. Przełączanie na each_with_object usuwa że poleganie na wartości zwracanej bloku, pozwalając poniższy kod, żeby być bardziej logicznie jasne:

[1,2,3,4,5].each_with_object([]) do |a, ary| 
    if a == 2 
    ary << a*a 
    else 
    ary << a 
    end 
end 
# => [1, 4, 3, 4, 5] 

Oczywiście, w tym momencie, cała sprawa może być zmniejszona dalej, i może skorzystać z potrójnym wersji przy użyciu:

[1,2,3,4,5].each_with_object([]) do |a, ary| 
    a2 = (a == 2) ? a * a : a 
    ary << a2 
end 
# => [1, 4, 3, 4, 5] 

Który z dwóch powyższych są bardziej czytelne jest nieco do osoby go i osobę odpowiedzialną za utrzymanie go kodowania. Skłoniłbym się do wersji innej niż trójskładnikowa, ponieważ łatwiej jest ją rozszerzać/rozszerzać i nie ma szumu linii trójskładnikowego łańcucha o numerze trzydziestym trzydziestym dziewiątym.


Ponieważ został poproszony w komentarzach, map redukuje hałas, a to, jak powinniśmy przekształcić tablicę:

[1,2,3,4,5].map { |a| 
    (a == 2) ? a * a : a 
} 

to niesprawdzone ale wygląda poprawne.

+0

Zdecydowanie się zgadzam. Piękna odpowiedź. –

+1

Zastanawiam się, dlaczego nie wspomniałeś o "mapie"? –

+0

@PatriceGahide Pytanie było prawdopodobnie, dlaczego moje 'next' nie działa? :-) Nie, jak napisać to lepiej w lepszy sposób. –