これは、Scalaで正規表現とパターンマッチを使って文字列を抽出して操作するの続編です。
以前のエントリーを社内のチャットで共有(大喜利)したところ、何名の方から別の書き方を教えてもらいました。
秒に変換する処理を別関数化して処理する方法
これは秒に変換する処理を関数としてまとめてあります。重複がなくなり、かつ処理に名前が付いたので以前のもとのよりわかりやすいんじゃないかと思います。正規表現も以前のと比べると?:
(前方参照を行わない正規表現グループ)を使って、本当に必要な時分秒の3つだけを抽出する形で書かれています。
val youtubeDurationPattern = "PT(?:([0-9])+H)?(?:([0-9])+M)?(?:([0-9])+S)?".r
def toSec(s: String, unit: Int) = Option(s).map { _.toInt * unit }
def duration(s: String) = s match {
case youtubeDurationPattern(h, m, s) =>
(toSec(h, 3600) ++ toSec(m, 60) ++ toSec(s, 1)).sum
}
println(duration("PT2H7M56S"))
println(duration("PT7M56S"))
抽出子を利用する方法
これが一番Scalaっぽいかも。Timeで定義しているunapplyメソッドを使って、値をInt型として抽出してそのままmatchのcase節に適用できています。Timeを定義するのが少し面倒ですが、最終的なコードは直感的で可読性が高いと思いました。
object Time{
def unapply(s: String): Option[Int] = Some(if(s == null) 0 else s.toInt)
}
object YoutubeDuration {
val youtubeDurationPattern = "PT(?:([0-9])+H)?(?:([0-9])+M)?(?:([0-9])+S)?".r
def unapply(st: String):Option[Int] = {
st match {
case youtubeDurationPattern(Time(h), Time(m), Time(s)) =>
Some(h * 3600 + m * 60 + s)
case _ => None
}
}
}
val YoutubeDuration(s) = "PT2H7M56S"
おまけ:Java版
Java8のOptional.ofNullable
とorElse
を使ったパターンです。Javaのif文では値が返せないので、別のメソッドや集計用の変数を使わずにやるならこんな感じになると思います。
public class YT {
private final static Pattern YOUTUBE_DURATION_PATTERN
= Pattern.compile("PT(([0-9]+)H)?(([0-9]+)M)?(([0-9]+)S)?");
public long duration(String s) {
final Matcher matcher = YOUTUBE_DURATION_PATTERN.matcher(s);
if (matcher.find()) {
return
Optional.ofNullable(matcher.group(2))
.map(it -> Long.parseLong(it)).orElse(0L) * 3600L +
Optional.ofNullable(matcher.group(4))
.map(it -> Long.parseLong(it)).orElse(0L) * 60L +
Optional.ofNullable(matcher.group(6))
.map(it -> Long.parseLong(it)).orElse(0L);
}
return 0L;
}
@Test
public void xxx() {
System.out.println(duration("PT2H7M56S"));
System.out.println(duration("PT7M56S"));
}
}
結論
社内大喜利おもしろい!