Вот (длинное, но простое) решение для рассмотрения.
Во-первых, полезно иметь вспомогательный предикат для извлечения элемента из позиции Row,Col
в матрице в виде списка списков, например:
get_matrix_entry_nth0(RowInd, ColInd, Matrix, Entry) :-
nth0(RowInd, Matrix, Row),
nth0(ColInd, Row, Entry).
Во-вторых, это также может помочь использовать этот предикат для определения записей, относящихся к индексированным Row
и Col
в терминах направлений, таких как:
up_left_entry(R-C, Matrix, E) :-
PrevR is R - 1,
PrevC is C - 1,
get_matrix_entry_nth0(PrevR, PrevC, Matrix, E).
up_entry(R-C, Matrix, E) :-
PrevR is R - 1,
get_matrix_entry_nth0(PrevR, C, Matrix, E).
up_right_entry(R-C, Matrix, E) :-
PrevR is R - 1,
NextC is C + 1,
get_matrix_entry_nth0(PrevR, NextC, Matrix, E).
right_entry(R-C, Matrix, E) :-
NextC is C + 1,
get_matrix_entry_nth0(R, NextC, Matrix, E).
down_right_entry(R-C, Matrix, E) :-
NextR is R + 1,
NextC is C + 1,
get_matrix_entry_nth0(NextR, NextC, Matrix, E).
down_entry(R-C, Matrix, E) :-
NextR is R + 1,
get_matrix_entry_nth0(NextR, C, Matrix, E).
down_left_entry(R-C, Matrix, E) :-
NextR is R + 1,
PrevC is C - 1,
get_matrix_entry_nth0(NextR, PrevC, Matrix, E).
left_entry(R-C, Matrix, E) :-
PrevC is C - 1,
get_matrix_entry_nth0(R, PrevC, Matrix, E).
С этими вспомогательными предикатами решение довольно простое:
matrix_path([R|Rs], P) :-
% determine rows, cols
length([R|Rs], Rows),
length(R, Cols),
% defer to matrix_path/4 to compute all paths starting with 0,0
matrix_path(0-0, Rows-Cols, [R|Rs], P).
matrix_path/2
является точкой входа в программу. Этот предикат с одним предложением заранее определяет количество строк и столбцов в данной матрице и откладывает обработку до matrix_path/4
, который начинает вычисление с элемента 0,0
(верхний левый).
matrix_path(R-C, Rows-Cols, Matrix, P) :-
C >= Cols, !,
% end of column, proceed to next row
NextRow is R + 1,
NextRow < Rows,
matrix_path(NextRow-0, Rows-Cols, Matrix, P).
В первом пункте matrix_path/4
проверяется, превышено ли количество столбцов; если это так, сокращение (!
) используется для исключения приведенных ниже предложений, и перезапускает вычисление в следующей строке и сбрасывает индекс столбца на 0.
matrix_path(R-C, _, Matrix, adj(S, T)) :-
% get this entry
get_matrix_entry_nth0(R, C, Matrix, S),
% get each adjacent entry
( up_left_entry(R-C, Matrix, T)
; up_entry(R-C, Matrix, T)
; up_right_entry(R-C, Matrix, T)
; right_entry(R-C, Matrix, T)
; down_right_entry(R-C, Matrix, T)
; down_entry(R-C, Matrix, T)
; down_left_entry(R-C, Matrix, T)
; left_entry(R-C, Matrix, T)
).
Второе предложение matrix_path/4
просто пытается извлечь все возможные «пути» из данной записи. Некоторые могут потерпеть неудачу (например, поиск в верхнем ряду), но Prolog откатится назад, чтобы найти все работающие решения.
matrix_path(R-C, Rows-Cols, Matrix, P) :-
% get the entry for the next column in this row
NextC is C + 1,
matrix_path(R-NextC, Rows-Cols, Matrix, P).
Последнее предложение matrix_path/4
просто переносит обработку относительно элемента в следующем столбце в той же строке.
Запуск простого примера:
?- matrix_path([[1,2],[3,4]], P).
P = adj(1, 2) ;
P = adj(1, 4) ;
P = adj(1, 3) ;
P = adj(2, 4) ;
P = adj(2, 3) ;
P = adj(2, 1) ;
P = adj(3, 1) ;
P = adj(3, 2) ;
P = adj(3, 4) ;
P = adj(4, 1) ;
P = adj(4, 2) ;
P = adj(4, 3) ;
false.
Обратите внимание, что если вы хотите, чтобы все соседние пары были сразу же без возврата, оберните вызов в findall/2
, например:
?- findall(P, matrix_path([[1,2],[3,4]], P), Ps).
Ps = [adj(1, 2), adj(1, 4), adj(1, 3), adj(2, 4), adj(2, 3), adj(2, 1), adj(3, 1), adj(3, 2), adj(..., ...)|...].