Scala 例 パターンマッチ pattern matching
Scalaの便利機能、パターンマッチを試してみる。パターンマッチは、条件分岐に使う。
Scalaのパターンマッチ、"match-case"は、Javaの"switch-case"と見た目はちょっと似ているけれど、もうまったくの別物!パターンマッチは、HaskellやF#にもでてきた。
おなじみのif-elseを差し置いて、いかにも使いたくなる例ってなんだろうな。
基本
matchとcase
"match-case"は、下のような書き方をする。
これは、Javaの"switch-case"とたいして違わない例
scala> def assignStringToNumber(x: Int): String = x match { | case 1 => "a" | case 2 => "b" | case 3 => "c" | case _ => "others" | } assignStringToNumber: (x: Int)String scala> assignStringToNumber(1) res5: String = a scala> assignStringToNumber(4) res7: String = others
Scalaのパターンマッチ、"match-case"は、Int型以外にも、さまざまな型で、利用できる。
したは、Tupleに利用している。
scala> 1 to 100 map { | e => ( e % 3, e % 5) match { | case (0, 0) => "FizzBuzz" | case (0, _) => "Fizz" | case (_, 0) => "Buzz" | case _ => "" | } | } foreach { println }
ガード条件
caseに指定する値について、ifで条件付けした値を指定することができる。
これをつかって、ディレクトリを再帰的に検索するコードを書いてみる。
package myapp.search import java.io.File object Main { def main(args: Array[String]): Unit = { DirFinder(new File("test-data/myapp/search/Main")).find().foreach { println(_) }; } } class DirFinder(val rootDir: File) { def find(): List[File] = findIn(rootDir) def findIn(file: File): List[File] = file match { case file if file.isFile() => List(file) case file if file.isDirectory() => file :: file.listFiles().foldLeft(List[File]()){ _ ::: findIn(_) } case _ => List[File]() } } object DirFinder { def apply(file: File): DirFinder = { new DirFinder(file); } }
実行
test-data\myapp\search\Main test-data\myapp\search\Main\1a test-data\myapp\search\Main\1b test-data\myapp\search\Main\1b\2a test-data\myapp\search\Main\1b\2b test-data\myapp\search\Main\1b\2b\3c test-data\myapp\search\Main\1b\2b\3c\4p.txt test-data\myapp\search\Main\1b\2p.txt test-data\myapp\search\Main\1p.txt
F:\Data\Eclipse\workspaces\Scala\ScalaNote\test-data\myapp\search>tree /F Main フォルダ パスの一覧 ボリューム シリアル番号は 781F-C3ED です F:\DATA\ECLIPSE\WORKSPACES\SCALA\SCALANOTE\TEST-DATA\MYAPP\SEARCH\MAIN │ 1p.txt │ ├─1a └─1b ├─2a ├─2b │ └─3c │ 4p.txt │ └─2p.txt
書いてはみたものの、Scalaでも、これは、プチめんどくさいな。
Javaでも、パッと一行ですむ方法はなくて、やっぱり再帰で、やるんだろうけど、再帰の便利APIがない分、割り増しでめんどうくさそうだ。
Commons IO http://commons.apache.org/io/api-release/org/apache/commons/io/FileUtils.htmlのlistFilesメソッドとかなのかなあ。
Javaのライブラリは、Scalaからも使うことができる。ためしにやってみよう。
package myapp.search import java.io.File import org.apache.commons.io.filefilter.IOFileFilter import org.apache.commons.io.FileUtils import org.apache.commons.io.filefilter.DirectoryFileFilter import org.apache.commons.io.filefilter.TrueFileFilter import org.apache.commons.io.filefilter.FileFilterUtils import scala.collection.JavaConverters._ object Main2 { def main(args: Array[String]): Unit = { val fileFilter = FileFilterUtils.trueFileFilter val dirFilter = FileFilterUtils.trueFileFilter FileUtils.listFiles(new File("test-data/myapp/search/Main"), fileFilter, dirFilter).asScala.foreach { println(_) } } }
Scala Standard Library 2.12.8 - scala.collection.JavaConvertersという便利クラスがあり、asScalaメソッドで、Javaのコレクション系のオブジェクトをScalaのコレクション系のオブジェクトに変換できるようだ。
実行
test-data\myapp\search\Main\1b\2b\3c\4p.txt test-data\myapp\search\Main\1p.txt
実行したら、ディレクトリは抽出されなくて、ファイルしか抽出されないな。パターンマッチを試しているのに、またはなしがそれすぎるから、もういいや。
applyとunapply
パターンマッチいろいろ
Scalaのパターンマッチ、"match-case"は、さまざまオブジェクトに利用することができる。
コンストラクタ
scala> case class Person(val name: String, val age: Int) defined class Person scala> val list = List(Person("a", 10), Person("b", 20), Person("c", 30)) list: List[Person] = List(Person(a,10), Person(b,20), Person(c,30)) scala> list filter { e => e match { | case Person(_, x) if x >= 20 => true | case _ => false | } | } res10: List[Person] = List(Person(b,20), Person(c,30))
List
先頭の要素と残りの要素
scala> def sum(list: List[Int]): Int = list match { | case Nil => 0 | case x :: xs => x + sum(xs) | } sum: (list: List[Int])Int scala> sum(List(1,2,3,4)) res3: Int = 10
scala> def sum(list: List[Int]): Int = list match { | case List() => 0 | case List(x,xs@_*) => x + sum(xs) | } <console>:10: error: type mismatch; found : Seq[Int] required: List[Int] case List(x,xs@_*) => x + sum(xs) scala> def sum(list: List[Int]): Int = list match { | case List() => 0 | case List(x,xs@_*) => x + sum(xs.toList) | } sum: (list: List[Int])Int scala> sum(List(1,2,3,4)) res6: Int = 10
Tuple(タプル)
List((1, "a"), (2, "b"), (3, "c"), (4, "d")) foreach { _ match { case (n, s) if n == 1 => println(n + " " + s) case (2, s) => println(2 + " " + s) case (n, s@"c") => println(n + " " + s) case _ => } }
タプルとは特に関係ないけれど、@は、パターンマッチの条件を指定しつつ変数を割り当てたいときに使うようだ。
1 a 2 b 3 c
正規表現
scala> val p = "([A-Z])".r p: scala.util.matching.Regex = ([A-Z]) scala> "abCdEf".toList.map { e => e.toString }.map { e => e match { | case p(c) => "_" + c | case _ => e.toUpperCase | }} mkString res68: String = AB_CD_EF
caseのところで、"([A-Z])".rを定義するとエラーになってしまって、だめだった。
型
Scalaのcaseは、型で場合分けができる。
Javaでも、型での場合分け、instanceofのをつかっての場合分けは、妙に苦しさを感じながらたまにやるな。
case class SomethingDto(val n:Int, val s:String) List(1, "b", new SomethingDto(3, "c"), (4, "d")) foreach { _ match { case n: Int => println(n) case s: String => println(s) case o: SomethingDto => println(o) case t: Tuple2[Int, String] => println(t) case _ => } }
1 b SomethingDto(3,c) (4,d)
Extractor 抽出子
Scalaのパターンマッチ、"match-case"のcaseの裏では、unapplyメソッドが実行される。
複雑なパターンマッチをunapplyメソッドとして、自分で定義しておくことができる。こういうのをextractor 抽出子という。
A Tour of Scala: Extractor Objects | The Scala Programming Language