一般に、Seq
で特定の条件を満たす最初の要素を見つける方法は?
たとえば、使用可能な日付形式のリストがあり、最初の1つの形式の解析結果を検索して、日付文字列を解析できます。
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
?を使用)
少なくとも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で導入されました。
view
は、値を遅延計算するコレクションの一種です。 Try
内のコードを、定義されている最初のアイテムを見つけるために必要なコレクション内のアイテムと同じ数だけに適用します。最初のformat
が文字列に適用される場合、残りの形式を文字列に適用しようとしません。
シーケンスには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)
}
または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)
これにより、不必要な評価が防止されます。
formats.collectFirst{ case format if Try(format.parse(str)).isSuccess => format.parse(str) }
parse
メソッドの評価の数は、試行回数+ 1です。
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 }
}
Findメソッドを もしあれば述語に一致する最初の要素のOptionを返す として使用します:
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)
ストリームの場合、実際には問題ではないことに注意してください。また、たとえばIntelliJを使用している場合は、次のことが推奨されます。
FilterとheadOptionをfindに置き換えます。
前:
seq.filter(p).headOption
後:
seq.find(p)
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
はスレッドセーフではないため、上記のコードもスレッドセーフではありません。
末尾再帰を使用する方がはるかに優れており、これまでのところここで提供されている最も効率的なソリューションだと思います。
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)
幸運を祈ります!