インデックス付きforeachほか

概要

先日Javaで書いたインデックス付きforeachループ
http://d.hatena.ne.jp/kya-zinc/20100107/1262856312
Scalaで書いてみた。
(なお、Scalaのfor-comprehensionはループ以外にも使える。)
Iterableのデフォルト実装は便利。

実装

ScalaのIterableには(Javaのインターフェースと異なり)abstractでない関数の実装があるので
elements(JavaのIterable#iterator()に対応)を実装するだけで
foreach, map, flatMap, filterが期待通りに動く。
リッチだ。

object IndexedForeach {
  def main(args : Array[String]) : Unit = {
    val list = List(100, 200, 300)
    val ilist = withIndex(list)
    val stream = Stream.from(101)
    val istream = withIndex(stream)

    //foreach
    for((i, n) <- ilist){
      println(n + " @ " + i + "ばんめ")
    }
    
    //map
    val list2 = ilist.map {case (i, n) => (i + n)}
    println(list2)
    
    //flatMap
    val list3 = ilist.flatMap {case (i, n) => List(i + n, i * n)}
    println(list3)
    
    //filter
    val list4 = ilist.filter {case (i, n) => i % 2 == 0}
    println(list4)
    
    //flatMap, filter, foreach
    for((i, x) <- ilist; (j, y) <- ilist; if (i + j) % 2 == 0){
      printf("(%d, %d) @ (%d, %d)ばんめ\n", x, y, i, j)
    }
    
    //foreach(Stream)
    for((i, n) <- istream){
      if(i > 5) return
      println(n + " @ " + i + "ばんめ")
    }
  }

  class IndexedIterator[A](iter : Iterator[A]) extends Iterator[(Int, A)] {
    var idx = 0
    
    override def hasNext = iter.hasNext
    
    override def next() = {
      val pair = (idx, iter.next)
      idx = idx + 1
      pair
    }
  }
  
  def withIndex[A](iter : Iterable[A]) : Iterable[(Int, A)] = 
    new Iterable[(Int, A)](){
      override def elements = new IndexedIterator[A](iter.elements)
  }
}

実行結果

100 @ 0ばんめ
200 @ 1ばんめ
300 @ 2ばんめ
ArrayBuffer(100, 201, 302)
ArrayBuffer(100, 0, 201, 200, 302, 600)
ArrayBuffer((0,100), (2,300))
(100, 100) @ (0, 0)ばんめ
(100, 300) @ (0, 2)ばんめ
(200, 200) @ (1, 1)ばんめ
(300, 100) @ (2, 0)ばんめ
(300, 300) @ (2, 2)ばんめ
101 @ 0ばんめ
102 @ 1ばんめ
103 @ 2ばんめ
104 @ 3ばんめ
105 @ 4ばんめ
106 @ 5ばんめ

メモ

  • デフォルト実装に頼るとmap, flatMap, filterの結果がArrayBufferになる
  • 予想に反してStreamでもforeachが動く
    • 無限だった場合、returnや例外でしか止められないが…
  • (A, B) => CはFunction2[A,B,C]でPair[A,B] => Cとは違う
    • for-comprehensionではパターンが使えるので(i, n) <- ...と書けるが、そうでない場合(i, n)を束縛するにはcaseが必要

改善

ArrayBufferが気に入らないので
foreach, map, flatMap, filterを適当に実装。(割と自明)
filterで迷ったが、mapを経由すれば一発だと気付く。
オブジェクトを返さないforeachをオーバーライドする意味はないかもしれない。
とりあえずArrayBufferだったところをListにすることに成功。

object IndexedForeach2 {
  def main(args : Array[String]) : Unit = {
    val list = List(100, 200, 300)
    val ilist = withIndex(list)
    val stream = Stream.from(101)
    val istream = withIndex(stream)

    //foreach
    for((i, n) <- ilist){
      println(n + " @ " + i + "ばんめ")
    }
    
    //map
    val list2 = ilist.map {case (i, n) => (i + n)}
    println(list2)
    
    //flatMap
    val list3 = ilist.flatMap {case (i, n) => List(i + n, i * n)}
    println(list3)
    
    //filter
    val list4 = ilist.filter {case (i, n) => i % 2 == 0}
    println(list4)
    
    //flatMap, filter, foreach
    for((i, x) <- ilist; (j, y) <- ilist; if (i + j) % 2 == 0){
      printf("(%d, %d) @ (%d, %d)ばんめ\n", x, y, i, j)
    }
    
    //foreach(Stream)
    for((i, n) <- istream){
      if(i > 5) return
      println(n + " @ " + i + "ばんめ")
    }
  }
  
  class IndexedIterator[A](iter : Iterator[A]) extends Iterator[(Int, A)] {
    var idx = 0
    
    override def hasNext = iter.hasNext
    
    override def next() = {
      val pair = (idx, iter.next)
      idx = idx + 1
      pair
    }
  }
  
  def withIndex[A](iter : Iterable[A]) : Iterable[(Int, A)] = 
    new Iterable[(Int, A)](){
      type IA = (Int, A)
      override def elements = new IndexedIterator[A](iter.elements)
      
      override def foreach(body : IA => Unit) = {
        var i = 0
        iter.foreach { x =>
          body(i, x)
          i = i + 1
        }
      }
      
      override def map[B](f : IA => B) : Iterable[B] = {
        var i = 0
        iter.map  { x =>
          val b = f(i, x)
          i = i + 1
          b
        }
      }
      
      override def flatMap[B](f : IA => Iterable[B]) : Iterable[B] = {
        var i = 0
        iter.flatMap { x =>
          val bs = f(i, x)
          i = i + 1
          bs
        }
      }
      
      override def filter(f : IA => Boolean) : Iterable[IA] = {
        val temp = map{x => x}
        temp.filter(f)
      }
    }
}

あれ?id関数ってないの?