2

我正在尝试在 Prolog 中解决 15 个难题,我需要找到最少的移动次数。

在这里,我们有一个带有详细答案的示例拼图。https://rosettacode.org/wiki/15_puzzle_solver

我正在使用 A* 搜索,使用曼哈顿距离作为启发式。

首先,我确保程序是确定性的。规则要么不适用,要么快速失败,要么在没有回溯的情况下运行完成。为了优化空间,我使用了一个数字来表示状态。搜索队列使用二项式队列进行优化,访问检查使用哈希树进行优化。但是,性能仍然很糟糕。

这是我对二项式队列的实现:

:- module(myheap, [myHeapInsert/4, myHeapDeleteMin/4]).

mergeOneTree(Tree, [], [Tree]) :-
  Tree = binomialQueueNode(_, _, _, _), !.

mergeOneTree(Tree, [Head|Tail], [Tree,Head|Tail]) :- 
  Tree = binomialQueueNode(Size, _, _, _),
  Head = binomialQueueNode(Head_Size, _, _, _),
  Size < Head_Size,
  !.

mergeOneTree(Tree, [Head|Tail], Result) :-
  Tree = binomialQueueNode(Size, Data, Priority, Subtree_Content-Subtree_Indeterminate),
  Head = binomialQueueNode(Size, _, Head_Priority, _),
  Priority < Head_Priority, 
  Concatenation = Subtree_Content-Concatenation_Indeterminate,
  Subtree_Indeterminate = [Head|Concatenation_Indeterminate],
  DoubleSize is Size *2,
  mergeOneTree(binomialQueueNode(DoubleSize, Data, Priority, Concatenation), Tail, Result),
  !.

mergeOneTree(Tree, [Head|Tail], Result) :-
  Tree = binomialQueueNode(Size, _, Priority, _),
  Head = binomialQueueNode(Size, Head_Data, Head_Priority, Head_Subtree_Content-Head_Subtree_Indeterminate),
  Priority >= Head_Priority, 
  Concatenation = Head_Subtree_Content-Concatenation_Indeterminate,
  Head_Subtree_Indeterminate = [Tree|Concatenation_Indeterminate],
  DoubleSize is Size *2,
  mergeOneTree(binomialQueueNode(DoubleSize, Head_Data, Head_Priority, Concatenation), Tail, Result),
  !.

mergeOneTree(Tree, [Head|Tail], [Head|TailResult]) :-
  Tree = binomialQueueNode(Size, _, _, _),
  Head = binomialQueueNode(Head_Size, _, _, _),
  Size > Head_Size,
  mergeOneTree(Tree, Tail, TailResult),
  !.

merge([], X, X) :- !.
merge([H|T], X, R) :- mergeOneTree(H, X, I), merge(T, I, R), !.

findMinTree([H|T], MinTree, Others) :-
  findMinTree(H, T, MinTree, Others), !.

findMinTree(CurrentMin, [], CurrentMin, []) :- !.

findMinTree(CurrentMin, [Candidate|Tail], ResultMinTree, [Candidate|ResultOthers]) :-
  CurrentMin = binomialQueueNode(_, _, CurrentMin_Priority, _),
  Candidate = binomialQueueNode(_, _, Candidate_Priority, _),
  Candidate_Priority > CurrentMin_Priority,
  findMinTree(CurrentMin, Tail, ResultMinTree, ResultOthers),
  !.

findMinTree(CurrentMin, [Candidate|Tail], ResultMinTree, [CurrentMin|ResultOthers]) :-
  CurrentMin = binomialQueueNode(_, _, CurrentMin_Priority, _),
  Candidate = binomialQueueNode(_, _, Candidate_Priority, _),
  Candidate_Priority =< CurrentMin_Priority,
  findMinTree(Candidate, Tail, ResultMinTree, ResultOthers),
  !.

myHeapInsert(BeforeTree, Data, Priority, AfterTree) :- 
  mergeOneTree(binomialQueueNode(1, Data, Priority, Dummy-Dummy), BeforeTree, AfterTree), !.

myHeapDeleteMin(BeforeTree, MinData, MinPriority, AfterTree) :- 
  findMinTree(BeforeTree, MinTree, Others), 
  MinTree = binomialQueueNode(_, MinData, MinPriority, MinTreeSubTree_Content-MinTreeSubTree_Indeterminate),
  MinTreeSubTree_Indeterminate = [],
  merge(Others, MinTreeSubTree_Content, AfterTree),
  !.

