2015-06-15 10 views
7

Ich bin neu in Scala und versuchen, die beste Art und Weise, um herauszufinden, zu filtern & eine Sammlung Karte. Hier ist ein Spielzeugbeispiel, um mein Problem zu erklären.Scala: Der beste Weg zu filtern & Karte in einer Iteration

Ansatz 1: Das ist ziemlich schlecht, da ich zweimal durch die Liste iteriere und den gleichen Wert in jeder Iteration berechne.

val N = 5 
val nums = 0 until 10 
val sqNumsLargerThanN = nums filter { x: Int => (x * x) > N } map { x: Int => (x * x).toString } 

Ansatz 2: Dies ist etwas besser, aber ich muss noch (x * x) zweimal berechnen.

Also, ist es möglich, dies zu berechnen, ohne zweimal durch die Sammlung zu iterieren und die gleichen Berechnungen zu vermeiden?

Antwort

2

können Sie collect verwenden, die zu jedem Wert der Sammlung eine Teilfunktion gilt, dass sie in bestimmten ist. Ihr Beispiel könnte wie folgt geschrieben werden:

val sqNumsLargerThanN = nums collect { 
    case (x: Int) if (x * x) > N => (x * x).toString 
} 
+0

Warum hat jemand folgt Down-Abstimmung diese Antwort? 'collect' scheint wie eine sehr idiomatische Art, dies zu tun. –

+0

Ist das nicht genau mein "Approach 2"? –

+0

Ja, es ist das gleiche wie Ansatz 2 oben, und nach der Definition von _collect_ zu gehen, erscheint mir dies vollkommen vernünftig; es sagt genau was es tut. Dies soll nicht heißen, dass andere oben erläuterte Ansätze besser oder schlechter sind. – Nirmalya

4

Der typische Ansatz ist es, ein iterator (wenn möglich) oder view zu verwenden (wenn iterator wird nicht funktionieren). Dies gilt nicht genau zwei Überquerungen vermeiden, aber es tut Schaffung einer Full-Size-Zwischen Sammlung zu vermeiden. Sie dann map erste und filter danach und dann map wieder, wenn nötig:

xs.iterator.map(x => x*x).filter(_ > N).map(_.toString) 

Der Vorteil dieses Ansatzes ist, dass es wirklich einfach ist und zu lesen, da es keine Zwischen Sammlungen sind, ist es einigermaßen effizient ist.

Wenn Sie fragen, denn dies ist eine Performance-Engpass ist, dann ist die Antwort in der Regel eine Schwanz-rekursive Funktion oder verwenden Sie den alten Stil while-Schleife-Methode zu schreiben. Zum Beispiel, in Ihrem Fall

def sumSqBigN(xs: Array[Int], N: Int): Array[String] = { 
    val ysb = Array.newBuilder[String] 
    def inner(start: Int): Array[String] = { 
    if (start >= xs.length) ysb.result 
    else { 
     val sq = xs(start) * xs(start) 
     if (sq > N) ysb += sq.toString 
     inner(start + 1) 
    } 
    } 
    inner(0) 
} 

Sie können auch einen Parameter vorwärts in inner anstelle der Verwendung eines externen Builder (besonders nützlich für Summen) übergeben.

+0

Hi Rex - was meinst du damit nicht genau zwei Durchquerungen vermeiden? – sourcedelica

+0

@sourceDelica - Jeder Iterator führt beim Durchlaufen der Liste (notwendigerweise) die vorherigen Iteratoren durch. Also durchlaufen sie alle in lock-step, aber wenn Sie mappen, dann filtern, dann mappen, haben Sie tatsächlich next/hasNext Aufrufe, die drei tief verschachtelt sind. –

7

Könnte ein foldRight

nums.foldRight(List.empty[Int]) { 
    case (i, is) => 
    val s = i * i 
    if (s > N) s :: is else is 
    } 

