Ik heb een Ruby-array met enkele tekenreekswaarden. Ik moet:
- Zoek alle elementen die overeenkomen met een predikaat
- Laat de overeenkomende elementen door een transformatie lopen
- Retourneer de resultaten als een array
Op dit moment ziet mijn oplossing er als volgt uit:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Is er een Array- of Enumerable-methode die select en map combineert in een enkele logische instructie?
Antwoord 1, autoriteit 100%
Ik gebruik meestal map
en compact
samen met mijn selectiecriteria als een postfix if
. compact
haalt de nulpunten weg.
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}
=> [3, 3, 3, nil, nil, nil]
jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]
Antwoord 2, autoriteit 55%
Ruby 2.7+
Er is nu!
Ruby 2.7 introduceert filter_map
voor precies dit doel. Het is idiomatisch en performant, en ik verwacht dat het heel snel de norm zal worden.
Bijvoorbeeld:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Hier is een goed gelezen over de onderwerp.
Ik hoop dat iemand er iets aan heeft!
Antwoord 3, autoriteit 46%
U kunt hiervoor reduce
gebruiken, waarvoor slechts één pas nodig is:
[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3]
Met andere woorden, initialiseer de status zodat deze is wat u wilt (in ons geval een lege lijst om in te vullen: []
), en zorg er dan altijd voor dat u deze waarde retourneert met wijzigingen voor elk element in de originele lijst (in ons geval het gewijzigde element naar de lijst gepusht).
Dit is het meest efficiënt omdat het de lijst slechts met één doorgang doorloopt (map
+ select
of compact
vereist twee doorgangen).
In jouw geval:
def example
results = @lines.reduce([]) do |lines, line|
lines.push( ...(line) ) if ...
lines
end
return results.uniq.sort
end
Antwoord 4, autoriteit 18%
Een andere manier om dit te benaderen is het gebruik van de nieuwe (ten opzichte van deze vraag) Enumerator::Lazy
:
def example
@lines.lazy
.select { |line| line.property == requirement }
.map { |line| transforming_method(line) }
.uniq
.sort
end
De .lazy
methode retourneert een luie enumerator. Het aanroepen van .select
of .map
op een luie enumerator levert een andere luie enumerator op. Pas als je .uniq
aanroept, wordt de enumerator daadwerkelijk geforceerd en wordt een array geretourneerd. Dus wat er effectief gebeurt, is dat uw .select
en .map
-aanroepen worden gecombineerd tot één – u herhaalt slechts eenmaal @lines
om beide .select
en .map
.
Mijn instinct is dat Adams reduce
-methode iets sneller zal zijn, maar ik denk dat dit veel leesbaarder is.
Het primaire gevolg hiervan is dat er geen intermediaire array-objecten worden gemaakt voor elke volgende methode-oproep. In een normale @lines.select.map
situatie, select
retourneert een array die vervolgens wordt gewijzigd door map
, opnieuw een array retourneren. Ter vergelijking, de luie evaluatie creëert slechts eenmaal een array. Dit is handig wanneer uw eerste verzamelobject groot is. Het machtigt je ook om met oneindige makelaars te werken – b.v. random_number_generator.lazy.select(&:odd?).take(10)
.
Antwoord 5
Probeer mijn bibliotheek Rearmed Rubyte gebruiken waarin ik de methode Enumerable#select_map
. Hier is een voorbeeld:
items = [{version: "1.1"}, {version: nil}, {version: false}]
items.select_map{|x| x[:version]} #=> [{version: "1.1"}]
# or without enumerable monkey patch
Rearmed.select_map(items){|x| x[:version]}
Antwoord 6
Als je geen twee verschillende arrays wilt maken, kun je compact!
gebruiken, maar wees er voorzichtig mee.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}
new_array.compact!
Interessant is dat compact!
een in-place verwijdering van nul doet. De geretourneerde waarde van compact!
is dezelfde array als er wijzigingen waren, maar nul als er geen nulwaarden waren.
array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! }
Zou een oneliner zijn.
Antwoord 7
Jouw versie:
def example
matchingLines = @lines.select{ |line| ... }
results = matchingLines.map{ |line| ... }
return results.uniq.sort
end
Mijn versie:
def example
results = {}
@lines.each{ |line| results[line] = true if ... }
return results.keys.sort
end
Dit zal 1 iteratie doen (behalve de sortering), en heeft de toegevoegde bonus dat het uniek blijft (als u niet om uniq geeft, maak dan de resultaten gewoon een array en results.push(line) if ...
Antwoord 8
Hier is een voorbeeld. Het is niet hetzelfde als uw probleem, maar het kan zijn wat u wilt, of het kan een aanwijzing zijn voor uw oplossing:
def example
lines.each do |x|
new_value = do_transform(x)
if new_value == some_thing
return new_value # here jump out example method directly.
else
next # continue next iterate.
end
end
end