List comprehension

Scala の for 式で覆面算を解いているエントリを見つけたので同じように Haskell と Groovy で解きながらリスト内包表記について考える。

リスト内包表記は Groovy にもいずれ実装されるのか?

共通部分

def num(int... xs) {
  xs.inject(0) { acc, x -> acc * 10 + x }
}
def digits = 0..9

for ループで解く

def solve1 = {
  def yield  = []
  for (s in digits)
  if (s != 0)
  for (e in digits - [s])
  for (n in digits - [s, e])
  for (d in digits - [s, e, n])
  for (m in digits - [s, e, n, d])
  if (m != 0)
  for (o in digits - [s, e, n, d, m])
  for (r in digits - [s, e, n, d, m, o])
  for (y in digits - [s, e, n, d, m, o, r])
  if (num(s, e, n, d) + num(m, o, r, e) == num(m, o, n, e, y))
  yield << [num(s, e, n, d), num(m, o, r, e), num(m, o, n, e, y)]
  return yield
}
  • ネストしないで記述 *1
  • このぐらいループすると for 式も一行で記述できなくなるので、ネストしないループと変わらない

List のメソッドで解く

Scala スケーラブルプログラミング』の第 23 章によれば Scala コンパイラは for 式を map, flatMap, filter 呼び出しの組合せに変換しているそうだ。*2
Groovy で変換後の形を記述する。

class ListOps {
  static def concat(List self) {
    self.inject([]) { acc, v ->
      acc + v
    }
  }

  static def map(List self, Closure f) {
    self.collect { f(it) }
  }

  static def concatMap(List self, Closure f) {
    concat(map(self, f))
  }
}

def solve2 = {
  use (ListOps) {
    digits.findAll { s ->
      s != 0
    }.concatMap { s ->
    (digits - [s]).concatMap { e ->
    (digits - [s, e]).concatMap { n ->
    (digits - [s, e, n]).concatMap { d ->
    (digits - [s, e, n, d]).findAll { m ->
      m != 0
    }.concatMap { m ->
    (digits - [s, e, n, d, m]).concatMap { o ->
    (digits - [s, e, n, d, m, o]).concatMap { r ->
    (digits - [s, e, n, d, m, o, r]).findAll { y ->
      num(s, e, n, d) + num(m, o, r, e) == num(m, o, n, e, y)
    }.map { y ->
      [num(s, e, n, d), num(m, o, r, e), num(m, o, n, e, y)]
    } } } } } } } }
  }
}
  • Scala の map, flatMap, filter は map, concatMap, findAll に対応する
    • Groovy の flatten は Scala と違い再帰的に処理されるので concat を定義した
    • concat は } の後ろになり対応が分かりづらいので concatMap も定義した
  • findAll の位置は『23.4 for 式の変換』を参考

List モナドで解く

まずは Haskell の do 式で

-- ghci
import Data.List
import Control.Monad
:{
let { digits = [0..9];
      num    = foldl ((+).(*10)) 0 }
 in do { 
      s <- digits;
      guard (s /= 0);
      e <- digits \\ [s];
      n <- digits \\ [s, e];
      d <- digits \\ [s, e, n];
      m <- digits \\ [s, e, n, d];
      guard (m /= 0);
      o <- digits \\ [s, e, n, d, m];
      r <- digits \\ [s, e, n, d, m, o];
      y <- digits \\ [s, e, n, d, m, o, r];
      guard (num [s, e, n, d] + num [m, o, r, e] == num [m, o, n, e, y]);
      return (num [s, e, n, d], num [m, o, r, e], num [m, o, n, e, y]) }
:}
  • 次のエラーが発生するので ; 区切りで記述した
    • The last statement in a 'do' construct must be an expression

do 式から bind へ変換した形で Groovy で記述する。

class ListMonad {
  static def rightShiftUnsigned(List self, Closure f) {
    ListOps.concat(self.collect { f(it) })
  }

  static def return_(_, x) {
    [x]
  }

  static def rightShift(List self, List other) {
    ListOps.concat(self.collect { other })
  }

  // guard   :: MonadPlus m => Bool -> m ()
  // guard p =  if p then return () else mzero
  static def guard(_, boolean p) {
    // guard(条件) >> リスト
    // 条件が真の場合は、要素が1つのリストを返す -> 要素は使われない。ループは1回
    // 条件が偽の場合は、空リストを返す -> ループ終了
    if (p) return_(_, null) else ListMonadPlus.mzero(_)
  }
}

class ListMonadPlus {
  static def mzero(_) {
    []
  }

  static def mplus(List self, List other) {
    self + other
  }
}

