Scala exercises for beginners をやってみた。

https://gist.github.com/horiuchi/5106615

scalaで何か書いてみようと、手始めに λ Tony's blog λ - Scala exercises for beginners をやってみました。解答は上記のGistに置きました。
問題としては scalaの構文を覚えることと関数型的な書き方に慣れるためのものです。なので、普通に再帰で書く場合と、foldrを使った畳み込みでやる場合の2通りで書いてみました。

Haskellっぽく、解けたので問題自体は難しくなかったです。
書いていて気になったのは、foldrにlambdaを渡す際に引数の型を明示しないとならないのがちょっと違和感。scalaでは型は基本的に明示するものということなんですかね?型推論はそれほど強くないのかな?
今まで HaskellC# では、特に明示しなくても推論されていたので、違いが気になりました。

解答

まずは準備として共通関数を書いておきます。

  def succ(n: Int) = n + 1
  def pred(n: Int) = n - 1

  def id[A](x:A):A = x

  def foldRight[A, B](x: List[A], f: (A, B) => B, v: B): B = x match {
    case Nil => v
    case a:: tail => f(a, foldRight(tail, f, v))
  }
  def foldLeft[A, B](x: List[A], f: (B, A) => B, v: B): B = x match {
    case Nil => v
    case a :: tail => foldLeft(tail, f, f(v, a))
  }

  def foldLeft2[A, B](x: List[A], f: (B, A) => B, v: B): B = {
    def h(a:A, g:B => B): B => B = (b:B) => g(f(b, a))
    foldRight(x, h, id[B] _)(v)
  }

問題1

「todo: Assume x and y are 0 or positive. Do not use + or - on Int. Only permitted to use succ/pred (above).」
とあるので、単純に再帰で解きました。

  // Exercise 1
  // Relative Difficulty: 1
  // Correctness: 2.0 marks
  // Performance: 0.5 mark
  // Elegance: 0.5 marks
  // Total: 3
  def add(x: Int, y: Int): Int = y match {
    case 0 => x
    case _ => add(succ(x), pred(y))
  }

問題2

Listの合計を求める問題。Exercise1のadd関数を使って解きました。
sum は単純な再帰で、sum2は末尾再帰関数で、sum3はfoldrで。

  // Exercise 2
  // Relative Difficulty: 2
  // Correctness: 2.5 marks
  // Performance: 1 mark
  // Elegance: 0.5 marks
  // Total: 4
  def sum(x: List[Int]): Int = x match {
    case Nil => 0
    case a :: tail => add(a, sum(tail))
  }
  def sum2(x: List[Int]): Int = {
    def f(x: List[Int], r: Int): Int = x match {
      case Nil => r
      case a :: tail => f(tail, add(r, a))
    }
    return f(x, 0)
  }
  def sum3(x: List[Int]): Int = foldRight(x, add, 0)

問題3

Listの長さを求める問題。
lengthは単純な再帰で、length2は他の関数を使って、length3はfoldrで解きました。

  // Exercise 3
  // Relative Difficulty: 2
  // Correctness: 2.5 marks
  // Performance: 1 mark
  // Elegance: 0.5 marks
  // Total: 4
  def length[A](x: List[A]): Int = x match {
    case Nil => 0
    case a :: tail => 1 + length(tail)
  }
  def length2[A](x: List[A]): Int = sum(map(x, (_:A) => 1))
  def length3[A](x: List[A]): Int = foldRight(x, (a:A, b:Int) => 1 + b, 0)

問題4

mapを再定義する問題。map関数は、関数型言語の基本要素の1つですね。
単純な再帰と、foldrでそれぞれ解きました。

  // Exercise 4
  // Relative Difficulty: 5
  // Correctness: 4.5 marks
  // Performance: 1.0 mark
  // Elegance: 1.5 marks
  // Total: 7
  def map[A, B](x: List[A], f: A => B): List[B] = x match {
    case Nil => Nil
    case a :: tail => f(a) :: map(tail, f)
  }
  def map2[A, B](x: List[A], f: A => B): List[B] = foldRight(x, (a:A, l:List[B]) => f(a) :: l, Nil)

