コンパイル時に8つのクイーンのパズルを解くことができますか?
適切な出力形式を選択します。
私は特にC ++テンプレートメタプログラミングソリューションに興味がありますが、たとえばHaskellの型システムなど、同様の構造を持つ言語を使用できます。
理想的には、メタプログラムはすべてのソリューションを出力します。ハードコーディングなし。
コンパイル時に8つのクイーンのパズルを解くことができますか?
適切な出力形式を選択します。
私は特にC ++テンプレートメタプログラミングソリューションに興味がありますが、たとえばHaskellの型システムなど、同様の構造を持つ言語を使用できます。
理想的には、メタプログラムはすべてのソリューションを出力します。ハードコーディングなし。
回答:
私のメタプログラムは、92のソリューションすべてを見つけます。エラーメッセージとして出力されます。
error: 'solution' is not a member of 'print<15863724>'
これは、最初のクイーンがy = 1に、2番目がy = 5に、3番目がy = 8に、というように配置されることを意味します。
まず、いくつかの便利なメタ関数:
template <typename T>
struct return_
{
typedef T type;
};
template <bool Condition, typename Then, typename Else>
struct if_then_else;
template <typename Then, typename Else>
struct if_then_else<true, Then, Else> : return_<Then> {};
template <typename Then, typename Else>
struct if_then_else<false, Then, Else> : return_<Else> {};
template <int N>
struct constant
{
enum { value = N };
};
template <int N>
struct print
{
// empty body -> member access yields a compiler error involving N
};
次に、2つの興味深いメタ関数(単数形と複数形に注意してください):
template <int queens, int rows, int sums, int difs, int x, int y>
struct put_queen;
template <int queens, int rows, int sums, int difs, int x>
struct put_queens : constant
< put_queen<queens, rows, sums, difs, x, 1>::value
+ put_queen<queens, rows, sums, difs, x, 2>::value
+ put_queen<queens, rows, sums, difs, x, 3>::value
+ put_queen<queens, rows, sums, difs, x, 4>::value
+ put_queen<queens, rows, sums, difs, x, 5>::value
+ put_queen<queens, rows, sums, difs, x, 6>::value
+ put_queen<queens, rows, sums, difs, x, 7>::value
+ put_queen<queens, rows, sums, difs, x, 8>::value > {};
template <int queens, int rows, int sums, int difs, int x, int y>
struct put_queen : if_then_else<
rows & (1 << y) || sums & (1 << (x + y)) || difs & (1 << (8 + x - y)),
constant<0>,
put_queens<queens * 10 + y, rows | (1 << y), sums | (1 << (x + y)),
difs | (1 << (8 + x - y)), x + 1>
>::type {};
この変数にqueens
は、これまでにボードに配置されたクイーンのy座標が格納されます。次の3つの変数には、すでにクイーンが占有している行と対角線が格納されます。
自明x
であるy
べきです。
if_then_else
現在の位置がブロックされているかどうかを確認する最初の引数。そうである場合、(意味のない)結果0を返すことによって再帰が停止します。そうでない場合、クイーンがボードに配置され、プロセスは次の列に進みます。
xが8に達すると、解決策が見つかりました。
template <int queens, int rows, int sums, int difs>
struct put_queens<queens, rows, sums, difs, 8>
{
enum { value = print<queens>::solution };
};
print
テンプレートにはmemberがないため、solution
コンパイラーはエラーを生成します。
そして最後に、プロセスを開始するためvalue
に、空のボードのメンバーを検査します。
int go = put_queens<0, 0, 0, 0, 0>::value;
完全なプログラムはideoneにあります。
Haskell型システムを使用するソリューションを思い付きました。私は値レベルで問題の既存の解決策を少しグーグルで調べ、それを少し変更してから、タイプレベルに上げました。多くの再発明が必要でした。また、多数のGHC拡張機能を有効にする必要がありました。
まず、整数は型レベルでは許可されないため、今回は型として自然数をもう一度作り直す必要がありました。
data Zero -- type that represents zero
data S n -- type constructor that constructs the successor of another natural number
-- Some numbers shortcuts
type One = S Zero
type Two = S One
type Three = S Two
type Four = S Three
type Five = S Four
type Six = S Five
type Seven = S Six
type Eight = S Seven
私が適応したアルゴリズムは、自然数に対して加算と減算を行うため、これらも再発明する必要がありました。型レベルの関数は、型クラスを使用して定義されます。これには、複数のパラメータータイプクラスと機能の依存関係の拡張が必要です。型クラスは「値を返す」ことはできないため、PROLOGと同様の方法で追加のパラメーターを使用します。
class Add a b r | a b -> r -- last param is the result
instance Add Zero b b -- 0 + b = b
instance (Add a b r) => Add (S a) b (S r) -- S(a) + b = S(a + b)
class Sub a b r | a b -> r
instance Sub a Zero a -- a - 0 = a
instance (Sub a b r) => Sub (S a) (S b) r -- S(a) - S(b) = a - b
再帰はクラスアサーションで実装されるため、構文は少し逆になります。
次はブール値でした:
data True -- type that represents truth
data False -- type that represents falsehood
そして、不等式比較を行う関数:
class NotEq a b r | a b -> r
instance NotEq Zero Zero False -- 0 /= 0 = False
instance NotEq (S a) Zero True -- S(a) /= 0 = True
instance NotEq Zero (S a) True -- 0 /= S(a) = True
instance (NotEq a b r) => NotEq (S a) (S b) r -- S(a) /= S(b) = a /= b
そしてリスト...
data Nil
data h ::: t
infixr 0 :::
class Append xs ys r | xs ys -> r
instance Append Nil ys ys -- [] ++ _ = []
instance (Append xs ys rec) => Append (x ::: xs) ys (x ::: rec) -- (x:xs) ++ ys = x:(xs ++ ys)
class Concat xs r | xs -> r
instance Concat Nil Nil -- concat [] = []
instance (Concat xs rec, Append x rec r) => Concat (x ::: xs) r -- concat (x:xs) = x ++ concat xs
class And l r | l -> r
instance And Nil True -- and [] = True
instance And (False ::: t) False -- and (False:_) = False
instance (And t r) => And (True ::: t) r -- and (True:t) = and t
if
sも型レベルで欠落しています...
class Cond c t e r | c t e -> r
instance Cond True t e t -- cond True t _ = t
instance Cond False t e e -- cond False _ e = e
そして、それで、私が使用したすべての支援機械が設置されました。問題自体に取り組む時間です!
既存のボードにクイーンを追加してもよいかどうかをテストする機能から始めます。
-- Testing if it's safe to add a queen
class Safe x b n r | x b n -> r
instance Safe x Nil n True -- safe x [] n = True
instance (Safe x y (S n) rec,
Add c n cpn, Sub c n cmn,
NotEq x c c1, NotEq x cpn c2, NotEq x cmn c3,
And (c1 ::: c2 ::: c3 ::: rec ::: Nil) r) => Safe x (c ::: y) n r
-- safe x (c:y) n = and [ x /= c , x /= c + n , x /= c - n , safe x y (n+1)]
クラスアサーションを使用して中間結果を取得することに注意してください。戻り値は実際には追加のパラメーターであるため、互いに直接アサーションを呼び出すことはできません。繰り返しますが、PROLOGを使用したことがある場合は、このスタイルが少し馴染みがあるかもしれません。
ラムダの必要性を削除するためにいくつかの変更を行った後(実装することもできましたが、別の日に残すことにしました)、これは元のソリューションのように見えました:
queens 0 = [[]]
-- The original used the list monad. I "unrolled" bind into concat & map.
queens n = concat $ map f $ queens (n-1)
g y x = if safe x y 1 then [x:y] else []
f y = concat $ map (g y) [1..8]
map
高階関数です。私は実装思ったより高次のメタ関数は、私はちょうど簡単な解決策と一緒に行ったので、あまり手間(再びラムダ)のようになります。私は、関数がマッピングされるかを知るために、私は専門のバージョンを実装することができmap
たものではないのでことを、それぞれのを高階関数。
-- Auxiliary meta-functions
class G y x r | y x -> r
instance (Safe x y One s, Cond s ((x ::: y) ::: Nil) Nil r) => G y x r
class MapG y l r | y l -> r
instance MapG y Nil Nil
instance (MapG y xs rec, G y x g) => MapG y (x ::: xs) (g ::: rec)
-- Shortcut for [1..8]
type OneToEight = One ::: Two ::: Three ::: Four ::: Five ::: Six ::: Seven ::: Eight ::: Nil
class F y r | y -> r
instance (MapG y OneToEight m, Concat m r) => F y r -- f y = concat $ map (g y) [1..8]
class MapF l r | l -> r
instance MapF Nil Nil
instance (MapF xs rec, F x f) => MapF (x ::: xs) (f ::: rec)
そして、最後のメタ関数を今書くことができます:
class Queens n r | n -> r
instance Queens Zero (Nil ::: Nil)
instance (Queens n rec, MapF rec m, Concat m r) => Queens (S n) r
残っているのは、型検査機械を解決して解決策を引き出すための何らかのドライバーです。
-- dummy value of type Eight
eight = undefined :: Eight
-- dummy function that asserts the Queens class
queens :: Queens n r => n -> r
queens = const undefined
このメタプログラムは型チェッカーで実行されることになっているため、起動して次の型ghci
を要求できますqueens eight
。
> :t queens eight
これは、デフォルトの再帰制限をかなり高速で超えます(わずか20です)。この制限を増やすにghci
は、-fcontext-stack=N
オプションを使用して呼び出す必要があります。ここN
で、希望するスタックの深さを指定します(N = 1000および15分では不十分です)。非常に長い時間がかかるため、この実行が完了するまでまだ見ていませんが、何とか実行できましたqueens four
。
あります完全なプログラムは、かなりの結果の種類を印刷するためのいくつかの機械でideoneに、そこだけqueens two
限界を超えることなく実行することができます:(
ANSI委員会は、Cプリプロセッサをチューリング完全になるまで拡張しないという意識的な選択をしたと思います。いずれにせよ、8クイーンの問題を解決するほど強力ではありません。一般的な方法ではありません。
ただし、ループカウンターをハードコーディングする場合は、実行できます。もちろん、実際にループする方法はありませんが、自己包含(を介して#include __FILE__
)を使用して、限られた種類の再帰を取得できます。
#ifdef i
# if (r_(i) & 1 << j_(i)) == 0 && (p_(i) & 1 << i + j_(i)) == 0 \
&& (n_(i) & 1 << 7 + i - j_(i)) == 0
# if i == 0
# undef i
# define i 1
# undef r1
# undef p1
# undef n1
# define r1 (r0 | (1 << j0))
# define p1 (p0 | (1 << j0))
# define n1 (n0 | (1 << 7 - j0))
# undef j1
# define j1 0
# include __FILE__
# undef j1
# define j1 1
# include __FILE__
# undef j1
# define j1 2
# include __FILE__
# undef j1
# define j1 3
# include __FILE__
# undef j1
# define j1 4
# include __FILE__
# undef j1
# define j1 5
# include __FILE__
# undef j1
# define j1 6
# include __FILE__
# undef j1
# define j1 7
# include __FILE__
# undef i
# define i 0
# elif i == 1
# undef i
# define i 2
# undef r2
# undef p2
# undef n2
# define r2 (r1 | (1 << j1))
# define p2 (p1 | (1 << 1 + j1))
# define n2 (n1 | (1 << 8 - j1))
# undef j2
# define j2 0
# include __FILE__
# undef j2
# define j2 1
# include __FILE__
# undef j2
# define j2 2
# include __FILE__
# undef j2
# define j2 3
# include __FILE__
# undef j2
# define j2 4
# include __FILE__
# undef j2
# define j2 5
# include __FILE__
# undef j2
# define j2 6
# include __FILE__
# undef j2
# define j2 7
# include __FILE__
# undef i
# define i 1
# elif i == 2
# undef i
# define i 3
# undef r3
# undef p3
# undef n3
# define r3 (r2 | (1 << j2))
# define p3 (p2 | (1 << 2 + j2))
# define n3 (n2 | (1 << 9 - j2))
# undef j3
# define j3 0
# include __FILE__
# undef j3
# define j3 1
# include __FILE__
# undef j3
# define j3 2
# include __FILE__
# undef j3
# define j3 3
# include __FILE__
# undef j3
# define j3 4
# include __FILE__
# undef j3
# define j3 5
# include __FILE__
# undef j3
# define j3 6
# include __FILE__
# undef j3
# define j3 7
# include __FILE__
# undef i
# define i 2
# elif i == 3
# undef i
# define i 4
# undef r4
# undef p4
# undef n4
# define r4 (r3 | (1 << j3))
# define p4 (p3 | (1 << 3 + j3))
# define n4 (n3 | (1 << 10 - j3))
# undef j4
# define j4 0
# include __FILE__
# undef j4
# define j4 1
# include __FILE__
# undef j4
# define j4 2
# include __FILE__
# undef j4
# define j4 3
# include __FILE__
# undef j4
# define j4 4
# include __FILE__
# undef j4
# define j4 5
# include __FILE__
# undef j4
# define j4 6
# include __FILE__
# undef j4
# define j4 7
# include __FILE__
# undef i
# define i 3
# elif i == 4
# undef i
# define i 5
# undef r5
# undef p5
# undef n5
# define r5 (r4 | (1 << j4))
# define p5 (p4 | (1 << 4 + j4))
# define n5 (n4 | (1 << 11 - j4))
# undef j5
# define j5 0
# include __FILE__
# undef j5
# define j5 1
# include __FILE__
# undef j5
# define j5 2
# include __FILE__
# undef j5
# define j5 3
# include __FILE__
# undef j5
# define j5 4
# include __FILE__
# undef j5
# define j5 5
# include __FILE__
# undef j5
# define j5 6
# include __FILE__
# undef j5
# define j5 7
# include __FILE__
# undef i
# define i 4
# elif i == 5
# undef i
# define i 6
# undef r6
# undef p6
# undef n6
# define r6 (r5 | (1 << j5))
# define p6 (p5 | (1 << 5 + j5))
# define n6 (n5 | (1 << 12 - j5))
# undef j6
# define j6 0
# include __FILE__
# undef j6
# define j6 1
# include __FILE__
# undef j6
# define j6 2
# include __FILE__
# undef j6
# define j6 3
# include __FILE__
# undef j6
# define j6 4
# include __FILE__
# undef j6
# define j6 5
# include __FILE__
# undef j6
# define j6 6
# include __FILE__
# undef j6
# define j6 7
# include __FILE__
# undef i
# define i 5
# elif i == 6
# undef i
# define i 7
# undef r7
# undef p7
# undef n7
# define r7 (r6 | (1 << j6))
# define p7 (p6 | (1 << 6 + j6))
# define n7 (n6 | (1 << 13 - j6))
# undef j7
# define j7 0
# include __FILE__
# undef j7
# define j7 1
# include __FILE__
# undef j7
# define j7 2
# include __FILE__
# undef j7
# define j7 3
# include __FILE__
# undef j7
# define j7 4
# include __FILE__
# undef j7
# define j7 5
# include __FILE__
# undef j7
# define j7 6
# include __FILE__
# undef j7
# define j7 7
# include __FILE__
# undef i
# define i 6
# elif i == 7
printf("(1 %d) (2 %d) (3 %d) (4 %d) (5 %d) (6 %d) (7 %d) (8 %d)\n",
j0 + 1, j1 + 1, j2 + 1, j3 + 1, j4 + 1, j5 + 1, j6 + 1, j7 + 1);
# endif
# endif
#else
#include <stdio.h>
#define _cat(a, b) a ## b
#define j_(i) _cat(j, i)
#define n_(i) _cat(n, i)
#define p_(i) _cat(p, i)
#define r_(i) _cat(r, i)
int main(void)
{
# define i 0
# define j0 0
# include __FILE__
# undef j0
# define j0 1
# include __FILE__
# undef j0
# define j0 2
# include __FILE__
# undef j0
# define j0 3
# include __FILE__
# undef j0
# define j0 4
# include __FILE__
# undef j0
# define j0 5
# include __FILE__
# undef j0
# define j0 6
# include __FILE__
# undef j0
# define j0 7
# include __FILE__
# undef j0
return 0;
}
#endif
膨大な量の繰り返しコンテンツがあるにもかかわらず、8つのクイーンの問題をアルゴリズム的に本当に解決していることを保証させてください。残念ながら、プリプロセッサではできなかったことの1つは、一般的なプッシュダウンスタックデータ構造を実装することです。結果は、i
設定する別の値を選択するために使用された場所の値をハードコーディングしなければならなかったということです。(値を取得するのとは対照的に、これは一般的には完全に実行できます。そのため#if
、現在の位置に女王を追加できるかどうかを決定するファイルの最上部を8回繰り返す必要はありません。)
プリプロセッサのコード内では、i
とj
考えられて現在の位置を示し、一方r
、p
、およびn
ランク付けし、対角線が現在配置のため利用できないこれを追跡します。ただし、i
現在の再帰の深さを示すカウンターとしても機能するため、実際には他のすべての値は実際にiを添え字の一種として使用し、再帰から再開するときに値が保持されるようにします。(また、完全に置き換えずにプリプロセッサシンボルの値を変更することは非常に困難です。)
コンパイルされたプログラムは、92のソリューションすべてを印刷します。ソリューションは実行可能ファイルに直接埋め込まれます。プリプロセッサの出力は次のようになります。
/* ... #included content from <stdio.h> ... */
int main(void)
{
printf("(1 %d) (2 %d) (3 %d) (4 %d) (5 %d) (6 %d) (7 %d) (8 %d)\n",
0 + 1, 4 + 1, 7 + 1, 5 + 1, 2 + 1, 6 + 1, 1 + 1, 3 + 1);
printf("(1 %d) (2 %d) (3 %d) (4 %d) (5 %d) (6 %d) (7 %d) (8 %d)\n",
0 + 1, 5 + 1, 7 + 1, 2 + 1, 6 + 1, 3 + 1, 1 + 1, 4 + 1);
printf("(1 %d) (2 %d) (3 %d) (4 %d) (5 %d) (6 %d) (7 %d) (8 %d)\n",
0 + 1, 6 + 1, 3 + 1, 5 + 1, 7 + 1, 1 + 1, 4 + 1, 2 + 1);
/* ... 88 more solutions ... */
printf("(1 %d) (2 %d) (3 %d) (4 %d) (5 %d) (6 %d) (7 %d) (8 %d)\n",
7 + 1, 3 + 1, 0 + 1, 2 + 1, 5 + 1, 1 + 1, 6 + 1, 4 + 1);
return 0;
}
明らかにそうすべきではないが、それはできる。
テンプレートなしのC ++ 11ソリューションを次に示します。
constexpr int trypos(
int work, int col, int row, int rows, int diags1, int diags2,
int rowbit, int diag1bit, int diag2bit);
constexpr int place(
int result, int work, int col, int row, int rows, int diags1, int diags2)
{
return result != 0 ? result
: col == 8 ? work
: row == 8 ? 0
: trypos(work, col, row, rows, diags1, diags2,
1 << row, 1 << (7 + col - row), 1 << (14 - col - row));
}
constexpr int trypos(
int work, int col, int row, int rows, int diags1, int diags2,
int rowbit, int diag1bit, int diag2bit)
{
return !(rows & rowbit) && !(diags1 & diag1bit) && !(diags2 & diag2bit)
? place(
place(0, work*10 + 8-row, col + 1, 0,
rows | rowbit, diags1 | diag1bit, diags2 | diag2bit),
work, col, row + 1, rows, diags1, diags2)
: place(0, work, col, row + 1, rows, diags1, diags2);
}
int places = place(0, 0, 0, 0, 0, 0, 0);
FredOverflowの回答のように、ソリューションは10進数としてエンコードされます。GCC 4.7.1は、上記のファイルを次のアセンブリソースにコンパイルしますg++ -S -std=c++11 8q.cpp
。
.file "8q.cpp"
.globl places
.data
.align 4
.type places, @object
.size places, 4
places:
.long 84136275
.ident "GCC: (GNU) 4.7.1"
.section .note.GNU-stack,"",@progbits
シンボルの値places
は84136275です。つまり、最初のクイーンはa8、2番目はb4などになります。
テンプレートクラスが1つだけ定義されているc ++テンプレート:
template <int N, int mask, int mask2, int mask3, int remainDigit, bool fail>
struct EQ;
template <int N, int mask, int mask2, int mask3>
struct EQ<N, mask, mask2, mask3, 0, false> {
enum _ { Output = (char [N])1 };
};
template <int N, int mask, int mask2, int mask3, int i>
struct EQ<N, mask, mask2, mask3, i, true> { };
template <int N, int mask, int mask2, int mask3, int i>
struct EQ<N, mask, mask2, mask3, i, false> {
enum _ { _ =
sizeof(EQ<N*10+1, mask|(1<<1), mask2|(1<<(1+i)), mask3|(1<<(1+8-i)), i-1,
(bool)(mask&(1<<1)) || (bool)(mask2&(1<<(1+i))) || (bool)(mask3&(1<<(1+8-i)))>) +
sizeof(EQ<N*10+2, mask|(1<<2), mask2|(1<<(2+i)), mask3|(1<<(2+8-i)), i-1,
(bool)(mask&(1<<2)) || (bool)(mask2&(1<<(2+i))) || (bool)(mask3&(1<<(2+8-i)))>) +
sizeof(EQ<N*10+3, mask|(1<<3), mask2|(1<<(3+i)), mask3|(1<<(3+8-i)), i-1,
(bool)(mask&(1<<3)) || (bool)(mask2&(1<<(3+i))) || (bool)(mask3&(1<<(3+8-i)))>) +
sizeof(EQ<N*10+4, mask|(1<<4), mask2|(1<<(4+i)), mask3|(1<<(4+8-i)), i-1,
(bool)(mask&(1<<4)) || (bool)(mask2&(1<<(4+i))) || (bool)(mask3&(1<<(4+8-i)))>) +
sizeof(EQ<N*10+5, mask|(1<<5), mask2|(1<<(5+i)), mask3|(1<<(5+8-i)), i-1,
(bool)(mask&(1<<5)) || (bool)(mask2&(1<<(5+i))) || (bool)(mask3&(1<<(5+8-i)))>) +
sizeof(EQ<N*10+6, mask|(1<<6), mask2|(1<<(6+i)), mask3|(1<<(6+8-i)), i-1,
(bool)(mask&(1<<6)) || (bool)(mask2&(1<<(6+i))) || (bool)(mask3&(1<<(6+8-i)))>) +
sizeof(EQ<N*10+7, mask|(1<<7), mask2|(1<<(7+i)), mask3|(1<<(7+8-i)), i-1,
(bool)(mask&(1<<7)) || (bool)(mask2&(1<<(7+i))) || (bool)(mask3&(1<<(7+8-i)))>) +
sizeof(EQ<N*10+8, mask|(1<<8), mask2|(1<<(8+i)), mask3|(1<<(8+8-i)), i-1,
(bool)(mask&(1<<8)) || (bool)(mask2&(1<<(8+i))) || (bool)(mask3&(1<<(8+8-i)))>)};
};
int main(int argc, _TCHAR* argv[])
{
// output all solutions to eight queens problems as error messages
sizeof(EQ<0, 0, 0, 0, 8, false>);
return 0;
}
そのため、エラーメッセージは次のようになります。
エラーC2440:「型キャスト」:「int」から「char [15863724]」に変換できません