Mam tablicę Ruby zawierającą wartości ciągów. Potrzebuję:
- Znajdź wszystkie elementy, które pasują do jakiegoś predykatu
- Przeprowadź transformację dopasowanych elementów
- Zwróć wyniki jako tablicę
W tej chwili moje rozwiązanie wygląda następująco:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Czy istnieje metoda Array lub Enumerable, która łączy wybieranie i mapowanie w jedną instrukcję logiczną?
Enumerable#grep
Metoda robi dokładnie to, co został poproszony i została w Ruby od ponad dziesięciu lat. Pobiera argument predykatu i blok transformacji. @hirolau daje jedyną poprawną odpowiedź na to pytanie.filter_map
tym właśnie celu wprowadza się Ruby 2.7 . Więcej informacji tutaj .Odpowiedzi:
Zwykle używam
map
icompact
razem z moimi kryteriami wyboru jako postfiksemif
.compact
pozbywa się zer.źródło
map
+compact
naprawdę działałby lepiej niżinject
i opublikowałem wyniki testów porównawczych w powiązanym wątku: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
iselect
jest to po prostucompact
specjalny przypadek,reject
który działa na zerach i działa nieco lepiej, ponieważ został zaimplementowany bezpośrednio w C.Możesz użyć
reduce
do tego, co wymaga tylko jednego przejścia:Innymi słowy, zainicjuj stan tak, jak chcesz (w naszym przypadku pusta lista do wypełnienia:)
[]
, a następnie zawsze upewnij się, że zwracasz tę wartość z modyfikacjami dla każdego elementu z oryginalnej listy (w naszym przypadku zmodyfikowany element przeniesiony na listę).Jest to najbardziej wydajne, ponieważ powoduje pętle po liście tylko jednym przebiegiem (
map
+select
lubcompact
wymaga dwóch przebiegów).W Twoim przypadku:
źródło
each_with_object
ma większego sensu? Nie musisz zwracać tablicy na końcu każdej iteracji bloku. Możesz po prostu to zrobićmy_array.each_with_object([]) { |i, a| a << i if i.condition }
.reduce
Pochodzę z funkcjonalnego zaplecza, dlatego trafiłem jako pierwszy 😊Ruby 2.7+
Jest teraz!
W
filter_map
tym właśnie celu wprowadza się Ruby 2.7 . Jest idiomatyczny i performatywny i spodziewałbym się, że wkrótce stanie się normą.Na przykład:
Oto dobra lektura na ten temat .
Mam nadzieję, że to komuś się przyda!
źródło
filter
,select
ifind_all
są synonimami, tak jakmap
icollect
są, może być trudno zapamiętać nazwę tej metody. Jest tofilter_map
,select_collect
,find_all_map
lubfilter_collect
?Innym innym sposobem podejścia jest użycie nowego (w stosunku do tego pytania)
Enumerator::Lazy
:.lazy
Metoda zwraca leniwe wyliczający. Wywołanie.select
lub.map
na leniwym module wyliczającym zwraca inny leniwy moduł wyliczający. Dopiero po wywołaniu.uniq
faktycznie wymusza to moduł wyliczający i zwraca tablicę. Skutecznie się dzieje,.select
a.map
połączenia są łączone w jedno - powtarzasz tylko@lines
raz, aby wykonać oba.select
i.map
.Wydaje mi się, że
reduce
metoda Adama będzie trochę szybsza, ale myślę, że jest to znacznie bardziej czytelne.Główną konsekwencją tego jest to, że dla każdego kolejnego wywołania metody nie są tworzone żadne pośrednie obiekty tablicy. W normalnej
@lines.select.map
sytuacjiselect
zwraca tablicę, która jest następnie modyfikowana przezmap
, ponownie zwracając tablicę. Dla porównania, leniwa ocena tworzy tablicę tylko raz. Jest to przydatne, gdy początkowy obiekt kolekcji jest duży. Umożliwia także pracę z nieskończonymi modułami wyliczającymi - nprandom_number_generator.lazy.select(&:odd?).take(10)
.źródło
reduce
ponieważ transformacja typu „robienie wszystkiego” zawsze wydaje mi się dość niechlujna.@lines
raz, aby zrobić jedno.select
i drugie.map
”. Użycie.lazy
nie oznacza, że operacje łańcuchowe na leniwym module wyliczającym zostaną „zwinięte” w jedną iterację. Jest to powszechne nieporozumienie dotyczące leniwej oceny w ramach operacji łączenia w łańcuch na kolekcji. (Możesz to sprawdzić, dodającputs
instrukcję na początku blokówselect
imap
w pierwszym przykładzie..lazy
, drukuje tę samą liczbę razy. O to mi chodzi - twójmap
blok i twójselect
blok są wykonywane tyle samo razy w wersji leniwej i chętnej. Wersja leniwa nie „łączy Twojego.select
i.map
dzwoni”lazy
łączy je, ponieważ element, który nie spełniaselect
warunku, nie jest przekazywany domap
. Innymi słowy: poprzedzanielazy
jest z grubsza równoważne zastępowaniuselect
imap
pojedynczymreduce([])
, a „inteligentnie” uczynienieselect
bloku warunkiem wstępnym włączenia doreduce
wyniku.Jeśli masz,
select
że możesz użyćcase
operatora (===
),grep
jest to dobra alternatywa:Jeśli potrzebujemy bardziej złożonej logiki, możemy stworzyć lambdy:
źródło
Nie jestem pewien, czy istnieje. Moduł Enumerable , która dodaje
select
imap
nie wykazuje jeden.Będziesz musiał przekazać
select_and_transform
metodę w dwóch blokach , co byłoby nieco nieintuicyjne w IMHO.Oczywiście możesz po prostu połączyć je ze sobą, co jest bardziej czytelne:
źródło
Prosta odpowiedź:
Jeśli masz n rekordów i chcesz,
select
imap
na podstawie warunku, toTutaj atrybut jest tym, co chcesz z rekordu i warunkiem, który możesz sprawdzić.
compact to spłukanie niepotrzebnych zera, które wyszły z tego warunku if
źródło
Nie, ale możesz to zrobić w ten sposób:
Albo jeszcze lepiej:
źródło
reject(&:nil?)
jest w zasadzie taki sam jakcompact
.Myślę, że ten sposób jest bardziej czytelny, ponieważ dzieli warunki filtru i zmapowaną wartość, pozostając jednocześnie jasnym, że akcje są połączone:
W swoim konkretnym przypadku usuń
result
zmienną wszystkie razem:źródło
W Rubim 1.9 i 1.8.7 możesz także łączyć i zawijać iteratory, po prostu nie przekazując im bloku:
Ale w tym przypadku nie jest to naprawdę możliwe, ponieważ typy bloków zwracają wartości
select
imap
nie pasują do siebie. Ma to większy sens w przypadku czegoś takiego:AFAICS, najlepsze, co możesz zrobić, to pierwszy przykład.
Oto mały przykład:
Ale to, czego naprawdę chcesz, to
["A", "B", "C", "D"]
.źródło
select
zwraca wartość typu Boolean, która decyduje, czy zachować element, czy nie,map
zwraca przekształconą wartość. Przekształcona wartość prawdopodobnie będzie prawdziwa, więc wszystkie elementy zostaną wybrane.Powinieneś spróbować użyć mojej biblioteki Rearmed Ruby, w której dodałem metodę
Enumerable#select_map
. Oto przykład:źródło
select_map
w tej bibliotece po prostu implementuje to samoselect { |i| ... }.map { |i| ... }
strategię z wielu odpowiedzi powyżej.Jeśli nie chcesz tworzyć dwóch różnych tablic, możesz ich użyć,
compact!
ale zachowaj ostrożność.Co ciekawe,
compact!
usuwa zero na miejscu. Wartość zwracanacompact!
jest tą samą tablicą, jeśli wystąpiły zmiany, ale nil, jeśli nie było żadnych wartości zerowych.Byłby to jeden liniowiec.
źródło
Twoja wersja:
Moja wersja:
Spowoduje to wykonanie 1 iteracji (z wyjątkiem sortowania) i ma dodatkową zaletę w postaci zachowania unikalności (jeśli nie obchodzi cię unikalność, po prostu utwórz z wyników tablicę i
results.push(line) if ...
źródło
Oto przykład. To nie to samo, co twój problem, ale może być tym, czego chcesz lub może dać wskazówkę do rozwiązania:
źródło