def solve3 = {
  use (ListMonad) {
    digits    >>> { s ->
    guard (s != 0) >>
    digits - [s] >>> { e ->
    digits - [s, e] >>> { n ->
    digits - [s, e, n] >>> { d ->
    digits - [s, e, n, d] >>> { m ->
    guard (m != 0) >>
    digits - [s, e, n, d, m] >>> { o ->
    digits - [s, e, n, d, m, o] >>> { r ->
    digits - [s, e, n, d, m, o, r] >>> { y ->
    guard (num(s, e, n, d) + num(m, o, r, e) == num(m, o, n, e, y)) >>
    return_([num(s, e, n, d), num(m, o, r, e), num(m, o, n, e, y)])
    } } } } } } } }
  }
}

Haskell のリスト内包表記で解く

-- ghci
import Data.List
:{
let { digits = [0..9];
      num    = foldl ((+).(*10)) 0 } in
[(num [s, e, n, d], num [m, o, r, e], num [m, o, n, e, y]) |
  s <- digits,
  s /= 0,
  e <- digits \\ [s],
  n <- digits \\ [s, e],
  d <- digits \\ [s, e, n],
  m <- digits \\ [s, e, n, d],
  m /= 0,
  o <- digits \\ [s, e, n, d, m],
  r <- digits \\ [s, e, n, d, m, o],
  y <- digits \\ [s, e, n, d, m, o, r],
  num [s, e, n, d] + num [m, o, r, e] == num [m, o, n, e, y]]
:}

デカルト積で解く

// USE VERY LARGE MEMORY
def solve4 = {
  def yield = []
  ([digits] * 8).combinations().each { s, e, n, d, m, o, r, y ->
    if (s != 0 && m != 0 && [s, e, n, d, m, o, r, y].unique().size() == 8)
      if (num(s, e, n, d) + num(m, o, r, e) == num(m, o, n, e, y))
        yield << [num(s, e, n, d), num(m, o, r, e), num(m, o, n, e, y)]
  }
  yield
}
  • 実行できないのでバグがあるかもしれない
問題点
  • 10 ** 8 サイズのリストを生成している
  • 途中で枝刈りができない
  • ループの順序が違う
list.reverse().combinations() { it.reverse() }
Scala スケーラブルプログラミング』

for の記法は、一般的なデータベースクエリーと本質的に同じである。

  • SQL 的な実装は別途調べたい
404 Blog Not Found:perl & javascript - nested list comprehension
// 生成された js にインデントを加えた
function anonymous(cond) {
  var _list_ = [];
  for (var i0 = 1, l0 = 4; i0 < l0; i0++) {
    for (var i1 = 2, l1 = 5; i1 < l1; i1++) {
      for (var i2 = 3, l2 = 6; i2 < l2; i2++) {
        if (cond(i0, i1, i2)) {
          _list_.push([i0, i1, i2]);
        }
      }
    }
  }
  return _list_;
}
  • デカルト積を求めてから絞り込んでいる
  • List#combinations と違って callback になっているのでリスト全体は必要にならない

Groovy で解く

def solve5 = {
  def combs = { list, n ->
    if (n == 0 || n > list.size()) return []
    if (n == 1) return list.collect { [it] }
    def (x, xs) = [list.head(), list.tail()]
    call(xs, n-1).collect { [x, *it] } + call(xs, n)
  }
  def yield = []
  combs(digits, 8)*.eachPermutation { s, e, n, d, m, o, r, y ->
    if (s != 0 && m != 0)
      if (num(s, e, n, d) + num(m, o, r, e) == num(m, o, n, e, y))
        yield << [num(s, e, n, d), num(m, o, r, e), num(m, o, n, e, y)]
  }
  yield
}
  • 8 種類の数字を使用することが分かっているので組合せを決めてから順列を求める
  • 順列は callback で呼び出せる
  • s != 0 の条件は、最内に入っているが combs().findAll で外に出すことは可能
  • *. は展開ドット演算子
// 『Groovy イン・アクション』から
list*.member
list.collect { item -> item?.member }

追記 2011-04-07

withFilter

id:xuwei 氏から Scala は withFilter にも変換されると教えてもらった。

scala> List(1,2,3).filter{ (i)=>println(i);i%2==1 }
1
2
3
res0: List[Int] = List(1, 3)

scala> List(1,2,3).withFilter{ (i)=>println(i);i%2==1 }
res1: scala.collection.generic.FilterMonadic[Int,List[Int]] = scala.collection.TraversableLike$WithFilter@173a0067

withFilter までで 1 つの List のように扱い filter のためだけのループを行わないようにしているんだと思う。
scala.tools.ast.parser.TreeBuilder#makeFor に答えがありそうだが調べられていない。

Scala for実体メモ(Hishidama's Scala for-convert Memo)ベンチマークも参考にした。

guard

型クラスでHaskellのguardを定義してみる(未完) - papamitra
似たようなことをしている人を発見。
Listモナドではなくモナドで挑戦されているようだ。


guard の動作原理を考える - あどけない話
プログラミング Haskell の訳者である山本氏のエントリを発見。
過程も含めて解説されている。

*1:s != 0 と m != 0 の枝刈りは値が決定した時点で行うようにする

*2:yield しない場合は filter と foreach

*3:SQL で言えば union に当たるのだろうか ?