今回はHaskellでlist monadを使って解いてみましょう。List monadを用いることで簡単に楽しく書けるという利点があります。
動機は、いろいろ調べたのですが、Haskellで単純で速そうなものが見つからなかったためです。
アルゴリズムとしては当然バックトラッキング。
クイーンの配置を表すデータ構造としては [Int] を用いましょう。[(Int, Int)]としてしまうと冗長な感があります。経験上データ構造はなるべくダイエットして必要十分に近いものが理想的だと思います。そのほうが簡単に楽しく書けます。
まずlist monadの定義を確認しましょう。List は Monad型クラスのインスタンスです。その定義はGHC.Baseに記載されています。
instance Monad [] where m >>= k = foldr ((++) . k) [] m m >> k = foldr ((++) . (\ _ -> k)) [] m return x = [x] fail _ = []
bind関数>>= がfoldrを使って書かれていますが、xs >>= f = concat (map f xs)と同じことです。
以上から書いてみると、こんな感じになりました。
import Data.List type Queens = [Int] nQueens :: Int -> [Queens] nQueens n = solve [1..n] [] solve :: Queens -> Queens -> [Queens] solve [] ys = return ys solve xs ys = xs >>= func where func x | isOK (x:ys) = solve (delete x xs) (x:ys) | otherwise = fail "failed!" isOK :: Queens -> Bool isOK [] = True isOK (q:qs) = (all (/= 0) . zipWith (-) [1..(length qs)] . (map (abs . (-) q))) qs
速度計測
比較対象
・LiteratePrograms.org
・pi8027
上のLiteratePrograms.org(以下LPと呼称)のものは同じくバックトラッキングとリストモナドを用いたもの、下のものはしらみ潰し探索法を用いたものです。しらみつぶし探索法は一般には遅いのですが、とてもコーディングしやすいという利点があります。
計測結果(単位:sec)
N | Etsukata | LP | pi8027 |
---|---|---|---|
9 | 0.23 | 0.73 | 2.73 |
10 | 1.06 | 3.84 | 27.33 |
11 | 5.44 | 23.83 | -- |
なんで速いの?
しらみ潰し探索法よりもバックトラッキングのほうが速いのは明らかです。
LPよりも自分のものが速いのは、確かめる必要の無い組み合わせを予め省いているからです。メモリの使用量も半分以下で済みます。
もっと速くてわかりやすい書き方がある場合は是非おしえてください!
0 件のコメント:
コメントを投稿