問題5

filterを再定義する問題。こちらも、関数型言語の基本要素の1つですね。
また、単純な再帰と、foldrでそれぞれ解きました。

  // Exercise 5
  // Relative Difficulty: 5
  // Correctness: 4.5 marks
  // Performance: 1.5 marks
  // Elegance: 1 mark
  // Total: 7
  def filter[A](x: List[A], f: A => Boolean): List[A] = x match {
    case Nil => Nil
    case a :: tail if f(a) => a :: filter(tail, f)
    case a :: tail         => filter(tail, f)
  }
  def filter2[A](x: List[A], f: A => Boolean): List[A] = foldRight(x, (a:A, l:List[A])=> if (f(a)) a :: l else l, Nil)

問題6

ListとListを連結する関数。
単純な再帰と、foldrでそれぞれ解きました。

  // Exercise 6
  // Relative Difficulty: 5
  // Correctness: 4.5 marks
  // Performance: 1.5 marks
  // Elegance: 1 mark
  // Total: 7
  def append[A](x: List[A], y: List[A]): List[A] = x match {
    case Nil => y
    case a :: tail => a :: append(tail, y)
  }
  def append2[A](x: List[A], y: List[A]): List[A] = foldRight(x, (a:A, l:List[A]) => a :: l, y)

問題7

ListのListをListに均す関数。
append関数を使って、単純な再帰とfoldrでそれぞれ解きました。

  // Exercise 7
  // Relative Difficulty: 5
  // Correctness: 4.5 marks
  // Performance: 1.5 marks
  // Elegance: 1 mark
  // Total: 7
  def concat[A](x: List[List[A]]): List[A] = x match {
    case Nil => Nil
    case a :: tail => append(a, concat(tail))
  }
  def concat2[A](x: List[List[A]]): List[A] = foldRight(x, append[A], Nil)

問題8

concatする際に単純に均すのではなく、何らかの処理を行う関数。
concatとほぼ同じ処理なのでappend関数を使って、単純な再帰とfoldrでそれぞれ解きました。

  // Exercise 8
  // Relative Difficulty: 7
  // Correctness: 5.0 marks
  // Performance: 1.5 marks
  // Elegance: 1.5 mark
  // Total: 8
  def concatMap[A, B](x: List[A], f: A => List[B]): List[B] = x match {
    case Nil => Nil
    case a :: tail => append(f(a), concatMap(tail, f))
  }
  def concatMap2[A, B](x: List[A], f: A => List[B]): List[B] = foldRight(x, (a:A, l:List[B]) => append(f(a), l), Nil)

問題9

Listの中の最大値を求める関数。
2引数のmax関数を定義して、単純な再帰とfoldrでそれぞれ解きました。
Listが空の場合の指定がなかったので、0を返すようにしてます。

  // Exercise 9
  // Relative Difficulty: 8
  // Correctness: 3.5 marks
  // Performance: 3.0 marks
  // Elegance: 2.5 marks
  // Total: 9
  def maximum(x: List[Int]): Int = x match {
    case Nil => 0
    case a :: tail => max(a, maximum(tail))
  }
  def max(a: Int, b:Int): Int = if (a > b) a else b
  def maximum2(x: List[Int]): Int = foldRight(x, max, 0)

問題10

Listを逆順にする関数。
逆順に処理を行う必要があるので、末尾再帰の形で解きました。
foldrでの解法も、一旦foldlをfoldrで定義してからそれを使ったものになっています。

  // Exercise 10
  // Relative Difficulty: 10
  // Correctness: 5.0 marks
  // Performance: 2.5 marks
  // Elegance: 2.5 marks
  // Total: 10
  def reverse[A](x: List[A]): List[A] = {
    def rev[A](y: List[A], r:List[A]): List[A] = y match {
      case Nil => r
      case a :: tail => rev(tail, a :: r)
    }
    rev(x, Nil)
  }
  def reverse2[A](x: List[A]): List[A] = foldLeft2(x, (l:List[A], a:A) => a :: l, Nil)

以上です。