2013-07-01 11 views
5

ho un codice come:scala, filtrare una collezione basata su diverse condizioni

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) 

ora, f1 e f2 dovrebbero essere applicati base a una condizione, come ad esempio:

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

è c'è un modo migliore per farlo, senza usare una variabile temporanea tmp1?

Un modo potrebbe filtrare sulla base di un elenco di funzioni, come ad esempio:

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

ma poi avrei bisogno di costruire una lista di funzioni in base alle condizioni (e così, vorrei spostare il problema di usare una variabile di lista di stringhe temporanea, di usare una variabile di lista di funzione temporanea (o dovrei avere bisogno di usare una lista mutabile)).

Sto cercando qualcosa di simile (naturalmente questo non viene compilato, altrimenti avrei già la risposta alla domanda):

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

Sembra che tu desideri un elenco di tuple con (condizione, filtroPredicato). È quindi possibile filtrare questo elenco in base alla presenza o meno di una condizione (ad es. _._ 1). Ora hai un elenco di funzioni che vuoi applicare. È quindi possibile mappare questo con la stringa e ridurre con && (logica e). Scusa se è troppo mosso. – Felix

+0

grazie, anche questa è una buona idea, ma stavo cercando qualcosa di più simile alla risposta di Noah. –

risposta

8

si potrebbe facilmente utilizzare una classe implicita di darvi questa sintassi:

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) 

avrei usato if come il nome del metodo, ma è una parola riservata.

+0

è quello che stavo cercando; quindi posso elaborarlo in più passaggi come in strs.filter (...). map (...). ifFilter (...). map (...). ifFilter (...). Modificherei solo "l.filter (a =>! Cond || filter (a))" di "if (cond) l.filter (filter) else l" per evitare di utilizzare un filtro con una condizione sempre true. –

+0

Ho aggiornato il codice per riflettere la modifica, non è necessario attraversare quando non è necessario. – Noah

3

È possibile eseguire questa operazione sommando le funzioni del predicato.

osservi che un predicato di filtro, A => Boolean, ha un'operazione di aggiunta:

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

E un valore di identità:

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

che soddisfa la condizione che per ogni predicato p: A => Boolean, append(p, id) === p.

Questo semplifica il problema di includere/escludere un predicato in base a una condizione: se la condizione è falsa, includere semplicemente il predicato id. Non ha alcun effetto sul filtro perché restituisce sempre true.

per sommare i predicati:

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

noti che pieghiamo sopra id, quindi se ps è vuota, si ottiene il predicato di identità, cioè un filtro che non fa nulla, come ci si aspetterebbe.

Mettendo tutto insieme:

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

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

Nota che la lista strs era attraversata solo una volta.


Ora, per la versione Scalaz di quanto sopra:

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

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

il problema è che se cond1 = false, stai applicando qualcosa come "strs.filter (true)", che è più lento del semplice ritorno della lista stessa. –

+1

David: ma, se entrambe le condizioni sono vere, applichiamo ancora 'filter' una sola volta. Per i predicati 'n', la soluzione accettata eseguirà' filter' fino a 'n volte! –

+0

Stai dicendo che queste operazioni/funzioni 'append' e identity' id' esistono, o solo che possono essere definite? Non riesco a trovarli in Scala 2.10. –

3

@ risposta di Noè è buona, e si può prendere e generalizzare ulteriormente se si vuole essere in grado di eseguire qualsiasi tipo di azione su una lista poi restituisce una nuova lista data una condizione se si effettua la seguente modifica:

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

quindi utilizzare in questo modo:

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

Sarebbe piuttosto semplice da solo includere la condizione nel blocco per il filtro, in questo modo:

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

per cui il risultato sarebbe applicare un filtro se la condizione è soddisfatta, o semplicemente restituire la stessa elenco.

+0

Sì, ma penso che il punto sia perché applicare la funzione anche se non farà nulla. Perché scorrere l'elenco (diciamo che è molto grande) se non è necessario? – cmbaxter

+0

Potrebbe scrivere 'strs filter {x => (! Cond1 || f1 (x)) && (! Cond2 || f2 (x))}' quindi attraversi solo una volta –

Problemi correlati