这是我对哈希树的实现:

:- module(myhash, [myHashEmpty/1, myHashGet/4, myHashPut/5]).

reverseBinary(0, 0, []).
reverseBinary(0, L, [0|R]) :- L > 0, D is L - 1, reverseBinary(0, D, R), !.
reverseBinary(1, L, [1|R]) :- L > 0, D is L - 1, reverseBinary(0, D, R), !.
reverseBinary(N, L, Result) :- N > 1, R is N mod 2, Q is (N - R) / 2, D is L - 1, reverseBinary(Q, D, QR), Result = [R|QR], !.

getValue([H|T], Key, Value) :- H = pair(Key, Value); getValue(T, Key, Value), !.

getHash(Key, [], hashTrieLeaf(Values), Value) :- getValue(Values, Key, Value), !.
getHash(Key, [0|T], hashTrieNode(Left,_), Value) :- getHash(Key, T, Left, Value), !.
getHash(Key, [1|T], hashTrieNode(_,Right), Value) :- getHash(Key, T, Right, Value), !.

putHash(Tuple, [], hashTrieNil, hashTrieLeaf([Tuple])) :- !.
putHash(Tuple, [], hashTrieLeaf(Tuples), hashTrieLeaf([Tuple|Tuples])) :- !.
putHash(Tuple, [0|T], hashTrieNil, hashTrieNode(LeftResult, hashTrieNil)) :-
  putHash(Tuple, T, hashTrieNil, LeftResult), !.
putHash(Tuple, [0|T], hashTrieNode(Left,Right), hashTrieNode(LeftResult, Right)) :-
  putHash(Tuple, T, Left, LeftResult), !.
putHash(Tuple, [1|T], hashTrieNil, hashTrieNode(hashTrieNil, RightResult)) :-
  putHash(Tuple, T, hashTrieNil, RightResult), !.
putHash(Tuple, [1|T], hashTrieNode(Left,Right), hashTrieNode(Left, RightResult)) :-
  putHash(Tuple, T, Right, RightResult), !.

myHashEmpty(hashTrieNil) :- !.
myHashGet(HashMap, Key, Hash, Value) :- reverseBinary(Hash, 30, HashBits), getHash(Key, HashBits, HashMap, Value), !.
myHashPut(BeforeHashMap, Key, Hash, Value, AfterHashMap) :- reverseBinary(Hash, 30, HashBits), putHash(pair(Key, Value), HashBits, BeforeHashMap, AfterHashMap), !.

最后,解谜代码:

:- set_prolog_stack(global, limit(100 000 000 000)).
:- set_prolog_stack(trail,  limit(20 000 000 000)).
:- set_prolog_stack(local,  limit(2 000 000 000)).

:- use_module(myheap).
:- use_module(myhash).

hash([], 0) :- !.
hash([H|T], Hash) :- hash(T, R), Hash is (H + 23 * R) mod 1073741824, !.

flatten([[A,B,C,D],[E,F,G,H],[I,J,K,L],[M,N,O,P]],[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P]) :- !.

