foreachによるループ - Scala覚書
-
概要
foreachでどういった書き方ができるかをさらっと試してみる。
@Author mwSoft
@Date 2010/11/19
@Env Scala2.8 -
foreach その1(引数省略)
foreachはScalaの目玉商品の1つ。
下記は0~9までループする例。
def main(args: Array[String]) { // 0から9までの数字でループしながら、値をprint関数に渡す (0 to 9).foreach(print) //=> 0123456789 }
これは何をやっているかというと、まず、(0 to 9)はイテレート可能なRangeを返す。イテレート可能なものはforeachで回せる。もちろんArrayとかListとかも回せる。
foreachの引数には関数が渡せる。ここではprint関数(Javaで言うところのSystem.out.print)を引数に渡している。
上記のコードだとprint関数自体には引数は指定されてないけど(普通は、print( i )みたいに書く)、foreachでは引数を省略した場合、イテレータが回るたびに値を勝手に引数として渡してくれる。
というわけで、イテレータが10回転して、0, 1, 2...と順に数字が表示される。
ユーザが定義した関数も普通にforeachの引数に渡せる。以下はユーザが作成した関数をforeachに渡してみる例。
def main(args: Array[String]) { (0 to 4).foreach(doublePrint) //=> 02468 } def doublePrint( i: Int ) { print( i * 2 ) }
doublePrintという、受け取った引数に2を掛けて表示する関数を定義して、それをforeachで呼び出している。ので、出力結果は0, 1, 2...をそれぞれ2倍して、0, 2, 4...になっている。
-
foreach その2(変数名を指定)
foreach内での引数の渡し方は他にもいろいろある。たとえばアンダースコアがマジックワードになっていたりする。
def main(args: Array[String]) { (0 to 2).foreach( print( _ ) ) //=> 012 }
上記のようにアンスコはイテレータが持ってきた値を意味する。これで下記のような引数が2つある関数を渡す時も安心。
def main(args: Array[String]) { (0 to 2).foreach( hoge( _, 3 ) ) //=> 036 } // 引数を2つ取る関数 def hoge(i: Int, j: Int) { print( i * j ) }
アンダースコア以外にも、任意の名前を指定することもできる。
def main(args: Array[String]) { (0 to 2).foreach( i => print( i * 2 )) //=> 024 }
こんな感じでforeachの中で、「変数名 => 関数」とすると、指定した変数名でイテレータが取ってきた値を扱うことができる。記述的にはRubyのlist.each { | i | puts i }に似ている。
-
foreach その3(ブロックとスコープ)
その1とその2では、foreachの引数に定義済みの関数を渡していたけど、無名関数を渡すことでJavaみたいにfor文の中にブロックを書く風な記述もできる。
def main(args: Array[String]) { (0 to 3).foreach( { i => if( i % 3 == 0 ) print( i + "=偶数" ) else print( i + "=奇数" ) }) //=> 0=偶数1=奇数2=偶数3=奇数 }
上記のコードでは、foreachの引数に { // 処理内容 } という無名関数を渡している感じ。
尚、Scalaは引数が0個、もしくは1個の関数については、引数を括弧で囲むのも省略できる。ので、こんな感じで括弧をなくしても良い。
def main(args: Array[String]) { (0 to 3).foreach { i => if (i % 3 == 0) print(i + "=偶数") else print(i + "=奇数") } //=> 0=偶数1=奇数2=偶数3=奇数 }
上記のように書くと、JavaとかRubyのソースとそれほど変わらなく見える。でも、書き方によっては意味合いが変わって見えるので最初は混乱しそう。
あとはスコープについて。下記のようにforeachの外の変数に対して加算とかも普通にできる。
def main(args: Array[String]) { var sum = 0 (0 to 10).foreach( sum += _ ) println( sum ) //=> 55 }
この辺のスコープはJavaと比べてけっこう広い上にクロージャ万歳。
-
foreach その4(Java型を回す)
java.util.ArrayListのようなJavaのクラスを扱う場合は、そのままだとforeachで回せない。Scala2.8からは、下記のようにimport文を書けばJavaのクラスも手軽にforeachできるようになった。
import java.util.ArrayList import scala.collection.JavaConversions._ def main(args: Array[String]) { val list = new ArrayList[String]() list.foreach( print ) }
上の例では、Javaのjava.util.ArrayListをforeachしている。これはscala.collection.JavaCoversions._をimportしているから実現できる。importしてないとコンパイラにforeachなんてないぞと怒られる。
-
foreach その5(Mapを回す)
もちろんMapも普通に回せる。
def main(args: Array[String]) { val map = Map( 1 -> 10, 2 -> 20 ) map.foreach( print ) //=> (1,10)(2,20) }
ScalaのMapの中身はTupleなので、上記のように引数にはTupleが返される。
keyとvalueを引き出したい時は下記のようになる。
def main(args: Array[String]) { val map = Map( 1 -> 10, 2 -> 20 ) map.foreach( item => println( item._1 + ":" + item._2 ) ) //=> 1:10 //=> 2:20 }
でも、_1 とかで値を取り出すのってカッコ悪いよねということで、普通はcaseを使うと思う。
def main(args: Array[String]) { val map = Map( 1 -> 10, 2 -> 20 ) map.foreach { case (key, value) => println(key + ":" + value) } //=> 1:10 //=> 2:20 }
上記のように書けば、Mapっぽくkeyとvalueという変数で値を扱える。
-
foreach その6(JavaのMap)
JavaのMapも、Listの時と同じようにJavaConversionsをimportすればforeachできる。
import java.util.HashMap import scala.collection.JavaConversions._ def main(args: Array[String]) { val map = new HashMap[Int, Int] map.put( 5, 10 ) map.put( 7, 15 ) map.foreach { case (key, value) => println(key + ":" + value) } //=> 5:10 //=> 7:15 }
JavaのMapだけど、Conversionされてるから要素の中身はScalaのMapと同じくtupleとして受け取っている。ので、書き方はその5と同じ方法が可能。
HashMapの中にArrayListを入れるみたいな構成にしても、普通に回せる。
// HashMapの中にArrayListという構成を作る val map = new HashMap[Int, ArrayList[String]] val list = new ArrayList[String] list.addAll( List( "a", "b", "c" ) ) map.put( 5, list ) map.put( 7, list ) // foreachを入れ子にして回す map.foreach { case (key, list) => list.foreach { value => print( key + ":" + value + ", " ) } }