在编译时解决八皇后问题[关闭]


39

您可以在编译时解决八个皇后难题吗?

选择任何合适的输出格式。

我对C ++模板元编程解决方案特别感兴趣,但是您可以使用具有类似构造的语言,例如Haskell的类型系统。

理想情况下,您的元程序将输出所有解决方案。没有硬编码。


您为什么不允许其他语言?
用户未知

@user:因为我对C ++ TMP解决方案感兴趣。但是,如果您知道一种具有相似结构的语言,请随时发布答案。
R. Martinho Fernandes

我还可以使用Haskell的字体系统吗?AFAIK应该已经完成​​。
FUZxxl 2011年

@FUZxxl:是的。我将编辑问题。
R. Martinho Fernandes

进行强力解决方案是否足够?
停止转动逆时针

Answers:


50

我的元程序找到所有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上找到。


2
我喜欢:1)使用位域存储数据,2)选择输出方法。
R. Martinho Fernandes

7
一个答案太棒了。
st0le 2011年

是否还应该输出x值?
DeadMG 2011年

2
@DeadMG每个皇后位置的x值是其在字符串(1-8)中的位置。
Briguy37 2011年

22

我想出了一个使用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在不超出限制的情况下运行:(


除了有趣的解决方案之外,这是对类/实例逻辑可以完成的操作的有趣参考
Michael Klein

11

C,通过预处理器

我认为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文件顶部的,它决定是否可以在当前位置添加一个皇后,而不必重复八次。)

在预处理程序代码,ij显示当前位置正在考虑,同时rpn跟踪哪些队伍和对角线目前放置不可用。但是,它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;
}

即使显然不应该这样做,也可以做到。


7

这是一个没有任何模板的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等处。


0

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]”

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.