Mam wartość, map
która albo zmienia wartość, albo ustawia ją na zero. Następnie chcę usunąć z listy wpisy zerowe. Lista nie musi być przechowywana.
Oto, co obecnie mam:
# A simple example function, which returns a value or nil
def transform(n)
rand > 0.5 ? n * 10 : nil }
end
items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]
Wiem, że mógłbym po prostu wykonać pętlę i warunkowo zebrać w innej tablicy takiej jak ta:
new_items = []
items.each do |x|
x = transform(x)
new_items.append(x) unless x.nil?
end
items = new_items
Ale to nie wydaje się takie idiomatyczne. Czy istnieje dobry sposób na mapowanie funkcji na liście, usuwając / wykluczając nils w miarę przemieszczania się?
filter_map
, co wydaje się idealne do tego. Oszczędza potrzebę ponownego przetwarzania tablicy, zamiast tego wykonując ją zgodnie z oczekiwaniami za pierwszym razem. Więcej informacji tutaj.Odpowiedzi:
Ruby 2.7+
Jest teraz!
Ruby 2.7 wprowadza właśnie
filter_map
w tym celu. Jest idiomatyczny i skuteczny i spodziewałbym się, że wkrótce stanie się normą.Na przykład:
W twoim przypadku, gdy blok zmienia się w falsey, po prostu:
„ Ruby 2.7 dodaje Enumerable # filter_map ” to dobra lektura na ten temat, z pewnymi testami wydajności w stosunku do niektórych wcześniejszych podejść do tego problemu:
źródło
Możesz użyć
compact
:Chciałbym przypomnieć ludziom, że jeśli otrzymujesz tablicę zawierającą wartości zerowe jako wynik
map
bloku, a ten blok próbuje warunkowo zwrócić wartości, to masz zapach kodu i musisz przemyśleć swoją logikę.Na przykład, jeśli robisz coś, co to robi:
Więc nie rób tego. Zamiast tego, przed kontrolą
map
,reject
rzeczy nie chcesz lubselect
czego chcą:Rozważam użycie
compact
sprzątania bałaganu jako ostatniego kroku do pozbycia się rzeczy, z którymi nie radziliśmy sobie poprawnie, zwykle dlatego, że nie wiedzieliśmy, co nas czeka. Zawsze powinniśmy wiedzieć, jakie dane są przekazywane w naszym programie; Nieoczekiwane / nieznane dane są złe. Za każdym razem, gdy widzę zero w tablicy, nad którą pracuję, kopię, dlaczego istnieją, i sprawdzam, czy mogę poprawić kod generujący tablicę, zamiast pozwolić Ruby marnować czas i generować pamięć, a następnie przeszukiwać tablicę, aby usunąć je później.źródło
nil
wpisy, a nie puste ciągi. BTW,nil
to nie to samo, co pusty ciąg.reduce
lubinject
?compact
jest najszybszy, ale tak naprawdę napisanie kodu na początku eliminuje potrzebę całkowitego radzenia sobie z zerami.Spróbuj użyć
reduce
lubinject
.Zgadzam się z przyjętymi odpowiedź, że nie powinniśmy
map
icompact
, ale nie z tych samych powodów.Czuję się głęboko wewnątrz, które
map
następniecompact
jest równoważneselect
wtedymap
. Zastanów się:map
to funkcja jeden do jednego. Jeśli odwzorowujesz z jakiegoś zestawu wartości, a tymap
, to potrzebujesz jednej wartości w zestawie wyjściowym dla każdej wartości w zestawie wejściowym. Jeśli musisz zselect
wyprzedzeniem, prawdopodobnie nie chcesz miećmap
na planie. Jeśli musiszselect
później (lubcompact
), prawdopodobnie nie chcesz miećmap
na planie. W obu przypadkach iterujesz dwa razy przez cały zestaw, gdy wystarczyreduce
tylko raz.Również w języku angielskim próbujesz „zredukować zestaw liczb całkowitych do zestawu parzystych liczb całkowitych”.
źródło
W twoim przykładzie:
nie wygląda na to, że wartości uległy zmianie poza zastąpieniem
nil
. W takim przypadku:wystarczy.
źródło
Jeśli chcesz luźniejszego kryterium odrzucenia, na przykład, aby odrzucić puste ciągi, a także zero, możesz użyć:
Jeśli chcesz pójść dalej i odrzucić wartości zerowe (lub zastosować bardziej złożoną logikę do procesu), możesz przekazać blok, aby odrzucić:
źródło
blank?
jest on dostępny tylko w szynach, moglibyśmy użyć tego,items.reject!(&:nil?) # [1, nil, 3, nil, nil] => [1, 3]
który nie jest połączony z szynami. (nie wyklucza jednak pustych ciągów lub zer)Zdecydowanie
compact
jest to najlepsze podejście do rozwiązania tego zadania. Możemy jednak osiągnąć ten sam wynik po prostu odejmując:źródło
each_with_object
jest prawdopodobnie najczystszym sposobem na przejście tutaj:Moim zdaniem
each_with_object
jest lepszy niżinject
/reduce
w przypadkach warunkowych, ponieważ nie musisz się martwić o wartość zwracaną bloku.źródło
Jeszcze jeden sposób na osiągnięcie tego będzie taki, jak pokazano poniżej. Tutaj używamy
Enumerable#each_with_object
do zbierania wartości i wykorzystujemyObject#tap
do pozbycia się zmiennej tymczasowej, która w przeciwnym razie byłaby potrzebna donil
sprawdzenia wynikuprocess_x
metody.Kompletny przykład dla ilustracji:
Alternatywne podejście:
Patrząc na metodę, którą wywołujesz
process_x url
, nie jest jasne, jaki jest cel wprowadzaniax
tej metody. Jeśli założę, że zamierzasz przetworzyć wartośćx
przekazania jejurl
i określić, które z nichx
naprawdę zostaną przetworzone na prawidłowe wyniki inne niż zero - wtedy może byćEnumerabble.group_by
lepszym rozwiązaniem niżEnumerable#map
.źródło