moves([0,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([X10,X01,X02,X03,0,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X10),move([X01,0,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X01)]) :- !.
moves([X00,0,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([0,X00,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X00),move([X00,X11,X02,X03,X10,0,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X11),move([X00,X02,0,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X02)]) :- !.
moves([X00,X01,0,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([X00,0,X01,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X01),move([X00,X01,X12,X03,X10,X11,0,X13,X20,X21,X22,X23,X30,X31,X32,X33],X12),move([X00,X01,X03,0,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X03)]):- !.
moves([X00,X01,X02,0,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([X00,X01,0,X02,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X02),move([X00,X01,X02,X13,X10,X11,X12,0,X20,X21,X22,X23,X30,X31,X32,X33],X13)]):- !.
moves([X00,X01,X02,X03,0,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([0,X01,X02,X03,X00,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X00),move([X00,X01,X02,X03,X20,X11,X12,X13,0,X21,X22,X23,X30,X31,X32,X33],X20),move([X00,X01,X02,X03,X11,0,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X11)]):- !.
moves([X00,X01,X02,X03,X10,0,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([X00,0,X02,X03,X10,X01,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X01),move([X00,X01,X02,X03,0,X10,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33],X10),move([X00,X01,X02,X03,X10,X21,X12,X13,X20,0,X22,X23,X30,X31,X32,X33],X21),move([X00,X01,X02,X03,X10,X12,0,X13,X20,X21,X22,X23,X30,X31,X32,X33],X12)]):- !.
moves([X00,X01,X02,X03,X10,X11,0,X13,X20,X21,X22,X23,X30,X31,X32,X33],[move([X00,X01,0,X03,X10,X11,X02,X13,X20,X21,X22,X23,X30,X31,X32,X33],X02),move([X00,X01,X02,X03,X10,0,X11,X13,X20,X21,X22,X23,X30,X31,X32,X33],X11),move([X00,X01,X02,X03,X10,X11,X22,X13,X20,X21,0,X23,X30,X31,X32,X33],X22),move([X00,X01,X02,X03,X10,X11,X13,0,X20,X21,X22,X23,X30,X31,X32,X33],X13)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,0,X20,X21,X22,X23,X30,X31,X32,X33],[move([X00,X01,X02,0,X10,X11,X12,X03,X20,X21,X22,X23,X30,X31,X32,X33],X03),move([X00,X01,X02,X03,X10,X11,0,X12,X20,X21,X22,X23,X30,X31,X32,X33],X12),move([X00,X01,X02,X03,X10,X11,X12,X23,X20,X21,X22,0,X30,X31,X32,X33],X23)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,0,X21,X22,X23,X30,X31,X32,X33],[move([X00,X01,X02,X03,0,X11,X12,X13,X10,X21,X22,X23,X30,X31,X32,X33],X10),move([X00,X01,X02,X03,X10,X11,X12,X13,X30,X21,X22,X23,0,X31,X32,X33],X30),move([X00,X01,X02,X03,X10,X11,X12,X13,X21,0,X22,X23,X30,X31,X32,X33],X21)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,0,X22,X23,X30,X31,X32,X33],[move([X00,X01,X02,X03,X10,0,X12,X13,X20,X11,X22,X23,X30,X31,X32,X33],X11),move([X00,X01,X02,X03,X10,X11,X12,X13,0,X20,X22,X23,X30,X31,X32,X33],X20),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X31,X22,X23,X30,0,X32,X33],X31),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X22,0,X23,X30,X31,X32,X33],X22)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,0,X23,X30,X31,X32,X33],[move([X00,X01,X02,X03,X10,X11,0,X13,X20,X21,X12,X23,X30,X31,X32,X33],X12),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,0,X21,X23,X30,X31,X32,X33],X21),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X32,X23,X30,X31,0,X33],X32),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X23,0,X30,X31,X32,X33],X23)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,0,X30,X31,X32,X33],[move([X00,X01,X02,X03,X10,X11,X12,0,X20,X21,X22,X13,X30,X31,X32,X33],X13),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,0,X22,X30,X31,X32,X33],X22),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X33,X30,X31,X32,0],X33)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,0,X31,X32,X33],[move([X00,X01,X02,X03,X10,X11,X12,X13,0,X21,X22,X23,X20,X31,X32,X33],X20),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X31,0,X32,X33],X31)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,0,X32,X33],[move([X00,X01,X02,X03,X10,X11,X12,X13,X20,0,X22,X23,X30,X21,X32,X33],X21),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,0,X30,X32,X33],X30),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X32,0,X33],X32)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,0,X33],[move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,0,X23,X30,X31,X22,X33],X22),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,0,X31,X33],X31),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X33,0],X33)]):- !.
moves([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,0],[move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,0,X30,X31,X32,X23],X23),move([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,0,X32],X32)]):- !.
debug([X00,X01,X02,X03,X10,X11,X12,X13,X20,X21,X22,X23,X30,X31,X32,X33]) :- true,write(X00),write('\t'),write(X01),write('\t'),write(X02),write('\t'),write(X03),write('\t'),write('\n'),write(X10),write('\t'),write(X11),write('\t'),write(X12),write('\t'),write(X13),write('\t'),write('\n'),write(X20),write('\t'),write(X21),write('\t'),write(X22),write('\t'),write(X23),write('\t'),write('\n'),write(X30),write('\t'),write(X31),write('\t'),write(X32),write('\t'),write(X33),write('\t'),write('\n'),write('\n').

easy([[1, 2, 3, 4],[5, 6, 0, 8],[9, 10, 7,  11],[13, 14, 15, 12]]).
hard([[15, 14,  1,  6],[ 9, 11,  4, 12],[ 0, 10,  7,  3],[13,  8,  5,  2]]).