A verwenden foldLeft erreichen würde auch ein ähnliches Ziel, aber die resultierende Liste in umgekehrter Reihenfolge (aufgrund der Assoziativität von foldLeft.

Alternativ wäre, wenn du würdest gerne mit Scalaz spielen

import scalaz.std.list._ 
import scalaz.syntax.foldable._ 

nums.foldMap { i => 
    val s = i * i 
    if (s > N) List(s) else List() 
} 
+0

Beachten Sie, dass Sie mit dem Standard "foldRight" Ihren Stack überlaufen lassen, wenn Ihre Liste mehr als tausend Elemente lang ist. Auch die Scalaz-Version hat keinen Vorteil gegenüber einer 'flatMap'. –

3

Ein sehr einfacher Ansatz, der nur die Multiplikation durchführt o nce. Es ist auch faul, so wird es nur seine Ausführung von Code, wenn nötig.

nums.view.map(x=>x*x).withFilter(x => x> N).map(_.toString) 

Werfen Sie einen Blick here für Unterschiede zwischen filter und withFilter.

+0

Das ist sehr interessant. In dem Thread, mit dem Sie verlinkt haben, gibt es einen Kommentar "Ich glaube nicht, dass Sie selbst mit Filter verwenden sollten (abgesehen von impliziten For-Ausdrücken)". Gibt es einen Grund, "withFilter" nicht zu verwenden? –

+0

Ich benutze 'filter' nur, wenn ich eine neue Sammlung erstellen möchte, um sie später zu verwenden. Wenn ich nur einen Filter als Zwischenschritt einer Pipeline von Operationen haben möchte, verwende ich immer 'withFilter'. – marios

2

Ich habe noch zu bestätigen, dass dies wirklich ein einziger Durchgang, aber:

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    if (square > N) Some(x) else None 
    } 
+0

Ich möchte fragen, wird das Laden von jedem Element für eine Option Layer leichter sein als x * x zweimal berechnen? Die Kosten für die Erstellung von Optionsobjekten können ignoriert werden? (Ich bin neu in Scala von C++.) –

+1

Um Ihre Frage direkt zu beantworten, nein, die Option Zuweisung ist nicht kostenlos. Es ist aber billig.Der JVM GC hat sich im Laufe der Jahre sehr gut entwickelt und kleine Objekte in Loops gesammelt und gesammelt. Obwohl es nicht frei ist, ist dies fast nie der Ort, an dem ich mit der Optimierung beginnen würde. – triggerNZ

+2

Darüber hinaus sollte ich erwähnen, dass, während dies ein lustiges Puzzle zu lösen ist, der Versuch, die Anzahl der Durchgänge über eine Sammlung in der Welt der funktionalen Programmierung zu minimieren, in der Regel nicht der beste Weg ist, Leistung zu gewinnen. Diese Dinge sind in der C/C++ - Welt üblich und auf der JVM viel seltener. Lassen Sie uns annehmen, dass Ihre Sammlung riesig ist, sagen wir 8GB. Dann willst du wirklich nur einmal vorbeikommen, und ich würde bei Collect bleiben, oder bei der Verwendung von faulen Sammlungen. Die Doppelmultiplikation wird vom JIT – triggerNZ

2

das Betrachten Sie zum Verständnis,

for (x <- 0 until 10; v = x*x if v > N) yield v.toString 

die zu einem flatMap über den Bereich entfaltet und ein (faul) withFilter auf das einmal berechnete Quadrat und ergibt eine Sammlung mit gefilterten Ergebnissen. Es ist erforderlich, eine Iteration und eine Berechnung des Quadrats zu notieren (zusätzlich zum Erstellen des Bereichs).

+0

@ErikMadsen wirklich, danke einen Haufen, behoben :) – elm

0

Sie können flatMap verwenden.

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    if (square > N) Some(square.toString) else None 
} 

Oder mit Scalaz,

import scalaz.Scalaz._ 

val sqNumsLargerThanN = nums flatMap { x => 
    val square = x * x 
    (square > N).option(square.toString) 
} 

Das löst die gestellte Frage, wie diese mit einer Iteration zu tun. Dies kann nützlich sein, wenn Daten wie mit einem Iterator gestreamt werden.

Allerdings ... wenn Sie stattdessen wollen die absolute schnellste Implementierung, das ist es nicht. Tatsächlich vermute ich, dass Sie eine veränderbare ArrayList und eine while-Schleife verwenden würden. Aber erst nach dem Profiling würden Sie es sicher wissen. Auf jeden Fall ist das für eine andere Frage.

0

ein für das Verständnis Verwendung funktionieren würde:

val sqNumsLargerThanN = for {x <- nums if x*x > N } yield (x*x).toString 

Auch ich bin nicht sicher, aber ich denke, die scala Compiler über einen Filter vor einer Karte smart ist und tun nur 1 Durchgang, wenn möglich.

-2

ich auch tat es Anfänger als

for(y<-(num.map(x=>x*x)) if y>5) { println(y)}