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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16883591/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:21:35  来源:igfitidea点击:

Find the first element that satisfies condition X in a Seq

scalacollections

提问by Lai Yu-Hsuan

Generally, how to find the first element satisfying certain condition in a Seq?

通常,如何在 a 中找到满足特定条件的第一个元素Seq

For example, I have a list of possible date format, and I want to find the parsed result of first one format can parse my date string.

例如,我有一个可能的日期格式列表,我想找到第一种格式的解析结果可以解析我的日期字符串。

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

Not bad. But 1. it's a little ugly. 2. it did some unnecessary work(tried "MM yyyy"and "MM, yyyy"formats). Perhaps there is more elegant and idiomatic way? (using Iterator?)

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

采纳答案by Infinity

If you're confident at least one will format will succeed:

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

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

If you want to be a bit safer:

如果你想更安全一点:

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

Trywas introduced in Scala 2.10.

Try是在 Scala 2.10 中引入的。

A viewis a type of collection that computes values lazily. It will apply the code within the Tryto only as many items in the collection as is necessary to find the first one that is defined. If the first formatapplies to the string, then it won't try to apply the remaining formats to the string.

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

回答by vitalii

You should use findmethod on sequences. Generally you should prefer built-in methods, because they might be optimised for a specific sequence.

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

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

That is, to return first SimpleDateFormat that match:

也就是说,要返回第一个匹配的 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)

To return first date that has being processed:

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

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

or use lazy collection:

或使用惰性收集

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)

回答by tiran

This prevents the unnecessary evaluations.

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

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

The number of evaluations of the parsemethod is number of tries + 1.

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

回答by teikitel

Just use find method as it returns an Option of the first element matching predicate if any:

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

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

Moreover, execution stops at the first match, so that you don't try parsing every element of your set before picking the first one. Here is an example :

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

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)

Note that for Stream it doesn't really matter. Also if you are using IntelliJ for example it will suggest you :

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

Replace filter and headOption with find.
Before:

用 find 替换 filter 和 headOption。
前:

seq.filter(p).headOption  

After:

后:

seq.find(p)

回答by Laurent Valdes

Same version with Scala Extractor and lazyness:

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

回答by Ben James

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)

By the way, since SimpleDateFormatis non-thread-safe, that means the above code is not thread-safe either!

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

回答by Ori Cohen

I think using tail recursion is much better and by far the most efficient solution offered here so far:

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

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

It would allow you upon importing the above class to simply do the something like the following wherever you need to:

它允许您在导入上述类时只需在需要的地方执行以下操作:

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

Best of luck!

祝你好运!

回答by bcoromina

Using org.joda.time:

使用 org.joda.time:

Definition:

定义

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

Usage:

用法

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