Haskell で nクィーン問題を解く
Transcript of Haskell で nクィーン問題を解く
THE “N
QUEENS
PROMBELM
” WITH
HASKELL
H A S K E L L 初心
者の
挑戦
1
自己紹介
名前 : @mitstream友達がいないのでつぶやいていません
趣味 :最近は Haskellだから友達ができないのかも
言語 :初めてちゃんと勉強するのが Haskell あと Fortran をちょこっとかじったりした
拠点 :茨城県神栖市
← 神栖市イメージキャラクター カミスココくん
© 神栖市
2
HASKELL の練習をしたいので…
今日は” N Queens Problem” を Haskell で解いてみたいと思います。
3
N QUEENS PROBLEM とは?N Queens Problem (N クィーン問題)
+ N×N のチェス盤に N 個のクィーンを配置する
+ どのクィーンもお互いに取られる位置にあってはならない
+ N 個のクィーンの配置は何通りあるだろうか?
コレは取られないので OK
コレは取られるので NG
4
答えの例 ( N = 8 の場合)
5
どのように解くか?
今回は全候補を”ふるい”にかける作戦で。
ふるい???
答えの候補をたくさんあげて、後から条件に合致しないものを排除していくと、最後に残ったものが探していた答え
という作戦
6
どのように解くか?
【ふるい①】タテ・ヨコに重複が無い
“a” の列 :1,2,3,4,5,6,7,8 から 1 つ…例えば (a,5)“b” の列 :1,2,3,4,6,7,8 から 1 つ …例えば (b,3)“c” の列 :1,2,4,6,7,8 から 1 つ …例えば (c,8)“d” の列 :1,2,4,6,7 から 1 つ …例えば (d,1)
このような配置は N! 通りある。大切なのは列(タテ)だけ
配置はリストで表すことができそう。
…
[5,3,8,1,2,4,7,6]
7
どのように解くか 【ふるい①】
タテ・ヨコに重複がない
配置をリストで表現できる
ふるい①を”くぐり抜けた”配置は N! 通り
permutations [1..n]
こんなのを思い浮かべつつ…
Prelude Data.List> permutations [1..3][[1,2,3],[2,1,3],[3,2,1],[2,3,1],[3,1,2],[1,3,2]
8
どのように解くか?【ふるい②】ナナメに重複がない
“ ふるい①”を通り抜けたものから
ナナメにクィーンが並ぶものを排除
で、どうやってリストで表現する?
(例) [5,3,6,2,4,1,7,8]…(c,6) と (e,4) がナナメ
ナナメに並ぶ = タテの距離 == ヨコの距離
⇔ 3 番目の” 6” ` ナナメに並ぶ ` 5 番目の” 4” | 3 - 5 | == | 6 – 4 | こういうものは排除する
×
1 2 3 4 5 6 7 8
9
どのように解くか 【ふるい②】
ナナメに重複がない
タテの距離 ≠ ヨコの距離
abs((q !! i) - (q !! j)) /= j - i
こっちはこんなパーツが入るかな…
q = [ 配置候補 ] の i 番目と j 番目がナナメにない
Prelude> [1..10] !! 34Prelude> abs (3-10)7
0 から数えて 3 番目の要素を返す
絶対値を返す
10
コードを書いてみる①
nQueensProblem :: Int -> [[Int]]
型を考えるのが Haskell の王道
答えを返す関数の名前は nQueensProblem にしよう
クィーンの数 n を受け取って配置の候補を返すから…
11
コードを書いてみる②
nQueensProblem :: Int -> [[Int]]
書き始めはこんな感じかな?
nQueensProblem n = candidateFrom $permutations [1..n] where candidateFrom = filter (hasNoDiagonals positions)
あとは hasNoDiagonals: 対角に並ばない条件 positions :2 つの駒の位置を付け加えて「対角に並ばない配置」だけを選ぶ。 candidateFrom はそういう関数。
0 から数えて 3 番目の要素を返すPrelude> filter (>3) [1..5][4,5]
12
できあがりのコード
import Data.ListnQueensProblem :: Int -> [[Int]]nQueensProblem n = candidateFrom $permutations [1..n] where candidateFrom = filter (hasNoDiagonals positions) positions = [(i,j) | i <- [0..n-2], j <- [i+1 .. n-1]] hasNoDiagonals [] _ = True hasNoDiagonals positions@(ij:rest) q = notinDiagonal ij q && hasNoDiagonals rest q where notinDiagonal (i,j) q = abs((q !! i) - (q !! j)) /= j - i
0 から数えて 3 番目の要素を返す
hasNoDiagonal は 2 つの駒の取り得る位置のリストと配置を表すリストを取って True or False を返す関数(Data.List は permutations を含むモジュール )
13
計算してみよう
*Main> length $nQueensProblem 892(0.69 secs, 70480272 bytes)
*Main> length $nQueensProblem 9352(7.24 secs, 760744596 bytes)
*Main> length $nQueensProblem 10724(82.60 secs, 8414580832 bytes)
ふむふむ…
14
めでたし めでたし…?
15
これで終わってよいのだろうか?*Main> length $nQueensProblem 892
8 個のクィーンを置く方法は 92 通りと分かったが…
これらを“異なる配置”として数えて良いのだろうか?
a b c d e f g h
12
876543
90o回転くるっと
[6,3,5,7,1,4,2,8] [8,4,1,3,6,2,7,5]
16
もう 1 歩踏み込んでみる
これらはチェス盤上の駒の配置としては等価
[6,3,5,7,1,4,2,8] [8,4,1,3,6,2,7,5] [1,7,5,8,2,4,6,3] [4,2,7,3,6,8,5,1]
[3,6,4,2,8,5,7,1] [1,5,8,6,3,7,2,4] [8,2,4,1,7,5,3,6] [5,7,2,6,3,1,4,8]
17
改めて N クィーン問題(重複なし)N Queens Problem (N クィーン問題)
+ N×N のチェス盤に N 個のクィーンを配置する
+ どのクィーンもお互いに取られる位置にあってはならない
+ N 個のクィーンの配置は何通りあるだろうか?
+ ただし盤面の回転・反転で重なる場合は同じ配置とみなす
18
反転をどのように表現するか
簡単な方から片付けよう
[6,3,5,7,1,4,2,8]
[3,6,4,2,8,5,7,1]上と下を足すと全部同じ数だね
上下の反転 ;リストの各要素 x を (n+1) – xに置き換えれば良い
map (n+1-) qs
注意: qs は駒の配置を表すリスト(例えば [6,3,5,7,1,4,2,8] )
Prelude> map (9-) [6,3,5,7,1,4,2,8][3,6,4,2,8,5,7,1]
map : リストの各要素に関数を適用する
19
反転をどのように表現するか(別案)
反転は左右にしたって構わない
好きな方を使えばいいよ
reverse qs
[6,3,5,7,1,4,2,8] [8,2,4,1,7,5,3,6]
これなら reverse 一発 !!
Prelude> reverse [6,3,5,7,1,4,2,8][8,2,4,1,7,5,3,6]
20
回転をどのように表現するか
ここで注目 !!
対角反転左右反転reverse
21
対角反転をどのように表現するか
( 3 , g )
1 2 3 4 5 6 7 8“c” は 左から 3 番目…“g” は 左から 7 番目…
3 7
対角反転は…“ i 番目の要素が x のリスト” を受け取って“ x 番目の要素が i のリスト” を返す関数で表現することができる
( 7 , c)
22
対角反転と左右反転で回転を表現する
せ、せまい…
“ 対角反転” = map snd $sort (zip qs [1..])Prelude> zip [6,3,5,7,1,4,2,8] [1..][(6,1),(3,2),(5,3),(7,4),(1,5),(4,6),(2,7),(8,8)]
Prelude Data.List> sort [(6,1),(3,2),(5,3),(7,4),(1,5),(4,6),(2,7),(8,8)][(1,5),(2,7),(3,2),(4,6),(5,3),(6,1),(7,4),(8,8)]
Prelude> snd (1,5)5
Prelude> map snd [(1,5),(2,7),(3,2),(4,6),(5,3),(6,1),(7,4),(8,8)][5,7,2,6,3,1,4,8]
” 対角反転” したものを左右反転 (reverse) すれば 90° 回転になる !!
23
コードを追加する①
roateBoard :: [Int] -> [Int]
4 回続けると 1 回転するね
rotateBoard qs = reverse $map snd $sort (zip qs [1..])
配置を受け取って 90° 回転した配置を返す関数をrotateBoard とする
24
コードを追加する②
roateBoard :: [Int] -> [Int]
iterate は便利
rotateBoard qs = reverse $map snd $sort (zip qs [1..])
equivalentPositions :: [Int] -> [[Int]]equivalentPositions qs = nub (rotate ++ map reverse rotate) where rotate = take 4 $iterate rotateBoard qs
配置を受け取ってそれと等価な配置を返す関数をequivalentPositions とする
Prelude Data.List> nub [[1,2,3],[2,3,1],[1,2,3]][[1,2,3],[2,3,1]] nub : 重複を除いたリストを返す
25
コードを追加する③
deleteEquivalent :: [[Int]] -> [[Int]]deleteEquivalent [] = []deleteEquivalent (q:qs) = q : deleteEquivalent restOfLists where restOfLists = [x | x <- qs, not $elem x (equivalentPositions q)]
deleteEquivalent は 配置のリスト(リストのリスト)を受け取って“等価な配置”を取り除いた配置のリストを返す
nub : 重複を除いたリストを返す
equivalentPositions で定義された”等価な配置”から 1 つを残して残りは消してしまうんだ。
26
後半で追加したコードroateBoard :: [Int] -> [Int]rotateBoard qs = reverse $map snd $sort (zip qs [1..])
equivalentPositions :: [Int] -> [[Int]]equivalentPositions qs = nub (rotate ++ map reverse rotate) where rotate = take 4 $iterate rotateBoard qs
deleteEquivalent :: [[Int]] -> [[Int]]deleteEquivalent [] = []deleteEquivalent (q:qs) = q : deleteEquivalent restOfLists where restOfLists = [x | x <- qs, not $elem x (equivalentPositions q)]
nQueensProblemUnique :: Int -> [[Int]]nQueensProblemUnique n = deleteEquivalent $nQueensProblem n
最後の 2行で、前半で求めた重複ありの配置リストをdeleteEquivalent に作用させ “等価な配置” を削除する
27
計算してみよう
*Main> length $nQueensProblemUnique 812(0.64 secs, 80219900 bytes)
*Main> length $nQueensProblemUnique 946(6.30 secs, 826290056 bytes)
*Main> length $nQueensProblemUnique 1092(69.11 secs, 8753169700 bytes)
ようやく解決
28
まとめ
Haskell で n クィーン問題を解いてみた
8×8 盤ではクィーンの配置は 92 通り。そのうち回転・反転で重なるものを除くと、配置は 12 通り。
29
いつか Haskeller を名乗りたいな
Prelude> nQueensProblemcomplete
30