在 Seq 中找到满足条件 X 的第一个元素
Posted
技术标签:
【中文标题】在 Seq 中找到满足条件 X 的第一个元素【英文标题】:Find the first element that satisfies condition X in a Seq 【发布时间】:2013-05-28 19:52:02 【问题描述】:一般来说,如何在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
?)
【问题讨论】:
使用find
方法Seq
不一样,bu相关:如果你已经有一个X => Option[R]
函数(它决定了函数的适用性+确实返回了实际的应用程序/映射),你可以轻松地将该函数转换为PartialFunction
,因此可以将它用于collectFirst
中的序列。请参阅:Function.unlift
(scala-lang.org/api/2.11.x/#scala.Function$)。 -- 更多信息:***.com/questions/1908295/…
【参考方案1】:
使用 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))
【讨论】:
【参考方案2】:只需使用 find 方法作为it returns an Option of the first element matching predicate if any :
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)
【讨论】:
【参考方案3】:我认为使用尾递归要好得多,也是迄今为止这里提供的最有效的解决方案:
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)
祝你好运!
【讨论】:
【参考方案4】:与 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
【讨论】:
【参考方案5】:如果您确信至少有一个格式会成功:
formats.view.mapformat => Try(format.parse(str)).toOption.filter(_.isDefined).head
如果你想更安全一点:
formats.view.mapformat => Try(format.parse(str)).toOption.find(_.isDefined)
Try
是在 Scala 2.10 中引入的。
view
是一种惰性计算值的集合。它将把Try
中的代码应用到集合中的尽可能多的项目,以找到第一个定义的项目。如果第一个format
应用于字符串,则不会尝试将其余格式应用于字符串。
【讨论】:
这个答案有两种反模式:i) Try 中抛出的意外异常将丢失,导致它隐藏错误并返回不正确的答案(例如,如果列表如果数据库视图?) ii) filter 构造一个临时列表,并且即使只需要第一个元素,也需要访问所有元素。它在时间上不必要地昂贵,尤其是在内存方面。【参考方案6】:这可以防止不必要的评估。
formats.collectFirst case format if Try(format.parse(str)).isSuccess => format.parse(str)
parse
方法的求值次数为尝试次数+1。
【讨论】:
【参考方案7】: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
是非线程安全的,也就是说上面的代码也不是线程安全的!
【讨论】:
【参考方案8】:您应该在序列上使用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)
【讨论】:
我确实提供了完整的工作示例。 Guy 正在询问如何找到序列中的第一个元素。 给你,带日期的那个 这不是 OP 要求的;您将返回第一个解析它的SimpleDateFormat
,而不是返回第一个成功的解析结果。以上是关于在 Seq 中找到满足条件 X 的第一个元素的主要内容,如果未能解决你的问题,请参考以下文章