See the Elephant

1992生まれのプログラマが書くエンジニアブログ

scala コレクションライブラリ foldLeft / foldRight

scala コレクションライブラリ foldLeft / foldRight

https://dwango.github.io/scala_text/collection.html#foldleft%EF%BC%9A%E5%B7%A6%E3%81%8B%E3%82%89%E3%81%AE%E7%95%B3%E3%81%BF%E8%BE%BC%E3%81%BF

foldLeft

foldLeft はリストの要素を左から順にfを使って「畳み込む」。

リストの要素に対して左側から関数をする、関数の結果と次の要素を取り出し関数を適用する、という風に再帰的に処理が繰り返される。

fold = 畳み込む という意味である。

codeで表すと以下のようになる

def foldLeft[B](z: B)(f: (B, A) ⇒ B): B

foldLeftはリストの左側からfを適用していく。zは初期値だ。

リストに加算を行う式は以下だ

scala> List(1, 2, 3).foldLeft(0)((x, y) => x + y)
res0: Int = 6

foldLeft が行う処理

List(1, 2, 3).foldLeft(0)((x, y) => x + y)

上記の式が計算する順序は以下だ。初期値を0としてListの先頭から加算を繰り返す。そしてリストの要素を一つの要素にたたみ込んで行く。

       +
      / \
     +   3
    / \
   +   2
  / \
 0   1

演習問題

foldLeftを用いて、Listの要素を反転させる次のシグニチャを持ったメソッドreverseを実装してみましょう:

def reverse[T](list: List[T]): List[T] = ???

コンスを使えばイケそう

scala> def reverse[T](list: List[T]): List[T] = {
     |   val rev: List[T] = Nil;
     |   list.foldLeft(rev)((r, x) => x :: r);
     | }
reverse: [T](list: List[T])List[T]

scala> List(1,2,3,4,5).reverse
res4: List[Int] = List(5, 4, 3, 2, 1)

なるほど。 1 :: Nil みたいにコレクション型を後置しないといけないんだね

foldRight

これはそのまま、右からの畳み込み。

codeで表現すると次になる。

def foldRight[B](z: B)(op: (A, B) ⇒ B): B

初期値とリストの右から要素を取り出し、関数を適用。その結果と1つ左の要素を取り出し関数を適用、という風に順番にたたみ込んで行く。

   +
  / \
 1   +   
    / \
   2   +   
      / \
     3   0

foldLeftが理解できているとわかりやすいね

練習問題1

Listの全ての要素を足し合わせるメソッドsumをfoldRightを用いて実装してみましょう。sumの宣言は次のようになります。なお、Listが空のときは0を返してみましょう。

def sum(list: List[Int]): Int = ???

自分の答え

これは簡単だね

scala> def sum(list: List[Int]): Int = {
     |   if (list.isEmpty) { return 0; }
     |   list.foldRight[Int](0)((x, y) => x+y);
     | }
sum: (list: List[Int])Int

scala> List(1,2,3,4,5).sum
res3: Int = 15

模範解答

def sum(list: List[Int]): Int = list.foldRight(0){(x, y) => x + y}

え、まじか。戻り値デフォルトで0なのね。これはfoldRightの仕様っぽいね

次の問題。

練習問題2

Listの全ての要素を掛け合わせるメソッドmulをfoldRightを用いて実装してみましょう。mulの宣言は次のようになります。なお、Listが空のときは1を返してみましょう。

def mul(list: List[Int]): Int = ???

自分の答え

これは模範解答通りだった。嬉しい。

scala> def mul(list: List[Int]): Int = list.foldRight(1)((x,y) => x*y);
mul: (list: List[Int])Int

scala> mul(List(1,2,3,4,5))
res4: Int = 120

練習問題3

mkStringを実装してみましょう。mkStringそのものを使ってはいけませんが、foldLeftやfoldRightなどのListに定義されている他のメソッドは自由に使って構いません。ListのAPIリファレンス を読めば必要なメソッドが載っています。実装するmkStringの宣言は

def mkString[T](list: List[T])(sep: String): String = ???

となります。残りの2つのバージョンのmkStringは実装しなくても構いません。

さて、急にレベル上がったな。

mkString はList内の要素を結合する関数だったはず。

自分の答え

あんまり綺麗な答えではないな。

scala> def mkString[T](list: List[T])(sep: String): String = {
     |   list.foldLeft("")((x, y) => { 
     |     if (x.isEmpty) {x + y.toString}
     |     else {x + sep + y.toString}
     |   });
     | }
mkString: [T](list: List[T])(sep: String)String

scala> mkString[Int](List(1,2,3))(",")
res13: String = 1,2,3

模範解答

確かに綺麗だけどまだmatch教えてくれてないやん!!!! ありかよ!!w

def mkString[T](list: List[T])(sep: String): String = list match {
  case Nil => ""
  case x::xs => xs.foldLeft(x.toString){(x, y) => x + sep + y}
}

2個目のcaseが気になった。

x::xs => xs.foldLeft(x.toString){(x, y) => x + sep + y}

x::xsなんらかのlistが2つ連結したもの と見立てることができる。

そう考えると x = Nil, xs = 処理したいList と考えてもいい。

scala> Nil :: List(1,2,3)
res14: List[Any] = List(List(), 1, 2, 3)

こう見ると xs には処理対象としたい list が入ってくるのがわかりやすい。

ということで今回はここまで。