一些Prolog代码

研究生阶段接触过Prolog编程,写的程序都比较简单。当时它给我留下很深的印象,你只需要描述规则和行为特征,然后再提出问题,它就会帮助你求解。 当时我对这种东西不以为然,因为这种求解器肯定没有专门的求解器高效,只能当做玩具使用。现在看起来这种观点的确很幼稚。首先自己的实现不一定就有 Prolog本身实现高效,这点可以参考SQL语句和自己查询DBMS;此外许多实验性质的项目,实验人员需要的是快速实验和得到反馈,他们不在乎速度差个50%或者是200%; 但是考虑自己实现一个通用的快速的求解器的时候,那么你得到又是一个Prolog实现。

最近看《七周七语言》里面的Prolog章节,又让我重新试试Prolog程序。这次使用的Gnu Prolog,当时使用应该是的是SWI-Prolog,感觉这两个实现上差别函数蛮大的, 里面的库函数之间的感觉交集也不是很多。编写这些Prolog程序的时候,StackOverflow帮了我不少忙,许多初学者的问题在上面都有直接的答案。

Prolog的求解器本质上是通过DFS来找到解空间的,在搜索期间不是所有的变量都已经绑定值了的,有些变量可能对应的只是一些约束。 我们可以通过在规则里面增加print语句来调试。print可以打印出在某个步骤的时候变量是什么内容。但是如同上面提到的,这些变量不一定是确切的值,也可能是约束。 好在即便是约束,Gnu Prolog也可以使用比较human-readbale的方式打印出来,所以调试并不是件特别困难的事情。

% load file.
% ['test.prolog']

% 使用 verb(obj1, obj2) 行为来定义规则
% 小写字母表示符号
% 大小字母表示变量
% X is Y 来匹配/合一变量和值
% X = Y 和上面类似,但是不会对Y求值。

likes(dirlt, cjy2020).

% likes(Who, cjy2020). Who = dirlt

% ==============================

count(0, []).
count(Value, [_|Tail]) :- count(X, Tail), Value is X + 1.

% Count(X, [1,2,3,4]). X = 4

sum(0, []).
sum(Value, [Head|Tail]) :- sum(X, Tail), Value is Head + X.

% Sum(X, [1,2,3,4]. X = 10

% ==============================
% 描述行为特征而不是执行方式
concat([], List, List).
concat([Head|Tail1], List, [Head|Tail2]) :- concat(Tail1, List, Tail2).

% concat([1,2,3,4], [5,6], X). x = [1,2,3,4,5,6]
% concat(X, [5,6], [1,2,3,4,5,6]). X = [1,2,3,4]

% ==============================
% 求解4x4的数独

sudoku(Puzzle, Solution) :-
    Solution = Puzzle,
    Puzzle = [S11, S12, S13, S14,
              S21, S22, S23, S24,
              S31, S32, S33, S34,
              S41, S42, S43, S44],
    % 值都在1,4范围内
    fd_domain(Puzzle, 1, 4),

    valid_sudoku(
        [
            % 行
            [S11,S12,S13,S14],
            [S21,S22,S23,S24],
            [S31,S32,S33,S34],
            [S41,S42,S43,S44],
            % 列
            [S11,S21,S31,S41],
            [S12,S22,S32,S42],
            [S13,S23,S33,S43],
            [S14,S24,S34,S44],
            % 区域
            [S11,S12,S21,S22],
            [S13,S14,S23,S24],
            [S31,S32,S41,S42],
            [S33,S34,S43,S44]
        ]
    )
.

% 每个对象里面的值都不相同
valid_sudoku([]).
valid_sudoku([Head|Tail]) :-
    fd_all_different(Head),
    valid_sudoku(Tail).

% sudoku([4,1,2,3, _,_,_,_, _,_,_,_, 3,4,_,_], Solution).

% ==============================
% 求解八皇后问题(实际上运行4皇后,为了计算方便)

% column在1-8之间
valid_board([]).
valid_board([(Row, Col) | Tail]) :-
    % fd_domain是约束列表的
    % fd_domain(Row, 1, 8),
    % fd_domain(Col, 1, 8),
    between(1, 4, Col), between(1, 4, Row),
    valid_board(Tail).


% 用continuation收集结果
cols([], []).
cols([(_, Col) | Tail], [Col|Tail1]) :- cols(Tail, Tail1).

diags([], [], []).
diags([(Row, Col) | Tail], [Diag1 | Tail1], [Diag2 | Tail2]) :-
    Diag1 is (Row + Col),
    Diag2 is (Row - Col),
    diags(Tail, Tail1, Tail2).

eight_queen(Board) :-
    Board = [(1,_), (2,_), (3,_), (4,_)], % (5,_), (6,_), (7,_), (8,_)],
    % print(Board),print('\n'),
    % 先要确保里面填充的是有效数字
    valid_board(Board),

    % print(Board),print('\n'),
    cols(Board, Cols),
    diags(Board, Diags1, Diags2),

    fd_all_different(Cols),
    fd_all_different(Diags1),
    fd_all_different(Diags2).

find_eight_queen(Solution) :-
    eight_queen([(1,A),(2,B),(3,C),(4,D)]), %(5,E),(6,F),(7,G),(8,H)]),
    Solution = [A,B,C,D]. % E,F,G,H].

% find_eight_queen(Solution).