114 Stimmen

Gibt es in Ruby eine Array-Methode, die 'select' und 'map' kombiniert?

Ich habe ein Ruby-Array, das einige String-Werte enthält. Das muss ich tun:

  1. Alle Elemente finden, die einem Prädikat entsprechen
  2. Durchlaufen Sie die übereinstimmenden Elemente durch eine Transformation
  3. Rückgabe der Ergebnisse als Array

Im Moment sieht meine Lösung wie folgt 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 in einer einzigen logischen Anweisung kombiniert?

120voto

Jed Schneider Punkte 13363

Ich verwende normalerweise map y compact zusammen mit meinen Auswahlkriterien als Postfix if . compact wird die Nils los.

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]

95voto

SRack Punkte 9986

Ruby 2.7+

Jetzt schon!

Mit Ruby 2.7 wird eingeführt filter_map für genau diesen Zweck. Es ist idiomatisch und leistungsfähig, und ich erwarte, dass es sehr bald zur Norm wird.

Zum Beispiel:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

Hier ist ein gute Lektüre zu diesem Thema .

Ich hoffe, das ist für jemanden nützlich!

57voto

Adam Lindberg Punkte 15973

Sie können verwenden reduce die nur einen einzigen Durchgang erfordert:

[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3] 

Mit anderen Worten, initialisieren Sie den Zustand so, wie Sie ihn haben wollen (in unserem Fall eine leere Liste zum Füllen): [] ), dann stellen Sie immer sicher, dass dieser Wert mit Änderungen für jedes Element in der ursprünglichen Liste zurückgegeben wird (in unserem Fall das geänderte Element, das in die Liste aufgenommen wurde).

Dies ist die effizienteste Methode, da sie die Liste nur in einem Durchgang durchläuft ( map + select o compact erfordert zwei Durchgänge).

In Ihrem Fall:

def example
  results = @lines.reduce([]) do |lines, line|
    lines.push( ...(line) ) if ...
    lines
  end
  return results.uniq.sort
end

22voto

henrebotha Punkte 1191

Eine andere Möglichkeit, dies zu tun, ist die Verwendung der neuen (auf diese Frage bezogenen) Enumerator::Lazy :

def example
  @lines.lazy
        .select { |line| line.property == requirement }
        .map    { |line| transforming_method(line) }
        .uniq
        .sort
end

En .lazy Methode gibt einen "Lazy Enumerator" zurück. Aufruf von .select o .map auf einen Lazy Enumerator gibt einen anderen Lazy Enumerator zurück. Nur einmal rufen Sie .uniq erzwingt er tatsächlich den Enumerator und gibt ein Array zurück. Was also effektiv passiert, ist Ihre .select y .map Aufrufe werden in einem einzigen zusammengefasst - Sie iterieren nur über @lines einmal, um beides zu tun .select y .map .

Mein Instinkt sagt mir, dass Adams reduce Methode ist zwar etwas schneller, aber ich denke, dass diese Methode viel besser lesbar ist.


Dies hat in erster Linie zur Folge, dass für jeden nachfolgenden Methodenaufruf keine Array-Zwischenobjekte erstellt werden. In einem normalen @lines.select.map Situation, select gibt ein Array zurück, das dann durch map und gibt wiederum ein Array zurück. Im Vergleich dazu wird bei der "Lazy Evaluation" nur einmal ein Array erstellt. Dies ist nützlich, wenn Ihr anfängliches Sammelobjekt groß ist. Es ermöglicht Ihnen auch die Arbeit mit unendlichen Aufzählungszeichen - z. B. random_number_generator.lazy.select(&:odd?).take(10) .

13voto

hirolau Punkte 12683

Wenn Sie eine select die den case Betreiber ( === ), grep ist eine gute Alternative:

p [1,2,'not_a_number',3].grep(Integer){|x| -x } #=> [-1, -2, -3]

p ['1','2','not_a_number','3'].grep(/\D/, &:upcase) #=> ["NOT_A_NUMBER"]

Wenn wir eine komplexere Logik benötigen, können wir Lambdas erstellen:

my_favourite_numbers = [1,4,6]

is_a_favourite_number = -> x { my_favourite_numbers.include? x }

make_awesome = -> x { "***#{x}***" }

my_data = [1,2,3,4]

p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"]

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X