46

一般来说,如何在 a 中找到满足某个条件的第一个元素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

不错。但是1.有点丑。2. 它做了一些不必要的工作(尝试"MM yyyy""MM, yyyy"格式化)。也许有更优雅和惯用的方式?(使用Iterator?)

4

8 回答 8

33

您应该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(java.text.SimpleDateFormat@ef736ccd)

要返回已处理的第一个日期:

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)
于 2013-06-02T14:23:43.960 回答
25

如果您确信至少一种格式会成功:

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 中引入。

Aview是一种惰性计算值的集合。它将仅将 中的代码应用于Try集合中的尽可能多的项目,以找到定义的第一个项目。如果第一个format应用于字符串,那么它不会尝试将其余格式应用于字符串。

于 2013-06-02T14:48:06.457 回答
10

这可以防止不必要的评估。

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

parse方法的评估次数为尝试次数 + 1。

于 2013-06-03T04:29:04.730 回答
5

只需使用 find 方法,因为它返回第一个元素匹配谓词的选项(如果有)

formats.find(str => Try(format.parse(str)).isSuccess)

此外,执行在第一个匹配时停止,因此您不会在选择第一个之前尝试解析集合中的每个元素。这是一个例子:

def isSuccess(t: Int) = {
  println(s"Testing $t")
  Math.floorMod(t, 3) == 0
}
isSuccess: isSuccess[](val t: Int) => Boolean

List(10, 20, 30, 40, 50, 60, 70, 80, 90).filter(isSuccess).headOption
Testing 10
Testing 20
Testing 30
Testing 40
Testing 50
Testing 60
Testing 70
Testing 80
Testing 90
res1: Option[Int] = Some(30)

Stream(10, 20, 30, 40, 50, 60, 70, 80, 90).filter(isSuccess).headOption
Testing 10
Testing 20
Testing 30
res2: Option[Int] = Some(30)

List(10, 20, 30, 40, 50, 60, 70, 80, 90).find(isSuccess)
Testing 10
Testing 20
Testing 30
res0: Option[Int] = Some(30)

请注意,对于 Stream 这并不重要。此外,例如,如果您使用 IntelliJ,它会建议您:

用 find 替换 filter 和 headOption。
前:

seq.filter(p).headOption  

后:

seq.find(p)
于 2018-08-03T08:08:17.913 回答
3

与 Scala Extractor 和惰性相同的版本:

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 }
}
于 2017-04-28T09:21:53.713 回答
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)

顺便说一句,由于SimpleDateFormat是非线程安全的,这意味着上面的代码也不是线程安全的!

于 2013-06-02T14:50:38.187 回答
2

我认为使用尾递归要好得多,也是迄今为止这里提供的最有效的解决方案:

implicit class ExtendedIterable[T](iterable: Iterable[T]) {
  def findFirst(predicate: (T) => Boolean): Option[T] = {
    @tailrec
    def findFirstInternal(remainingItems: Iterable[T]): Option[T] = {
      if (remainingItems.nonEmpty)
        if (predicate(remainingItems.head))
          Some(remainingItems.head)
        else
          findFirstInternal(remainingItems.tail)
      else
        None
    }
    findFirstInternal(iterable)
  }
}

它允许您在导入上述类后,在需要的任何地方简单地执行以下操作:

formats.findFirst(format => Try(format.parse(str)).isSuccess)

祝你好运!

于 2018-04-03T07:32:34.190 回答
0

使用 org.joda.time:

定义

def getBaseLocalFromFormats[T <: BaseLocal](
                        value: String, 
                        validPatterns: Seq[String], 
                        parse: (String, String)  => T) : Option[T] = {
    validPatterns.view.map(p => Try{ parse(value, p) }).find(_.isSuccess).map(_.get)
  }

用法

getBaseLocalFromFormats( 
                "01/10/1980 16:08:22",
                List("dd/MM/yyyy HH:mm:ss"),
                (v,p) => DateTimeFormat.forPattern(p).parseLocalDateTime(v))
getBaseLocalFromFormats( 
                "01/10/1980",
                List("dd/MM/yyyy",  "dd-MM-yyyy", "yyyy-MM-dd"),
                (v,p) => DateTimeFormat.forPattern(p).parseLocalDate(v))
于 2020-02-20T09:14:12.690 回答