Haskell 勉強メモ6
再帰
最大値を求める関数を考えてみる。命令的に考えると、現在の最大値を保持する変数を用意し、リストの全ての要素についてループして、変数を更新していく形になる。Scalaで書いてみると
def maximum(list: List[Int]): Int = { var max = list.head list.foreach { v => if (v > max) max = v } max }
となる。再帰的に書く場合最初に基底部(解を明示的に定義しなければならないケース)を定義しなければならない。単一要素のリストに対する最大値は、その唯一の要素と同じ値である。それ以上の場合は、リストの先頭要素と残りのリストの最大値でどちらが大きいかを見る。Scalaで書いてみる。
def maximum(list: List[Int]): Int = { def max(a: Int, b: Int): Int = if (a >= b) a else b list match { case Nil => throw new IllegalArgumentException case ::(head, next) if list.size == 1 => head case ::(head, next) => max(head, maximum(next)) } }
要素が1つのみの表現がわからなかったので、ガードでかいた。
Haskellでは
maximum' :: (Ord a) => [a] -> a maximum' [] = error "maximun of empty list!" maximum' [x] = x maximum' (x : xs) = max x (maximum' xs) *Main> maximum' [1, 3, 5, 0, 13, 11, 12] 13
replicate
Int
と値をとり、その値を指定された数だけ繰り返したリストを返す。
Haskell には元々ある。
*Main> replicate 3 5 [5,5,5]
基底部を考える。0 以下の回数だけ複製することはできないので、この際は空のリストを返す。
replicate' :: Int -> a -> [a] replicate' n x | n <= 0 = [] | otherwise = x : replicate' (n - 1) x
Scalaで書く場合
def replicate[T](n: Int)(x: T): List[T] = n match { case k if k <= 0 => Nil case k => x +: replicate(n - 1)(x) } replicate(3)(5) // val res2: List[Int] = List(5, 5, 5)
Scala側はカリー化する必要はあまりないが、Haskell と合わせている。
take
takeは指定されたリストから指定された数を返す。
take' :: Int -> [a] -> [a] take' n _ | n <= 0 = [] take' _ [] = [] take' n (x : xs) = x : take' (n -1) xs *Main> take' 3 [1,2,3,4,5] [1,2,3]
Scalaでの記述
def take[T](n: Int)(xs: List[T]): List[T] = (n, xs) match { case (k, _) if k <= 0 => Nil case (_, Nil) => Nil case (k, ::(head, next)) => head +: take(k-1)(next) } take(2)(List(1,2,3,4,5)) // val res3: List[Int] = List(1, 2)
Scalaのリストメソッドには、takeはあらかじめ実装されているので実用上はわざわざ作る必要はない。
override def take(n: Int): List[A] = if (isEmpty || n <= 0) Nil else { val h = new ::(head, Nil) var t = h var rest = tail var i = 1 while ({if (rest.isEmpty) return this; i < n}) { i += 1 val nx = new ::(rest.head, Nil) t.next = nx t = nx rest = rest.tail } releaseFence() h }
手続き的な実装にはなっているが。
reverse
関数をリストに取り、逆順のリストを返す
reverse' :: [a] -> [a] reverse' [] = [] reverse' (x : xs) = reverse' xs ++ [x]
Scala だと
def reverse[T](xs: List[T]): List[T] = xs match { case Nil => Nil case ::(head, next) => reverse(next) :+ head } reverse(List(1,2,3,4,5)) // val res3: List[Int] = List(5, 4, 3, 2, 1)
repeat
受け取った要素から無限リストを作る関数。Haskellは基本遅延評価だから、無限リストでも途中で切るようにとれば問題ない。
*Main> take 3 (repeat 5) [5,5,5]
再帰的実装は
repeat' :: a -> [a] repeat' x = x : repeat' x *Main> take 3 (repeat' 3) [3,3,3]
Scalaの通常のListは遅延評価ではないから、真似はできないか調べたら LazyList
っていうのがあるのを知った。
val lazyList: LazyList[Int] = LazyList.continually(1).take(15) var count = 0 lazyList.foreach { v => count += v} println(count) // 15
API ドキュメントに記載されているサンプルが分かりやすい。
val fibs: LazyList[Int] = { 0 #:: 1 #:: fibs.zip(fibs.tail).map { n => println(s"Adding ${n._1} and ${n._2}") n._1 + n._2 } } fibs.take(5).foreach(println) // 0 // 1 // Adding 0 and 1 // 1 // Adding 1 and 1 // 2 // Adding 1 and 2 // 3 fibs.take(6).foreach(println) // 0 // 1 // 1 // 2 // 3 // Adding 2 and 3 // 5
アクセスされて初めて評価されていることが分かる。
zip
2つのリストを引数にとり、これらを綴じ合わせて返す。
Prelude> zip [1,2] ['a', 'b', 'c'] [(1,'a'),(2,'b')]
List(1,2,3).zip(List("a", "b")) // val res9: List[(Int, String)] = List((1,a), (2,b))
実装。どちらか一方が空のリストの時は、空のリストを返す。
どちらの先頭要素もあれば、先頭要素をとったタプルを作り、再帰する
zip' :: [a] -> [b] -> [(a, b)] zip' _ [] = [] zip' [] _ = [] zip' (x:xs) (y:ys) = (x, y) : zip' xs ys
Scalaで書くと
def zip[A, B](a: List[A])(b: List[B]): List[(A, B)] = (a, b) match { case (_, Nil) => Nil case (Nil, _) => Nil case (::(x, xs), ::(y, ys)) => (x, y) +: zip(xs)(ys) } zip(List(1, 2, 3))(List("a", "b")) // val res11: List[(Int, String)] = List((1,a), (2,b))
elem
値とリストを受け取り、その値がリストに含まれるか調べる関数
*Main> 1 `elem` [2,3,4] False
Scalaだと contains
にあたるかなと。
List(2,3,4).contains(1)
Haskell 再帰での実装。基底部は空のリストを受け取った時のFalse。リストに要素がある場合は、先頭と値を比較して一致しなければ、残りの要素に対して再帰する。
elem' :: (Eq a) => a -> [a] -> Bool elem' a [] = False elem' a (x:xs) | a == x = True | otherwise = a `elem'` xs
Scalaでの実装
@tailrec def elem[A](a: A)(list: List[A]): Boolean = (a, list) match { case (_, Nil) => false case (a, ::(x, _)) if a == x => true case (a, ::(_, xs)) => elem(a)(xs) } elem(1)(List(2,3,4)) // val res13: Boolean = false elem(1)(List(2,1,3)) // val res14: Boolean = true