Ich habe ein Ruby-Array mit einigen Zeichenfolgenwerten. Ich muss einfach:
- Finden Sie alle Elemente, die einem Prädikat entsprechen
- Führen Sie die übereinstimmenden Elemente durch eine Transformation
- Geben Sie die Ergebnisse als Array zurück
Im Moment sieht meine Lösung so aus:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Gibt es eine Array- oder Enumerable-Methode, die select und map zu einer einzigen logischen Anweisung kombiniert?
Enumerable#grep
Methode macht genau das, was gefragt wurde und ist seit über zehn Jahren in Ruby. Es braucht ein Prädikatargument und einen Transformationsblock. @hirolau gibt die einzig richtige Antwort auf diese Frage.filter_map
genau für diesen Zweck eingeführt. Mehr Infos hier .Antworten:
Normalerweise verwende ich
map
undcompact
zusammen mit meinen Auswahlkriterien als Postfixif
.compact
wird die Nullen los.quelle
map
+compact
wirklich besser abschneiden würde alsinject
und veröffentlichte meine Benchmark-Ergebnisse in einem verwandten Thread: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…map
undselect
es ist nurcompact
ein Sonderfallreject
, der mit Nullen funktioniert und etwas besser abschneidet, da er direkt in C implementiert wurde.Sie können
reduce
dies verwenden, was nur einen Durchgang erfordert:Mit anderen Worten, initialisieren Sie den Status so, wie Sie es möchten (in unserem Fall eine leere Liste, die gefüllt werden muss :)
[]
, und stellen Sie dann immer sicher, dass Sie diesen Wert mit Änderungen für jedes Element in der ursprünglichen Liste (in unserem Fall das geänderte Element) zurückgeben auf die Liste geschoben).Dies ist am effizientesten, da die Liste nur mit einem Durchgang durchlaufen wird (
map
+select
odercompact
zwei Durchgänge erforderlich sind).In deinem Fall:
quelle
each_with_object
ein bisschen mehr Sinn? Sie müssen das Array nicht am Ende jeder Iteration des Blocks zurückgeben. Sie können einfach tunmy_array.each_with_object([]) { |i, a| a << i if i.condition }
.reduce
zuerst gefunden 😊Ruby 2.7+
Da ist jetzt!
Ruby 2.7 wird
filter_map
genau für diesen Zweck eingeführt. Es ist idiomatisch und performant, und ich würde erwarten, dass es sehr bald zur Norm wird.Beispielsweise:
Hier ist eine gute Lektüre zu diesem Thema .
Hoffe das ist nützlich für jemanden!
quelle
filter
,select
undfind_all
sind auch, wiemap
undcollect
sind, könnte es schwierig sein , den Namen dieser Methode zu erinnern. Ist esfilter_map
,select_collect
,find_all_map
oderfilter_collect
?Eine andere Art, dies zu erreichen, ist die Verwendung des Neuen (relativ zu dieser Frage)
Enumerator::Lazy
:Die
.lazy
Methode gibt einen Lazy Enumerator zurück. Wenn Sie einen Lazy Enumerator aufrufen.select
oder.map
aktivieren, wird ein anderer Lazy Enumerator zurückgegeben. Erst wenn Sie aufrufen,.uniq
wird der Enumerator erzwungen und ein Array zurückgegeben. Was also effektiv passiert, ist, dass Ihre.select
und.map
Anrufe zu einem zusammengefasst werden - Sie iterieren nur@lines
einmal, um beides.select
und zu tun.map
.Mein Instinkt ist, dass Adams
reduce
Methode etwas schneller sein wird, aber ich denke, dass dies weitaus besser lesbar ist.Die Hauptfolge davon ist, dass für jeden nachfolgenden Methodenaufruf keine Zwischenarrayobjekte erstellt werden. Gibt in einer normalen
@lines.select.map
Situationselect
ein Array zurück, das dann von geändert wirdmap
, und gibt erneut ein Array zurück. Im Vergleich dazu erstellt die verzögerte Auswertung ein Array nur einmal. Dies ist nützlich, wenn Ihr anfängliches Sammlungsobjekt groß ist. Es ermöglicht Ihnen auch, mit unendlichen Enumeratoren zu arbeiten - zrandom_number_generator.lazy.select(&:odd?).take(10)
.quelle
reduce
Als "alles tun" -Transformation fühlt sich für mich immer ziemlich chaotisch an.@lines
einmal iterieren , um beides zu tun.select
und.map
". Die Verwendung.lazy
bedeutet nicht, dass verkettete Operationen Operationen auf einem Lazy Enumerator in einer einzigen Iteration "reduziert" werden. Dies ist ein häufiges Missverständnis der verzögerten Bewertung von Verkettungsvorgängen über eine Sammlung. (Sie können dies testen, indem Sie im ersten Beispiel eineputs
Anweisung am Anfang der Blöckeselect
und hinzufügenmap
. Sie werden feststellen, dass sie die gleiche Anzahl von Zeilen drucken).lazy
es genauso oft gedruckt. Das ist mein Punkt - Ihrmap
Block und Ihrselect
Block werden in den faulen und eifrigen Versionen gleich oft ausgeführt. Die faule Version "kombiniert nicht Ihre.select
und.map
Anrufe"lazy
verbindet sie , weil ein Element , das die nichtselect
erhalten Bedingung nicht auf den übergebenenmap
. Mit anderen Worten: Das Voranstellenlazy
ist ungefähr gleichbedeutend mit dem Ersetzenselect
undmap
durch ein einzelnesreduce([])
und "intelligent", wasselect
den Block zu einer Voraussetzung für die Aufnahme inreduce
das Ergebnis macht.Wenn Sie eine haben
select
, die dencase
Operator (===
) verwenden kann,grep
ist dies eine gute Alternative:Wenn wir eine komplexere Logik benötigen, können wir Lambdas erstellen:
quelle
Ich bin mir nicht sicher, ob es einen gibt. Das Enumerable-Modul , das
select
und hinzufügtmap
, zeigt keines an.Sie müssten in zwei Blöcken an die
select_and_transform
Methode übergeben, was meiner Meinung nach etwas unintuitiv wäre.Natürlich können Sie sie einfach miteinander verketten, was besser lesbar ist:
quelle
Einfache Antwort:
Wenn Sie n Datensätze haben und möchten
select
undmap
basierend auf der Bedingung dannHier ist das Attribut das, was Sie von dem Datensatz und der Bedingung erwarten, die Sie überprüfen können.
Kompakt ist es, die unnötigen Nullen zu spülen, die aus diesem Zustand hervorgegangen sind
quelle
Nein, aber Sie können es so machen:
Oder noch besser:
quelle
reject(&:nil?)
ist im Grunde das gleiche wiecompact
.Ich denke, dass dieser Weg besser lesbar ist, da die Filterbedingungen und der zugeordnete Wert aufgeteilt werden, während klar bleibt, dass die Aktionen miteinander verbunden sind:
Und eliminieren Sie in Ihrem speziellen Fall die
result
Variable insgesamt:quelle
In Ruby 1.9 und 1.8.7 können Sie Iteratoren auch verketten und umschließen, indem Sie ihnen einfach keinen Block übergeben:
In diesem Fall ist dies jedoch nicht wirklich möglich, da die Typen der Blockrückgabewerte von
select
undmap
nicht übereinstimmen. Für so etwas macht es mehr Sinn:AFAICS, das Beste, was Sie tun können, ist das erste Beispiel.
Hier ist ein kleines Beispiel:
Aber was Sie wirklich wollen, ist
["A", "B", "C", "D"]
.quelle
select
Gibt einen booleschen Wert zurück, der entscheidet, ob das Element beibehalten werden soll oder nicht.map
Gibt den transformierten Wert zurück. Der transformierte Wert selbst wird wahrscheinlich wahr sein, sodass alle Elemente ausgewählt werden.Sie sollten versuchen, meine Bibliothek Rearmed Ruby zu verwenden, in der ich die Methode hinzugefügt habe
Enumerable#select_map
. Hier ein Beispiel:quelle
select_map
in dieser Bibliothek implementiert nur die gleicheselect { |i| ... }.map { |i| ... }
Strategie aus vielen Antworten oben.Wenn Sie nicht zwei verschiedene Arrays erstellen möchten, können Sie diese verwenden,
compact!
aber seien Sie vorsichtig.Interessanterweise
compact!
wird an Ort und Stelle Null entfernt. Der Rückgabewert voncompact!
ist das gleiche Array, wenn Änderungen vorgenommen wurden, aber Null, wenn keine Nullen vorhanden waren.Wäre ein Einzeiler.
quelle
Deine Version:
Meine Version:
Dies führt 1 Iteration durch (mit Ausnahme der Sortierung) und hat den zusätzlichen Vorteil, dass die Eindeutigkeit erhalten bleibt (wenn Sie sich nicht für Uniq interessieren, machen Sie die Ergebnisse einfach zu einem Array und
results.push(line) if ...
quelle
Hier ist ein Beispiel. Es ist nicht dasselbe wie Ihr Problem, kann aber das sein, was Sie wollen, oder kann einen Hinweis auf Ihre Lösung geben:
quelle