2013-07-01 8 views
5

I einen Code haben, wie zum Beispiel:Scala, das Filter eine Sammlung basierend auf mehreren Bedingungen

val strs = List("hello", "andorra", "trab", "world") 
def f1(s: String) = !s.startsWith("a") 
def f2(s: String) = !s.endsWith("b") 

val result = strs.filter(f1).filter(f2) 

nun, f1 und f2 sollten, basierend auf einer Bedingung angewandt werden, wie zum Beispiel:

val tmp1 = if (cond1) strs.filter(f1) else strs 
val out = if (cond2) tmp1.filter(f2) else tmp1 

ist gibt es einen schöneren Weg, dies zu tun, ohne eine temporäre Variable zu verwenden tmp1?

Eine Möglichkeit wäre auf einer Liste von Funktionen zu filtern, wie zB:

val fs = List(f1 _,f2 _) 
fs.foldLeft(strs)((fn, list) => list.filter(fn)) 

aber dann würde ich eine Liste von Funktionen, basierend auf den Bedingungen bauen muß (und so würde ich das Problem, bewegt Verwenden einer temporären Variablenliste, um eine temporäre Funktionslistenvariable zu verwenden (oder ich müsste eine veränderbare Liste verwenden)).

Ich suche so etwas wie dieses (natürlich ist dies nicht kompilieren, sonst hätte ich schon die Antwort auf die Frage):

val result = 
    strs 
    .if(cond1, filter(f1)) 
    .if(cond2, filter(f2)) 
+0

Klingt wie Sie wollen eine Liste von Tupeln mit (Bedingung, FilterPredicate). Sie können diese Liste dann basierend darauf filtern, ob die Bedingung (z. B. _._ 1) gilt oder nicht. Jetzt haben Sie eine Liste von Funktionen, die Sie anwenden möchten. Sie können dies dann mit der Zeichenkette abbilden und mit && (logisch und) reduzieren. Entschuldigung, wenn es zu wellig ist. – Felix

+0

danke, das ist auch eine gute Idee, aber ich sah etwas eher wie Noahs Antwort. –

Antwort

8

Sie leicht eine implizite Klasse verwenden, können Sie diese Syntax zu geben:

val strs = List("hello", "andorra", "trab", "world") 

    def f1(s: String) = !s.startsWith("a") 

    def f2(s: String) = !s.endsWith("b") 

    val cond1 = true 
    val cond2 = true 

    implicit class FilterHelper[A](l: List[A]) { 
    def ifFilter(cond: Boolean, f: A => Boolean) = { 
     if (cond) l.filter(f) else l 
    } 
    } 

    strs 
    .ifFilter(cond1, f1) 
    .ifFilter(cond2, f2) 

res1: List[String] = List(hello, world) 

I if als Methodennamen verwendet haben würde, aber es ist ein reserviertes Wort.

+0

das ist, was ich gesucht habe; dann kann ich es in mehreren Schritten verarbeiten wie in strs.filter (...). map (...). ifFilter (...). map (...). ifFilter (...). Ich würde nur "l.filter (a =>! Cond || filter (a))" durch "if (cond) l.filter (filter) else l" ändern, um die Verwendung eines Filters mit einer immer wahren Bedingung zu vermeiden. –

+0

Ich habe den Code aktualisiert, um die Änderung widerzuspiegeln, und muss nicht mehr durchlaufen werden, wenn Sie dies nicht tun müssen. – Noah

3

Sie können dies tun, indem Sie Ihre Prädikatfunktionen summieren.

Beachten Sie, dass ein Filterprädikat, A => Boolean, eine Anfügevorgang hat:

def append[A](p1: A => Boolean, p2: A => Boolean): A => Boolean = 
    a => p1(a) && p2(a) 

und einen Kennwert:

def id[A]: A => Boolean = 
    _ => true 

, die die Bedingung, dass für jedes Prädikat p: A => Boolean, append(p, id) === p erfüllt.

Dies vereinfacht das Problem des Einschließens/Ausschließens eines Prädikats basierend auf einer Bedingung: Wenn die Bedingung falsch ist, fügen Sie einfach das Prädikat id ein. Es hat keine Auswirkungen auf den Filter, da es immer true zurückgibt.

Um die Prädikate Summe:

def sum[A](ps: List[A => Boolean]): A => Boolean = 
    ps.foldLeft[A => Boolean](id)(append) 

Beachten Sie, dass wir auf id falten, so dass, wenn ps leer ist, erhalten wir die Identität Prädikat, das heißt ein Filter, der nichts tut, wie man erwarten würde.

Putting dies alles zusammen:

val predicates = List(cond1 -> f1 _, cond2 -> f2 _) 

strs.filter(sum(predicates.collect { case (cond, p) if cond => p })) 
// List(hello, world) 

Beachten Sie, dass die Liste strs nur einmal durchlaufen wurde.


Nun, für die Scalaz Version des obigen:

val predicates = List(cond1 -> f1 _, cond2 -> f2 _) 

strs filter predicates.foldMap { 
    case (cond, p) => cond ?? (p andThen (_.conjunction)) 
} 
// List("hello", "world") 
+0

Das Problem ist, dass, wenn cond1 = false, Sie etwas wie "strs.filter (true)" anwenden, die langsamer ist als nur die Liste selbst zurückgeben. –

+1

David: Aber wenn beide Bedingungen zutreffen, wird "Filter" nur einmal angewendet. Für 'n' Prädikate wird Ihre akzeptierte Lösung' Filter' bis zu 'n' laufen lassen! –

+0

Willst du sagen, dass diese 'appd'- und identity' id'-Operationen/-Funktionen existieren oder nur, dass sie definiert werden können? Ich kann sie in Scala 2.10 nicht finden. –

3

@ Noah Antwort ist gut, und man kann es es nehmen und verallgemeinern weiter, wenn Sie jede Art von durchführen zu können, wollen Aktion auf einer Liste gibt dann eine neue Liste gegeben eine Bedingung, wenn Sie die folgende Änderung vornehmen:

implicit class FilterHelper[A](l: List[A]) { 
    def ifthen[B](cond: Boolean, f:(List[A]) => List[B]) = { 
    if (cond) f(l) else l 
    } 
} 

es dann wie folgt verwenden:

val list = List("1", "2")  
val l2 = list.ifthen(someCondition, _.filter(f1) 
val l3 = list.ifthen(someOtherCondition, _.map(_.size)) 
2

Es wäre ziemlich einfach sein, nur den Zustand, in dem Block für die Filter umfassen, etwa so:

val result = strs filter (x => !cond1 || f1(x)) filter (x => !cond2 || f2(x)) 

wobei das Ergebnis einen Filter anzuwenden wäre, wenn die Bedingung erfüllt ist, oder Sie einfach die gleiche Liste.

+0

Ja, aber ich denke, der Punkt ist, warum sogar die Funktion anwenden, wenn nichts tun wird. Warum iterieren Sie die Liste (sagen Sie, es ist sehr groß), wenn Sie nicht müssen? – cmbaxter

+0

Könnte 'strs Filter schreiben {x => (! Cond1 || f1 (x)) && (! Cond2 || f2 (x))}' so dass Sie nur einmal durchlaufen –

Verwandte Themen