練習問題を解いてみる

kdaiba2005-11-28


Haskellに関してちょっとづつ調べてみてるけど,難しいって思う以外,全然進みません.そこで,情報処理学会に連載されてるhttp://www.ipsj.or.jp/07editj/promenade/という記事の例題を解いてみようと思いました.今回トライしてみたのは,2005年5月号の「木(tree)で遊ぶ」http://www.ipsj.or.jp/07editj/promenade/4605.pdfの例題1についていた,練習問題1です.

例題1の問題は,「末端のノードがn個の二分木を列挙せよ」というもので,

練習問題1 trees 4 の評価結果が

[(L^(L^(L^R))),(L^((L^R)^R)),((L^R)^(L^R)),((L^(L^R))^R),(((L^R)^R)^R)]

と表示されるようDumbTree用のshowの定義を変更せよ.

というのが練習問題でした.私の答えはこんな感じ.

instance Show DumbTree where
  show (Fork Empty Empty) = "(" ++ "L"    ++ "^" ++ "R" ++ ")"
  show (Fork l Empty)     = "(" ++ show l ++ "^" ++ "R" ++ ")"
  show (Fork Empty r)     = "(" ++ "L"    ++ "^" ++ show r ++ ")"
  show (Fork l r)         = "(" ++ show l ++ "^" ++ show r ++ ")"

元々,DubmTreeという型は次のように定義されていました.

data DumbTree = Empty | Fork DumbTree DumbTree

これは『DumbTree型は,末端ノードEmptyかあるいは再帰的に二つの部分木DumbTreeを含むForkノードで構成』と読むらしいです.最初は,Fork/Emptyというのが何者か(記事にはデータ構成子と書いてある)がわからず,ずっと悩んでいました.組み込み型なのかと思ってぐぐったけど,全然ヒットなし.データ構成子でぐぐると,変わった名前が出てきたりして,予め組み込まれた名前とも思えません.ふと思いついて,プログラム中のキーワードを":%s/Fork/Mata/g", ":%s/Empty/Kara/g" (vimでの置換表現)とやっても動いたので,ユーザ定義名なんだということに気がついたのでした.そうしてこうして,最終的にできたプログラムは以下のようになります.

-- data宣言: 組込型以外の型を定義
-- 『DumbTree型は,末端ノードEmptyかあるいは
-- 再帰的に二つの部分木DumbTreeを含むForkノードで構成』と,読む
--   DumbTree : 型構成子
--   Empty, Fork : データ構成子
data DumbTree = Empty | Fork DumbTree DumbTree

-- DumbTreeがShowクラスのインスタンスだと宣言
-- show関数は,ある型のデータを表示するための関数
instance Show DumbTree where
  show (Fork Empty Empty) = "(" ++ "L"    ++ "^" ++ "R" ++ ")"
  show (Fork l Empty)     = "(" ++ show l ++ "^" ++ "R" ++ ")"
  show (Fork Empty r)     = "(" ++ "L"    ++ "^" ++ show r ++ ")"
  show (Fork l r)         = "(" ++ show l ++ "^" ++ show r ++ ")"

-- trees: 『Int型からDumbTree型を要素とするリストへの関数』という型
-- 整数からDumbTreeのリストへ
trees :: Int -> [DumbTree]
trees 1 = [Empty]
-- "splits1 n"で構成されるリストの各要素である二つ組み(xs, ys)に
-- 関数lrsを適用してできたリストの要素である二つ組み(ls, rs)に
-- 関数joinsを適用してできたリスト(複数)に
-- 関数concatを適用して一つのリストにする
trees n = concat [joins ls rs | (ls, rs) <- [lrs xs ys | (xs, ys) <- splits1 n]]

-- splits1: 『Int型からInt型の二つ組みのリストへの関数』という型
-- 整数から,和がその数になる整数の二つ組みのリストへ
splits1 :: Int -> [(Int, Int)]
splits1 1 = []
splits1 n = (1, n-1) : [(i+1, j) | (i, j) <- splits1 (n-1)]

-- lrs: 『ふたつのInt型から,DumbTreeリストの二つ組みへの関数』という型
-- 二つの整数から,それぞれの大きさのDumbTreeのリストの二つ組みへ
lrs :: Int -> Int -> ([DumbTree], [DumbTree])
lrs xs ys = (trees xs, trees ys)

-- joins: 『ふたつのDumbTreeリスト型からDumbTreeリスト型への関数』という型
-- ふたつのDumbTreeリストからそれぞれを左右に持つDumbTreeのリスト
joins :: [DumbTree] -> [DumbTree] -> [DumbTree]
joins ls rs = [Fork l r | l <-ls, r <- rs]

先はとっても長いです.まず,時間を作らないと… orz