Подтвердить что ты не робот

Найти первый элемент, удовлетворяющий условию X в Seq

Как правило, как найти первый элемент, удовлетворяющий определенному условию в Seq?

Например, у меня есть список возможных форматов даты, и я хочу найти проанализированный результат первого формата, который может проанализировать мою строку даты.

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

Неплохо. Но это немного уродливо. 2. он сделал некоторую ненужную работу (попробовал форматы "MM yyyy" и "MM, yyyy"). Возможно, есть более элегантный и идиоматический путь? (используя Iterator?)

4b9b3361

Ответ 1

Если вы уверены, что по крайней мере один формат будет успешным:

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

Если вы хотите быть немного безопаснее:

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

Try был введен в Scala 2.10.

A view - это тип коллекции, который вычисляет значения лениво. Он будет применять код внутри Try только к такому количеству элементов в коллекции, который необходим, чтобы найти первый, который определен. Если первый format применяется к строке, то он не будет пытаться применить остальные форматы к строке.

Ответ 2

Вы должны использовать метод find для последовательностей. Обычно вам следует использовать встроенные методы, поскольку они могут быть оптимизированы для определенной последовательности.

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

То есть, чтобы вернуть первый SimpleDateFormat, который соответствует:

 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])

Чтобы вернуть обработанную первую дату:

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)
}

или используйте ленивую коллекцию:

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)

Ответ 3

Это предотвращает ненужные оценки.

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

Число оценок метода parse - количество попыток + 1.

Ответ 4

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)

Кстати, поскольку SimpleDateFormat не является потокобезопасным, это означает, что приведенный выше код не является потокобезопасным!

Ответ 5

Такая же версия с Scala Extractor и lazyness:

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 }
}