Scala 例 List
REPLをつかって、ぼちぼち試してみる。
関数型といわれるプログラミング言語はとにかくListの処理が充実している。
Scalaでも、それは同じ。
とりあえずの相棒は、Scala Standard Library 2.12.8 - scala.collection.immutable.Listこのページ
もっとも、Scalaのライブラリの階層を見ると、ListよりSeqをつかおうということらしい。Effective Scala(Effective Scala)
リストの生成と要素の追加
scala> Nil res39: scala.collection.immutable.Nil.type = List() scala> 1::Nil res40: List[Int] = List(1) scala> 1::2::Nil res41: List[Int] = List(1, 2)
いきなり、とっつきにくいかもしれなけいど、ScalaっぽいListのつくりかたはこれだろうか。::で、Listの先頭に要素を追加する。
このリストの構造は、Javaだったら、java.util.LinkedListが、いちばん近いようだ。
Common Lispだったら、consというやつか。
- は、Listについての、パターンマッチという機能でも使う。
実際に、Listをつくるメソッドを使ってみると、
scala> val list = List("a", "b") list: List[java.lang.String] = List(a, b) scala> "c" :: list res11: List[java.lang.String] = List(c, a, b) scala> "d" :: "c" :: list res12: List[java.lang.String] = List(d, c, a, b) scala> list :+ "c" res2: List[java.lang.String] = List(a, b, c) scala> "c" +: list res4: List[java.lang.String] = List(c, a, b) scala> val list2 = List("c", "d") list2: List[java.lang.String] = List(c, d) scala> list ::: list2 res14: List[java.lang.String] = List(a, b, c, d) scala> list ++ list2 res19: List[java.lang.String] = List(a, b, c, d) scala> list res20: List[java.lang.String] = List(a, b) scala> list2 res21: List[java.lang.String] = List(c, d)
うえの場合の要素の追加では、もとのListは変化せず、もとのListに対して要素を追加した、別のListができる。JavaのArrayListなんかとは、ちがうところ。それが、パッケージ名にもあるimmutableということ。mutableな、ListBufferもある。
java.util.ArrayListと、そのaddメソッドに相当するのは、Scala Standard Library 2.12.8 - scala.collection.mutable.ListBufferの+=メソッドになるんだろうか。
はなしがListからそれるけど
はなしがListからそれるけど、上の例からScalaについていくつかまとめておくと、
- Listは、実際にはscala.collection.immutable.ListScala Standard Library 2.12.8 - scala.collection.immutable.Listのこと。
- Scalaでは、http://www.scala-lang.org/api/current/scala/package.htmlパッケージのクラスは自動でimportされる。そして、Listは、scalaパッケージの下に、type List(typeで型を定義できる)として定義されているため、importされる。Javaでは、java.langパッケージのクラスは自動でimportされるのに似ている。
- Scala Standard Library 2.12.8 - scala.Predef(ソースコードPredef.scala in scala/tags/R_2_9_0_1/src/library/scala – Scala)もscalaパッケージだから自動でimportされ、結果として、Predef内部にある定義も自動で効くようになる。
- Scalaでは、http://www.scala-lang.org/api/current/scala/package.htmlパッケージのクラスは自動でimportされる。そして、Listは、scalaパッケージの下に、type List(typeで型を定義できる)として定義されているため、importされる。Javaでは、java.langパッケージのクラスは自動でimportされるのに似ている。
- :+も+:もScalaではメソッド
- メソッド名に記号を含むのはあり。
- Javaではメソッドを実行するときかならず.をつけるが、Scalaではメソッドを呼び出すとき.をつけなくても良い場合がある。()も必須でなく、場合によっては、省略できる。
- 引数がない場合
- 引数がひとつの場合
- scala> "c" +: list
- Scalaではメソッド名の最後が:の場合は、メソッドの引数とオブジェクトを入れ替えて、メソッド呼び出しを記述できる。
- a do: bという記述のメソッド呼び出しは、実際はb.do:(a)の意味になる。
- list自体は、はじめに生成したオブジェクトまま変化していない。リストから別のリストを生成するメソッドということ。
- Listとは、またぜんぜんべつのはなしだけど、Scalaのvalは、Javaのfinalのことで変更不可。
おなじみのメソッドでListクラスを調べてみる
ScalaのREPLを活用して、おなじみのメソッドでListを調べてみる。
scala> List().getClass res4: java.lang.Class[_] = class scala.collection.immutable.Nil$ scala> List().getClass.getSuperclass res5: java.lang.Class[_ >: ?0] = class scala.collection.immutable.List scala> List() == Nil res6: Boolean = true scala> List[Int]() res3: List[Int] = List() scala> List[Int]().getClass res4: java.lang.Class[_] = class scala.collection.immutable.Nil$ scala> List(1) res5: List[Int] = List(1) scala> List(1).getClass res43: java.lang.Class[_] = class scala.collection.immutable.$colon$colon scala> List(1,2) res6: List[Int] = List(1, 2) scala> List(1,2).getClass res5: java.lang.Class[_] = class scala.collection.immutable.$colon$colon scala> List(1,2).getClass.getSuperclass res6: java.lang.Class[_ >: ?0] = class scala.collection.immutable.List scala> List(1,2).getClass.getSuperclass.getSuperclass res7: java.lang.Class[_ >: ?0] = class java.lang.Object scala> List(1,2).getClass.getInterfaces res8: Array[java.lang.Class[_]] = Array(interface scala.ScalaObject, interface scala.Product, interface scala.Serializable)
scala> List(1, 2).getClass.getMethods.map(_.getName).toList.sortWith(_ <= _).distinct.foreach { println _ } $colon$bslash $colon$colon $colon$colon$colon .... zip zipAll zipWithIndex
- Listは、けっこう複雑なクラス階層のなかにあるようだ。
- あらためてScala Standard Library 2.12.8を確認すると、extendsしたりwithしたりと実際複雑。
- $colon$colonは、::クラス
- $colon$colonは、::メソッド。::クラスだけでなく::メソッドもある。
- :の$colonのだけでなく、記号を含んだメソッドがたくさん。
- とにかくListは処理が充実して、メソッドがたくさん。
コップ本を復習しよう。。。。
なんだか:などの記号をやたらとメソッド名につかっていることはわかったからちょっとfilterして、
scala> List().getClass.getMethods.map{_.getName}.toList.sortWith{_ <= _}.distinct.filter{! _.contains("$") }.foreach { println _ }
addString
aggregate
andThen
....
zip
zipAll
zipWithIndex
なるほどね!
Listのメソッドの一覧を取得したところで、Listのメソッドとその挙動を調べる作業にもどる。
リストの生成と要素の追加のつづき
scala> var list = List("a", "b") list: List[java.lang.String] = List(a, b) scala> list :+= "c" scala> list res9: List[java.lang.String] = List(a, b, c) scala> list +:= "d" scala> list res12: List[java.lang.String] = List(d, a, b, c)
- listは、はじめに生成したListオブジェクトではなくなっている。
Listから要素を取得する
ほかの言語でもおなじみの、インデックス指定で要素をとりだすことができる。
scala> val list = List(1,2,3,4) list: List[Int] = List(1, 2, 3, 4) scala> list(0) res7: Int = 1 scala> list(3) res9: Int = 4 scala> list(4) java.lang.IndexOutOfBoundsException: 4 at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:51) at scala.collection.immutable.List.apply(List.scala:45)
おなじみのforループ
は、scalaのREPLで、例をつくっているために、行が変わったときに | がついているだけで、特別な意味はない。 |
scala> for (e <- list) { | println(e) | } 1 2 3 4
いかにもList操作のためにあるようなメソッド
scala> val list = List(1,2,3,4) list: List[Int] = List(1, 2, 3, 4) scala> list.head res0: Int = 1 scala> list.tail res1: List[Int] = List(2, 3, 4) scala> list.init res2: List[Int] = List(1, 2, 3) scala> list.last res4: Int = 4
Listのsort
なにげに、JavaのListは、sortメソッドないよな。java.util.Collections.sortとかつかう。
ScalaのListは、sortedとかがもともとついている。
scala> val list = scala.util.Random.shuffle(1 to 20 toList) list: List[Int] = List(4, 2, 12, 19, 9, 14, 8, 7, 15, 18, 20, 10, 16, 17, 1, 13, 11, 5, 3, 6) scala> list sorted res0: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) scala> list map { "%d" format _ } sorted res3: List[String] = List(1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 3, 4, 5, 6, 7, 8, 9) scala> list map { "%02d" format _ } sorted res4: List[String] = List(01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
sortBy、、、、だと?
scala> case class Person(val name: String, val age: Int) defined class Person scala> val list = List(Person("c", 2), Person("a", 3), Person("b", 1)) list: List[Person] = List(Person(c,2), Person(a,3), Person(b,1)) scala> list sortBy {_.name} res11: List[Person] = List(Person(a,3), Person(b,1), Person(c,2)) scala> list sortBy {_.age} res12: List[Person] = List(Person(b,1), Person(c,2), Person(a,3))
Scala Standard Library 2.12.8をみなおすと、SQLのselectっぽいことがいろいろできるようだ。このあたり、JavaのListより便利でいいなあ。
Listのforを書かかずにすます
自分は、Javaのプログラマー2,3年生のときにhttp://commons.apache.org/collections/api-release/org/apache/commons/collections/CollectionUtils.html(最近では、Apache Commons Functorという独立したプロダクトもあるようだ)
を見て、「えっ、リストのループって、Transfomer, Predicate, Closureの3つでなんとかなっちゃうの?ループでやりたいことって、実はこの3とおりしかないの?」ってひどく驚いたことがあった。(このClosureっていう単語だけは、あのクロージャなのかと誤解してしまいそうだが。)
そのあと、関数型言語(はじめは、Common Lisp)というものにふれて、ようやく得心がいった。「ああー、あれは、これだったのか」と
map
Listを別のListに変換する。
Listの要素ひとつひとつを、別のオブジェクトに変換して、別のListをつくる。
scala> List(1,2,3,4).map{e => "%04d".format(e)} res25: List[String] = List(0001, 0002, 0003, 0004)
この例では、要素をeという名前をつけてあつかっている。
filter
ListからListを抽出する。
こいつは、SQLのselectのイメージか。テーブルのレコード全体から、あるレコードを抽出するイメージ
scala> List(1,2,3,4).filter{_ >= 3} res18: List[Int] = List(3, 4)
この例では、要素に名前をつけず、_という便利な記号であつかっている。
foreach
Listの要素ひとつひとつについて、何らかの処理を実行する。
たとえば、for文をまわして、要素をprintlnっていうあれだ。
scala> List(1,2,3,4).foreach{println} 1 2 3 4
この例では、要素に名前をつけず、省略している。
foldLeft, foldRight
Listを再帰的に処理して、集約して、別のオブジェクトをつくりだす。
じぶんの表現がへたなのか、われながら何いってるかピンとこない。for文をつかって、リストを処理して、何か値をひとつ、取得したい場合がままあるんだよな。
foldは、プログラミングの、日本語の用語?だと「畳み込み」という表現になるらしい。
関数型言語のListの処理で、いちばんはじめに、とっつきづらさを味わったのはこいつだったとおもう。
例を見ても、動きがイメージできないし、つかいどころなんてまったくわからない。
畳み込まれていくようすをていねいにひとつひとつ式に書いて説明してくれている教科書もあるのだけれど、コップ本に描いてあるような要素と演算のツリーを思い浮かべるのが一番わかりやすいと思う。
foldLeft, foldRightのほかに、reduceLeft, reduceRightなんてのもある。
合計を計算する例
accは、「accumulator 蓄積するもの」の略なんだそうな。この手のサンプルでわりとみかける。eは、ここでは、「element Listの要素」ってことで。
scala> List(1,2,3,4).foldLeft(0){(acc, e) => acc + e} res12: Int = 10 scala> List(1,2,3,4).foldLeft(0){_ + _} res11: Int = 10 scala> List(1,2,3,4).reduceLeft{_ + _} res0: Int = 10
上の、合計を出すのをJavaでやってみる。
forを使うならば、
public class SumA { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4); System.out.println(sum(list)); } public static Integer sum(List<Integer> list) { Integer acc = 0; for (Integer e : list) { acc = acc + e; } return acc; } }
プログラマーだったらだれでも1回ぐらいは、このような、Listをループで処理して、集約して、別のオブジェクトをつくりだすというコードを書いていると思う。このような処理と、同じ結果を出す処理を、パッと書けるのがfoldということのようだ。
Javaで、foldっぽいの書いてみると、こんな感じになるのかなあ
public class SumB { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4); System.out.println(sum(list)); } private static Integer sum(List<Integer> list) { LinkedList<Integer> listCopy = new LinkedList<Integer>(list); return applyOperatorRecursively(0, listCopy, new Addition()); } //foldLeftのつもり private static <A, B> A applyOperatorRecursively(A acc, LinkedList<B> list, Operator<A, B> operator) { if (list.isEmpty()) { return acc; } else { B e = list.removeFirst(); A newAcc = operator.op(acc, e); return applyOperatorRecursively(newAcc, list, operator); } } private static interface Operator<A, B> { A op(A a, B b); } private static class Addition implements Operator<Integer, Integer> { @Override public Integer op(Integer a, Integer b) { return a + b; } } }
もっと、立派なライブラリはApache Commons Functor 1.0-SNAPSHOT APIとか、http://functionaljava.org/examples/1.5/とかあるみたいだ。
Listの大きさを求める例
scala> list.foldLeft(0){(acc, e) => acc + 1} res6: Int = 4 scala> list.size res7: Int = 4 scala> list.count { _ => true } res8: Int = 4
最大値を求める例
scala> val list = scala.util.Random.shuffle(1 to 10 toList) list: List[Int] = List(3, 9, 6, 2, 10, 8, 5, 7, 4, 1) scala> list.reduceLeft{(acc, e) => if (acc > e) acc else e } res0: Int = 10 scala> list.max res9: Int = 10
LeftとRightのちがい。
Leftは先頭から処理、Rightは後尾から処理
scala> List(1,2,3,4).foldLeft(List[Int]()){(acc, e) => e :: acc} res75: List[Int] = List(4, 3, 2, 1) scala> List(1,2,3,4).foldRight(List[Int]()){(e, acc) => e :: acc} res76: List[Int] = List(1, 2, 3, 4)
Listの、その他のメソッドと例
scala> val p = "([A-Z])".r p: scala.util.matching.Regex = ([A-Z]) scala> "abCdEf".toList res0: List[Char] = List(a, b, C, d, E, f) scala> "abCdEf".toList.map { e => e.toString }.map { e => e match { | case p(c) => "_" + c | case _ => e.toUpperCase | }} mkString res68: String = AB_CD_EF
scala> "AB_CD_EF".split("_") res1: Array[java.lang.String] = Array(AB, CD, EF) scala> "AB_CD_EF".split("_").zipWithIndex res2: Array[(java.lang.String, Int)] = Array((AB,0), (CD,1), (EF,2)) scala> "AB_CD_EF".split("_").zipWithIndex.map { e => e match { | case (h, 0) => h.toLowerCase | case (t, _) => t.toLowerCase.capitalize | }} mkString res72: String = abCdEf
Listの例じゃなくなっちゃった。Scala Standard Library 2.12.8 - scala.Arrayには、zipWithIndexメソッドはない。Scala Standard Library 2.12.8 - scala.Predefのimplicit def wrapRefArrayの効用。
ほかのList
scala.collection.mutable.ListBuffer
Scala Standard Library 2.12.8 - scala.collection.immutable.Listのようにリストに要素を追加したら新しいリストができるのでなくて、java.util.ArrayListのように、おなじリストに要素を追加したいならScala Standard Library 2.12.8 - scala.collection.mutable.ListBufferがある。
scala> val listBuffer = ListBuffer() listBuffer: scala.collection.mutable.ListBuffer[Nothing] = ListBuffer() scala> listBuffer += "a" <console>:10: error: type mismatch; found : java.lang.String("a") required: Nothing listBuffer += "a"
あら?だめだ。型を指定して、もう一度、、、、
scala> val listBuffer = ListBuffer[String]() listBuffer: scala.collection.mutable.ListBuffer[String] = ListBuffer() scala> listBuffer += "a" res12: listBuffer.type = ListBuffer(a) scala> listBuffer res13: scala.collection.mutable.ListBuffer[String] = ListBuffer(a) scala> listBuffer += "b" res14: listBuffer.type = ListBuffer(a, b) scala> listBuffer res15: scala.collection.mutable.ListBuffer[String] = ListBuffer(a, b) scala> "c" +=: listBuffer res16: listBuffer.type = ListBuffer(c, a, b) scala> listBuffer res17: scala.collection.mutable.ListBuffer[String] = ListBuffer(c, a, b)
同一のListBufferオブジェクトに対して、a,b,cが追加されているのがわかる。