Help us understand the problem. What is going on with this article?

QueensパズルをElixirで -Elixir見習いの実習-

More than 1 year has passed since last update.

はじめに

Elixirに習熟するために基本的なコードを書いています。Nqueens、女王問題に取り組みました。

女王問題とは

チェスのqueenは縦、横、斜めに駒を進めて相手の駒を取りことができます。8Queensであれば8×8の盤面で8個のqueenの駒を互いに利き筋にならないように配置するというパズルです。

例えば次のような配置は解のうちの1つです。

(出典 wikipedia)
2018-11-25_15-20-29.png

方策

効率のことはとりあえず考えないで、素朴かつ関数プログラミング的な発想で取り組みました。queenの位置をリストで表します。 [1,2,3,4,5,6,7,8] というリストは図の(a,1)(b,2)(c,3)... にqueenがある配置だと考えることにします。8つの数字が全部異なるのであれば縦横方向への利き筋はきいてないと考えることができます。そうすると斜めに効いてるかどうかを調べればいいはずです。1の隣に2がある場合には利き筋ですからこれは答えにはなっていません。

まずは1..8の順列を生成することを考えます。その順列の中には答えの配置の順列もあるはずです。これをfilterで篩にかけて答えを探すという方法で考えていきます。

順列の生成

いくつか方法があることが知られています。今回はM.Hiroiさんのページで解説されていたバックトラック法によることにしました。
M.Hiroiさんの解説ページ

解説のページではSchemeでコードが示されています。これはほぼElixirに直訳可能です。再帰を巧みに使っています。foldrはElixirのEnumモジュールにありますので、これを使いました。コードは次のようです。

 def perm_list(ls) do
    perm_list1(ls,[],[])
  end

  def perm_list1([],a,b) do [Enum.reverse(a)|b] end
  def perm_list1(ls,a,b) do
    List.foldr(ls,
              b,
              fn(x,y) -> perm_list1(ls--[x],[x|a],y) end)
  end

これを使うとリストの順列を求めることができます。

iex(14)> P.perm_list([1,2,3])
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
iex(15)>

利き筋のチェック

縦横方向には効いてないことは明らかですから、斜め方向にぶつかるqueenが置いてないかを調べる関数safe/1を考えます。

まずリストの先頭要素を取り出します。これが2番目以降のqueenとぶつかっていないかと調べます。先頭のqueensの位置を表す数とそこからの距離nを使えば判定できます。それをリストの先頭から全部について調べます。コードにすると下記のようになります。

  def safe([]) do true end
  def safe([l|ls]) do
    if safe1(ls,l,1) do
      safe(ls)
    else
      false
    end
  end

  def safe1([],_,_) do true end
  def safe1([l|ls],a,n) do
    if a+n == l || a-n == l do
      false
    else
      safe1(ls,a,n+1)
    end
  end

safe/1がメインです。先頭と残りと位置番号を下請け関数のsafe1/3に渡します。リストが空になるまで再帰により調べて判定を下します。空リストまでたどり着けばセーフ、trueです。

仕上げ

ここまで部品が揃えばあとは簡単です。

1..8までの順列リストを生成します。
それをパイプ演算子でsafeかどうかでふるい落とすfilterに回します。
これだけです。

  def queens(n) do
    Enum.to_list(1..n)
    |> perm_list
    |> Enum.filter(fn(x) -> safe(x) end)
  end

実行すると92個の答えのリストがでてくるはずです。

iex(12)> P.queens(8)
[
  [1, 5, 8, 6, 3, 7, 2, 4],
  [1, 6, 8, 3, 7, 4, 2, 5],
  ...
  [5, ...],
  [...],
  ...
]

lengthで確認してみます。

iex(13)> length(P.queens(8))
92

どうやら、うまくできたようです。

効率の問題

この方法では無駄な点が多くあり、実行速度は遅いのです。順列の中のsafeではない順列もすべて生成してからfilterにかけて除去しているからです。順列生成段階で明らかに解にならないものを生成しなければもっと効率よくなります。

詳しいことはM.Hiroiさんの解説やMITの教科書のSICPに解説があります。

全コード

defmodule P do
  def queens(n) do
    Enum.to_list(1..n)
    |> perm_list
    |> Enum.filter(fn(x) -> safe(x) end)
  end

  def perm_list(ls) do
    perm_list1(ls,[],[])
  end

  def perm_list1([],a,b) do [Enum.reverse(a)|b] end
  def perm_list1(ls,a,b) do
    List.foldr(ls,
              b,
              fn(x,y) -> perm_list1(ls--[x],[x|a],y) end)
  end

  def safe([]) do true end
  def safe([l|ls]) do
    if safe1(ls,l,1) do
      safe(ls)
    else
      false
    end
  end

  def safe1([],_,_) do true end
  def safe1([l|ls],a,n) do
    if a+n == l || a-n == l do
      false
    else
      safe1(ls,a,n+1)
    end
  end

#使われていない。おまけ
  def perm([],a) do :io.write(Enum.reverse(a)) end
  def perm(ls,a) do
    Enum.each(ls,fn(n) ->
                  perm(ls--[n],[n|a]) end)
  end
end





参考文献

N queens problem M.Hiroi

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away