Haskell で nクィーン問題を解く

30
THE “N QUEENS PROMBELM” WITH HASKELL HAS KELL 初初初 初初初 1

Transcript of Haskell で nクィーン問題を解く

Page 1: Haskell で nクィーン問題を解く

THE “N

QUEENS

PROMBELM

” WITH

HASKELL

H A S K E L L 初心

者の

挑戦

1

Page 2: Haskell で nクィーン問題を解く

自己紹介

名前 : @mitstream友達がいないのでつぶやいていません

趣味 :最近は Haskellだから友達ができないのかも

言語 :初めてちゃんと勉強するのが Haskell あと Fortran をちょこっとかじったりした

拠点 :茨城県神栖市

← 神栖市イメージキャラクター カミスココくん

© 神栖市

2

Page 3: Haskell で nクィーン問題を解く

HASKELL の練習をしたいので…

今日は” N Queens Problem” を Haskell で解いてみたいと思います。

3

Page 4: Haskell で nクィーン問題を解く

N QUEENS PROBLEM とは?N Queens Problem (N クィーン問題)

+   N×N のチェス盤に N 個のクィーンを配置する

+  どのクィーンもお互いに取られる位置にあってはならない

+   N 個のクィーンの配置は何通りあるだろうか?

コレは取られないので OK

コレは取られるので NG

4

Page 5: Haskell で nクィーン問題を解く

答えの例 ( N = 8 の場合)

5

Page 6: Haskell で nクィーン問題を解く

どのように解くか?

今回は全候補を”ふるい”にかける作戦で。

ふるい???

答えの候補をたくさんあげて、後から条件に合致しないものを排除していくと、最後に残ったものが探していた答え

という作戦

6

Page 7: Haskell で nクィーン問題を解く

どのように解くか?

【ふるい①】タテ・ヨコに重複が無い

“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

Page 8: Haskell で nクィーン問題を解く

どのように解くか 【ふるい①】

タテ・ヨコに重複がない

配置をリストで表現できる

  ふるい①を”くぐり抜けた”配置は 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

Page 9: Haskell で nクィーン問題を解く

どのように解くか?【ふるい②】ナナメに重複がない

“ ふるい①”を通り抜けたものから

ナナメにクィーンが並ぶものを排除

で、どうやってリストで表現する?

(例) [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

Page 10: Haskell で nクィーン問題を解く

どのように解くか 【ふるい②】

ナナメに重複がない

タテの距離 ≠ ヨコの距離

abs((q !! i) - (q !! j)) /= j - i

こっちはこんなパーツが入るかな…

q = [ 配置候補 ] の i 番目と j 番目がナナメにない

Prelude> [1..10] !! 34Prelude> abs (3-10)7

0 から数えて 3 番目の要素を返す

絶対値を返す

10

Page 11: Haskell で nクィーン問題を解く

コードを書いてみる①

nQueensProblem :: Int -> [[Int]]

型を考えるのが Haskell の王道

答えを返す関数の名前は nQueensProblem にしよう

クィーンの数 n を受け取って配置の候補を返すから…

11

Page 12: Haskell で nクィーン問題を解く

コードを書いてみる②

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

Page 13: Haskell で nクィーン問題を解く

できあがりのコード

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

Page 14: Haskell で nクィーン問題を解く

計算してみよう

*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

Page 15: Haskell で nクィーン問題を解く

めでたし めでたし…?

15

Page 16: Haskell で nクィーン問題を解く

これで終わってよいのだろうか?*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

Page 17: Haskell で nクィーン問題を解く

もう 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

Page 18: Haskell で nクィーン問題を解く

改めて N クィーン問題(重複なし)N Queens Problem (N クィーン問題)

+   N×N のチェス盤に N 個のクィーンを配置する

+  どのクィーンもお互いに取られる位置にあってはならない

+   N 個のクィーンの配置は何通りあるだろうか?

+  ただし盤面の回転・反転で重なる場合は同じ配置とみなす

18

Page 19: Haskell で nクィーン問題を解く

反転をどのように表現するか

簡単な方から片付けよう

[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

Page 20: Haskell で nクィーン問題を解く

反転をどのように表現するか(別案)

反転は左右にしたって構わない

好きな方を使えばいいよ

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

Page 21: Haskell で nクィーン問題を解く

回転をどのように表現するか

ここで注目 !!

対角反転左右反転reverse

21

Page 22: Haskell で nクィーン問題を解く

対角反転をどのように表現するか

( 3 , g )

1 2 3 4 5 6 7 8“c” は 左から 3 番目…“g” は 左から 7 番目…

3 7

対角反転は…“ i 番目の要素が x のリスト” を受け取って“ x 番目の要素が i のリスト” を返す関数で表現することができる

( 7 , c)

22

Page 23: Haskell で nクィーン問題を解く

対角反転と左右反転で回転を表現する

せ、せまい…

“ 対角反転” = 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

Page 24: Haskell で nクィーン問題を解く

コードを追加する①

roateBoard :: [Int] -> [Int]

4 回続けると 1 回転するね

rotateBoard qs = reverse $map snd $sort (zip qs [1..])

配置を受け取って 90° 回転した配置を返す関数をrotateBoard とする

24

Page 25: Haskell で nクィーン問題を解く

コードを追加する②

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

Page 26: Haskell で nクィーン問題を解く

コードを追加する③

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

Page 27: Haskell で nクィーン問題を解く

後半で追加したコード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

Page 28: Haskell で nクィーン問題を解く

計算してみよう

*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

Page 29: Haskell で nクィーン問題を解く

まとめ

Haskell で n クィーン問題を解いてみた

8×8 盤ではクィーンの配置は 92 通り。そのうち回転・反転で重なるものを除くと、配置は 12 通り。

29

Page 30: Haskell で nクィーン問題を解く

いつか Haskeller を名乗りたいな

Prelude> nQueensProblemcomplete

30