2013-06-02 8 views
21

Generalmente, come trovare il primo elemento che soddisfa determinate condizioni in un Seq?Trova il primo elemento che soddisfa la condizione X in un Seq

Ad esempio, ho un elenco di possibili formati di data, e voglio trovare il risultato analizzato del primo formato in grado di analizzare la mia stringa di data.

val str = "1903 January" 
val formats = List("MMM yyyy", "yyyy MMM", "MM yyyy", "MM, yyyy") 
    .map(new SimpleDateFormat(_)) 
formats.flatMap(f => {try { 
    Some(f.parse(str)) 
}catch { 
    case e: Throwable => None 
}}).head 

Non male. Ma 1. è un po 'brutto. 2. ha fatto del lavoro non necessario (provato i formati "MM yyyy" e "MM, yyyy"). Forse c'è un modo più elegante e idiomatico? (usando Iterator?)

+0

Usa 'metodo find' di' Seq' – Kakaji

risposta

13

Se si è sicuri almeno un formato volontà avrà successo:

formats.view.map{format => Try(format.parse(str)).toOption}.filter(_.isDefined).head 

Se si vuole essere un po 'più sicuro:

formats.view.map{format => Try(format.parse(str)).toOption}.find(_.isDefined) 

Try è stato introdotto nel Scala 2.10.

A view è un tipo di raccolta che calcola i valori pigramente. Applicherà il codice all'interno dello Try solo al numero di elementi della raccolta necessario per trovare il primo definito. Se il primo format si applica alla stringa, non tenterà di applicare i formati rimanenti alla stringa.

+3

Questa risposta ha due anti-motivi: i) le eccezioni impreviste lanciate nella Prova andranno perse, causando di nascondere i bug e restituire risposte errate (ad esempio, se l'elenco se una vista di un database?) ii) filtro costruisce un elenco temporaneo e richiede anche che TUTTI gli elementi siano visitati anche se solo il primo è richiesto. È inutilmente costoso nel tempo, ma soprattutto anche nella memoria. – user48956

12

È necessario utilizzare il metodo find sulle sequenze. Generalmente si dovrebbero preferire metodi built-in, perché potrebbero essere ottimizzati per una sequenza specifica.

Console println List(1,2,3,4,5).find(_ == 5) 
res: Some(5) 

Cioè, per tornare prima SimpleDateFormat che corrispondono:

val str = "1903 January" 
val formats = List("MMM yyyy", "yyyy MMM", "MM yyyy", "MM, yyyy") 
    .map(new SimpleDateFormat(_)) 
formats.find { sdf => 
     sdf.parse(str, new ParsePosition(0)) != null 
} 

res: Some([email protected]) 

Per tornare prima data che è in fase di elaborazione:

val str = "1903 January" 
val formats = List("MMM yyyy", "yyyy MMM", "MM yyyy", "MM, yyyy").map(new SimpleDateFormat(_)) 
val result = formats.collectFirst { 
    case sdf if sdf.parse(str, new ParsePosition(0)) != null => sdf.parse(str) 
} 

o utilizzare collezione pigro:

val str = "1903 January" 
val formats = List("MMM yyyy", "yyyy MMM", "MM yyyy", "MM, yyyy").map(new SimpleDateFormat(_)) 
formats.toStream.flatMap { sdf => 
    Option(sdf.parse(str, new ParsePosition(0))) 
}.headOption 

res: Some(Thu Jan 01 00:00:00 EET 1903) 
+0

Hai il coraggio di fornire un esempio operativo completo? * modificato * Intendo quello con le date. –

+0

Ho fornito un esempio operativo completo. Guy sta chiedendo come in genere trovi il primo elemento in una sequenza. – vitalii

+0

ecco qui, quello con le date – vitalii

2
scala> def parseOpt(fmt: SimpleDateFormat)(str: String): Option[Date] = 
    | Option(fmt.parse(str, new ParsePosition(0))) 
tryParse: (str: String, fmt: java.text.SimpleDateFormat)Option[java.util.Date] 

scala> formats.view.flatMap(parseOpt(fmt)).headOption 
res0: Option[java.util.Date] = Some(Thu Jan 01 00:00:00 GMT 1903) 

proposito, poiché SimpleDateFormat è non-thread-safe, che significa che il codice di cui sopra non è thread-safe sia!

3

Ciò impedisce le valutazioni non necessarie.

formats.collectFirst{ case format if Try(format.parse(str)).isSuccess => format.parse(str) } 

Il numero di valutazioni del metodo parse è il numero di tentativi + 1.

2

stessa versione con Scala Extractor e pigrizia:

case class ParseSpec(dateString: String, formatter:DateTimeFormatter) 


object Parsed { 
    def unapply(parsableDate: ParseSpec): Option[LocalDate] = Try(
    LocalDate.parse(parsableDate.dateString, parsableDate.formatter) 
).toOption 
} 


private def parseDate(dateString: String): Option[LocalDate] = { 
    formats.view. 
    map(ParseSpec(dateString, _)). 
    collectFirst { case Parsed(date: LocalDate) => date } 
} 
Problemi correlati