2010-02-01 6 views
10

Ho il seguente problema in scala. Devo trovare il primo elemento in un elenco che soddisfi una funzione di predicato con due condizioni in OR. Il problema è che mi piacerebbe ottenere l'elemento ma anche sapere quale delle due condizioni è stata soddisfatta. Ecco un semplice esempio:Trovare elementi in una lista scala e anche sapere quale predicato è stato soddisfatto

val l1 = List("A", "B", "AA", "BB") 
val l2 = List("AA", "BB", "A", "B") 

def c1(s: String) = s.startsWith("B") 
def c2(s: String) = s.length == 2 

println(l1.find(s => c1(s) || c2(s))) 
println(l2.find(s => c1(s) || c2(s))) 

risultato è:

Some(B) 
Some(AA) 

Per il caso L1 mi piacerebbe avere qualche valore di ritorno (una stringa per esempio) che indica che c1 è stata soddisfatta (c2 per la l2 caso). Una possibile soluzione potrebbe essere quella di definire una var prima del test e impostarla all'interno delle funzioni c1 e c2, ma mi piacerebbe trovare una soluzione più "di stile funzionale", magari qualcosa che restituisca una tupla come: (elemento trovato, condizione soddisfatto).

Grazie in anticipo per l'aiuto

risposta

9

farei questo:

Scala 2.8:

def find2p[T](l: List[T], p1: T => Boolean, p2: T => Boolean) = 
    l.view.map(el => (el, p1(el), p2(el))).find(t => t._2 || t._3) 

Scala 2.7:

def find2p[T](l: List[T], p1: T => Boolean, p2: T => Boolean) = 
    l.projection.map(el => (el, p1(el), p2(el))).find(t => t._2 || t._3) 

Il view/projection assicura che la mappatura sarà fatto on-demand, invece di essere applicato all'intera lista.

+0

Generalizzato a un elenco di predicati 'def findPredsOr [T] (l: List [T], ps: List [T => Boolean]): Option [(T , List [Boolean])] = l.view.map (el => (el, ps.map (_. Apply (el)))). Find (t => t._2.contains (true)) ' – retronym

+0

Ottima soluzione. Grazie per farmi scoprire la vista/proiezione, mi sembra molto utile! –

3
def find[T](l1 : List[T], c1 : T => Boolean, c2 : T => Boolean) = ((None : Option[(String, T)]) /: l1)((l, n) => l match { 
    case x : Some[_] => l 
    case x if c1(n) => Some("c1", n) 
    case x if c2(n) => Some("c2", n) 
    case _ => None 
}) 

scala> find(l1, c1, c2) 
res2: Option[(String, java.lang.String)] = Some((c1,B)) 

scala> find(l2, c1, c2) 
res3: Option[(String, java.lang.String)] = Some((c2,AA)) 

A seconda delle esigenze si potrebbe avere un parametro di mappa [T => booleana, String] per le stringhe di etichette per tornare: def find[T](l1 : List[T], fs : Map[T => Boolean, String]) o definire i propri operatori.

Questo valuterà l'intera lista in cui trova la ricerca per il primo elemento trovato.

1

Ecco una variante della (e) risposta (e) di Daniel.

Se si desidera solo il predicato (da una lista) che è riuscito, quindi è possibile utilizzare

def findP[T](list: Iterable[T], preds: Iterable[T=>Boolean]) = { 
    list.view.map(x => (x , preds.find(_(x)))).find(_._2.isDefined) 
} 

In alternativa, è possibile utilizzare un elenco dei predicati con nome:

def findP[T](list: Iterable[T],preds: Iterable[(T=>Boolean,String)]) = { 
    list.view.map(x => (x , preds.find(_._1(x)))).find(_._2.isDefined) 
} 

scala> findP(
    | List(1,2,3,4,5,6), 
    | List(((i:Int)=>i>4,"Fred") , ((i:Int)=>(i%6)==0,"Barney")) 
    |) 
res2: Option[(Int, Option[((Int) => Boolean, String)])] = 
    Some((5,Some((<function1>,Fred)))) 

Il risultato un po 'disordinato, ma può essere scartato abbastanza facilmente da dare esattamente quello che hai chiesto:

def findP[T](list: Iterable[T],preds: Iterable[(T=>Boolean,String)]) = { 
    list.view.map(x => (x , preds.find(_._1(x)))).find(_._2.isDefined) match { 
    case Some((i,Some((_,s)))) => Some((i,s)) 
    case _ => None 
    } 
} 

(Questo è il codice per 2.8; passare "vista" a "proiezione" per 2.7.)

Problemi correlati