在Seq中找到满足条件X的第一个元素

Lai*_*uan 36 collections scala

一般来说,如何在?中找到满足一定条件的第一个元素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
Run Code Online (Sandbox Code Playgroud)

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

vit*_*lii 23

你应该find在序列上使用方法.通常,您应该更喜欢内置方法,因为它们可能针对特定序列进行了优化.

Console println List(1,2,3,4,5).find( _ == 5)
res: Some(5)
Run Code Online (Sandbox Code Playgroud)

也就是说,要返回匹配的第一个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)
Run Code Online (Sandbox Code Playgroud)

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

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)
}
Run Code Online (Sandbox Code Playgroud)

或使用惰性集合:

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)
Run Code Online (Sandbox Code Playgroud)


Inf*_*ity 17

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

formats.view.map{format => Try(format.parse(str)).toOption}.filter(_.isDefined).head
Run Code Online (Sandbox Code Playgroud)

如果你想要更安全一点:

formats.view.map{format => Try(format.parse(str)).toOption}.find(_.isDefined)
Run Code Online (Sandbox Code Playgroud)

Try 在Scala 2.10中引入.

A view是一种懒惰计算值的集合.它将Try仅在集合中的代码应用于查找定义的第一个项目所需的项目.如果第一个format应用于字符串,则它不会尝试将其余格式应用于字符串.

  • 这个答案有两种反模式:i)在Try中抛出的意外异常会丢失,导致它隐藏错误并返回错误的答案(例如,如果列表是一个数据库的视图怎么办?)ii)过滤器构造一个临时的列表并且还要求访问所有元素,即使只需要第一个元素.它在时间上不必要地昂贵,但特别是在记忆中. (9认同)

tir*_*ran 10

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

formats.collectFirst{ case format if Try(format.parse(str)).isSuccess => format.parse(str) } 
Run Code Online (Sandbox Code Playgroud)

parse方法的评估次数是尝试次数+ 1.


tei*_*tel 5

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

formats.find(str => Try(format.parse(str)).isSuccess)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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

用 find 替换 filter 和 headOption。
前:

seq.filter(p).headOption  
Run Code Online (Sandbox Code Playgroud)

后:

seq.find(p)
Run Code Online (Sandbox Code Playgroud)