dist(A, B, C) :- C is A - B, C >= 0, !.
dist(A, B, C) :- C is B - A, C >= 0, !.

manhattan(L, D) :- manhattan(L, 0, D), !.
manhattan([], _, 0) :- !.
manhattan([0|T], L, D) :- 
  NextL is L + 1, 
  manhattan(T, NextL, D),
  !.
manhattan([H|T], L, D) :- 
  H > 0, 
  CellCol is L mod 4, 
  CellRow is (L - CellCol) / 4, 
  DataCol is (H - 1) mod 4, 
  DataRow is (H - 1- DataCol) / 4, 
  dist(CellCol, DataCol, CD), 
  dist(CellRow, DataRow, RD), 
  NextL is L + 1, 
  manhattan(T, NextL, TD), 
  D is CD + RD + TD,
  !.

compress([], 0) :- !.
compress([H|T], L) :- compress(T, I), L is I * 16 + H, !.

uncompress(L, R) :- uncompress(L, 16, R), !.
uncompress(_, 0, []) :- !.
uncompress(L, C, [H|T]) :- C > 0, D is C - 1, H is L mod 16, R is (L - H) / 16, uncompress(R, D, T), !.

search(CurrentBoard) :-
  myHashEmpty(EmptyHash),
  hash(CurrentBoard, CurrentBoardHash),  
  manhattan(CurrentBoard, CurrentBoardPriority),
  compress(CurrentBoard, CurrentBoardCompressed),
  myHashPut(EmptyHash, CurrentBoardCompressed, CurrentBoardHash, _, Enqueued),  
  search(CurrentBoard, CurrentBoardPriority, 0, Enqueued, []), !.

search(CurrentBoard, _, _, _, _) :-
  manhattan(CurrentBoard, 0),
  !.

search(CurrentBoard, CurrentBoardPriority, CurrentStep, Enqueued, Queue) :-
  CurrentBoardPriority > 0,
  moves(CurrentBoard, NextMoves),
  update_enqueued_queue_all(Enqueued, Queue, NextMoves, CurrentStep, NextEnqueued, ImmediateQueue),
  myHeapDeleteMin(ImmediateQueue, NextState, NextBoardPriority, NextQueue),
  state(NextBoardCompressed, _, NextStep) = NextState,
  uncompress(NextBoardCompressed, NextBoard),
  search(NextBoard, NextBoardPriority, NextStep, NextEnqueued, NextQueue),
  !.

update_enqueued_queue_all(Enqueued, Queue, [], _, Enqueued, Queue) :- !.
update_enqueued_queue_all(Enqueued, Queue, [Head|Tail], CurrentStep, NextEnqueued, NextQueue) :-
  move(HeadBoard, HeadMove) = Head,
  hash(HeadBoard, HeadHash),
  compress(HeadBoard, HeadBoardCompressed),
  update_enqueued_queue(Enqueued, Queue, HeadBoard, HeadBoardCompressed, HeadHash, HeadMove, CurrentStep, ImmediateEnqueued, ImmediateQueue),
  update_enqueued_queue_all(ImmediateEnqueued, ImmediateQueue, Tail, CurrentStep, NextEnqueued, NextQueue),
  !.

update_enqueued_queue(Enqueued, Queue, Board, BoardCompressed, BoardHash, Move, CurrentStep, NextEnqueued, NextQueue) :-
  myHashGet(Enqueued, BoardCompressed, BoardHash, _), NextEnqueued = Enqueued, NextQueue = Queue, !;  
  NextStep is CurrentStep + 1, manhattan(Board, BoardHeuristic), BoardPriority is NextStep + BoardHeuristic, myHashPut(Enqueued, BoardCompressed, BoardHash, _, NextEnqueued), myHeapInsert(Queue, state(BoardCompressed, Move, NextStep), BoardPriority, NextQueue), !.

solve(X) :- flatten(X, Y), search(Y).

run :- hard(X), solve(X).

按原样,代码在我的计算机上不会在几分钟内运行完成。我做了一个减少目标的配置文件(运行)(例如,当曼哈顿距离为 10 时停止),大部分时间都花在垃圾收集上。

我读过关于 StackOverflow 上的另一个线程谈论同样的事情,“解决方案”是使用约束库,这是我不能使用的东西。

我用尽了我的技巧,坦率地说,我不是一个经常使用 Prolog 的程序员。知道如何在速度方面做得更好吗?

4

0 回答 0