您可以在编译时解决八个皇后难题吗?
选择任何合适的输出格式。
我对C ++模板元编程解决方案特别感兴趣,但是您可以使用具有类似构造的语言,例如Haskell的类型系统。
理想情况下,您的元程序将输出所有解决方案。没有硬编码。
您可以在编译时解决八个皇后难题吗?
选择任何合适的输出格式。
我对C ++模板元编程解决方案特别感兴趣,但是您可以使用具有类似构造的语言,例如Haskell的类型系统。
理想情况下,您的元程序将输出所有解决方案。没有硬编码。
Answers:
我的元程序找到所有92个解决方案。它们被打印为错误消息:
error: 'solution' is not a member of 'print<15863724>'
这意味着第一个皇后应该放置在y = 1,第二个皇后应该放置在y = 5,第三个皇后应该放置在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
};
然后,两个有趣的元函数(注意单数和复数):
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坐标。以下三个变量存储皇后已经占据的行和对角线。
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
模板没有成员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,可能会觉得这种样式有点熟悉。
在进行了一些更改以消除对lambda的需要之后(可以实现,但我决定离开另一天),这就是原始解决方案的样子:
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
是高阶函数。我认为实现高阶元函数会很麻烦(再次针对lambda),所以我只是采用了一个简单的解决方案:由于我知道将映射哪些函数,因此我可以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预处理程序扩展到图灵完成的程度。无论如何,它还不足以解决八皇后问题。不以任何一般方式。
但是,如果您愿意对循环计数器进行硬编码,则可以这样做。当然,没有真正的循环方法,但是您可以使用自我包含(通过#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
尽管有大量重复的内容,但让我向您保证,它确实是在通过算法解决八皇后问题。不幸的是,我无法使用预处理器的一件事是实现了通用的下推堆栈数据结构。结果是我必须对i
用于选择另一个要设置的值的值进行硬编码。(与检索值相反,该操作通常可以完全完成。这就是为什么#if
文件顶部的,它决定是否可以在当前位置添加一个皇后,而不必重复八次。)
在预处理程序代码,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的答案。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处,第二个皇后在b4等处。
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]”