続:Haskell/Clojureでrepeated_combinationを実装してみる
http://hifistar.hatenablog.com/entry/2018/03/24/131654:repeat_combinationを実装してみるの続き。
この前書いたコードは結構無駄が多くて、実はもっとぜんぜん簡潔にコードが書けた。 ここまで簡潔なコードにブラッシュアップできたのは、Haskellのおかげだなと。
Clojureだけの場合は、ある程度で満足して、先に思考を進めることはあまりしなかった。 Haskellでコードを書くと、違う視点でものごとを考えることができる、というのが大きい。
Haskellは、文字通り「型にはまる」とコードがものすごくシンプルになる、と実感する。 関数のInput/Outputの型(データの抽象度のレベル感)を揃えることが、が非常に大事だと学んだ。
これはプログラミングの基本中の基本かもしれないが、なかなか意識しても難しい。 Haskellの場合は強制的にそれをさせる、という意味でかなり良い。
Clojureの場合は、そろってなくてもプログラムが組める。自由度が高い。
if
などの条件文で動的に型をチェックして、処理を振り分けてしまえる。
これはもろ刃の剣で、素早くコードを組んだり変更したりするには向いているが、かなり意識しないとプログラムの構造がmessyになる。
双方をやることでいろいろ気づきが出てきて、「結局こういうことじゃね?」という風に理解が深まる。 短いコードで表現できるアルゴリズム系の勉強に、結構いいんじゃなかろうかと思った。
さて、あらためてrepeat_combinationの実行例:
*Main> repeat_combination [1,2,3] 2 [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]] *Main> repeat_combination [1,2,3] 3 [[1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],[2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],[3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3]]
幅優先で組み合わせする場合
結局こういうことだ。(「幅優先」という表現が合っているか微妙だが)
Haskell
repeat_combination :: [Int] -> Int -> [[Int]] repeat_combination xs 0 = [[]] repeat_combination xs n = [ x : y | x <- xs, y <- repeat_combination xs (n-1)]
Clojure
(defn repeat-combination [xs n] (if (= n 0) [[]] (for [x xs y (repeat-combination xs (dec n))] (cons x y))))
上記は、組み合わせた結果の帰り値に対して、さらに組み合わせを計算する。
末尾最適化可能なやり方
同じ幅優先だが、こちらは引数に結果を渡すやり方で、これだと末尾最適化できる。
Haskell
combination :: [Int] -> [[Int]] -> Int -> [[Int]] combination xs ys 0 = ys combination xs ys n = combination xs [ x : y | x <- xs, y <- ys ] (n-1) repeat_combination :: [Int] -> Int -> [[Int]] repeat_combination xs n = combination xs [[]] n
Clojure
(defn combination [xs ys n] (if (= n 0) ys (recur xs (for [x xs, y ys] (cons x y)) (dec n)))) (defn repeat-combination [xs n] (combination xs [[]] n))
深さ優先で組み合わせる場合
これも引数に結果を渡して繰り返すやり方。
Haskell
combination xs ys 0 = [ys] combination xs ys n = concat [combination xs (ys++[x]) (n-1) | x <- xs] repeat_combination :: [Int] -> Int -> [[Int]] repeat_combination xs n = combination xs [] n
Clojure
(defn combination [xs ys n] (if (= n 0) [ys] (apply concat (for [x xs] (combination xs (conj ys x) (dec n)))))) (defn repeat-combination [xs n] (combination xs [] n))
concat
が美しさを損ねているが、まあしょうがない・・・。
ひとつ選ぶとしたら、分かりやすさと簡潔さ、プラスStack Overflowにならないので、2番目のやり方かなあ。