Перевести понимание списка в Prolog

у меня есть понимание списка в Haskell, которое я хочу перевести в Prolog.

точка понимания списка вращает сетку 4 на 4:

rotate :: [Int] -> [Int]
rotate grid = [ grid !! (a + 4 * b) | a <- [0..3], b <- [0..3] ]

теперь в прологе я перевел это так:

rotateGrid([T0,T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15],
       [T0,T4,T8,T12,T1,T5,T9,T13,T2,T6,T10,T14,T3,T7,T11,T15]).

можем мы сделать лучше?

3 ответов


можно использовать findall/3 для понимания списка (ср. the документация SWI-Prolog). Е. Г.,

?- findall(X, between(1,10,X), Xs).
Xs = [1,2,3,4,5,6,7,8,9,10]

Xs - это список, содержащий все значения, которые могут объединяться с X, когда X - это число от 1 до 10. Это примерно эквивалентно выражению Haskell let Xs = [x | x <- [1..10]](1). Вы можете прочитать findall/3 утверждение таким образом: "найдите все значения [первого аргумента] такие, что [условия во втором аргументе] удерживаются, и поместите эти значения в список, [третий Аргумент."]

я использовал findall/3 написать предикат rotate_grid(+Grid, ?RotatedGrid). Вот список приближенных эквивалентностей Haskell-Prolog, которые я использовал в предикате; каждая строка показывает отношение между значением, которое выражение Haskell будет оценивать, и переменной Prolog с тем же значением:

  • a <- [0..3] = A на between(0, 3, A)
  • b <- [0..3] = B на between(0, 3, B)
  • (a + 4 * d) = X in X is A + 4 * D
  • <Grid> !! <Index> = Element на nth0(Index, Grid, Element)

тогда просто нужно найти все значения Element:

rotate_grid(Grid, RotatedGrid) :-
    findall( Element,

            ( between(0,3,A),
              between(0,3,B),
              Index is A + 4 * B,
              nth0(Index, Grid, Element) ),

             RotatedGrid
           ).

чтобы убедиться, что это производит правильное преобразование, я опустил код пролога из вопроса и задал следующий запрос:

?- rotate_grid([t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15],
       [t0,t4,t8,t12,t1,t5,t9,t13,t2,t6,t10,t14,t3,t7,t11,t15]).
|    true.

Примечания:

(1): between/3 на самом деле не аналог [m..n], так как последний возвращает список значения m to n здесь between(M,N,X) будет создавать экземпляр X с каждым значением между M и N (включительно) при обратном отслеживании. Чтобы получить список номеров в SWI-Prolog, мы можем использовать numlist(M,N,Ns). Таким образом, более строгий аналог для x <- [1.10] будет вместе member(X, Ns), numlist(1, 10, Ns).


вы хотите перестановку списка. Конкретные элементы не рассматриваются. Поэтому вы можете обобщить свою подпись Haskell на

rotate :: [x] -> [x]

это уже очень ценная подсказка для Prolog: элементы списка не будут рассматриваться - элементы даже не будут сравниваться. Таким образом, решение Prolog должно иметь возможность напрямую обрабатывать переменные, например:

| ?- rotateGrid(L,R).
L = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P],
R = [_A,_E,_I,_M,_B,_F,_J,_N,_C,_G,_K,_O,_D,_H,_L,_P].

и ваше первоначальное определение отлично справляется с этим.

ваша версия с помощью понимание списка предполагает, что оно должно быть реализовано путем отступления, необходимо принять определенные меры предосторожности. Используя findall/3, как предложил @aBathologist переименует переменные:

| ?- length(L,16),rotate_grid(L,R).
L = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P],
R = [_Q,_R,_S,_T,_U,_V,_W,_X,_Y,_Z,_A1,_B1,_C1,_D1,_E1,_F1].

встроенный предикат bagof/3 решает эту проблему. Обратите внимание, что мы должны объявить все локальные экзистенциальные переменные явно:

rotate_grid2(Grid, RotatedGrid) :-
   bagof(
     Element,
     A^B^Index^    % declaration of existential variables
       (  between(0,3,A),
          between(0,3,B),
          Index is A + 4 * B,
          nth0(Index, Grid, Element)
       ),
     RotatedGrid).

для списков, которые короче 16 элементов, версия Haskell производит чистую ошибку, но здесь мы получаем довольно случайный результаты:

| ?- L=[1,2,3,4],rotate_grid(L,R).
L = [1,2,3,4],
R = [1,2,3,4] ? 
yes
| ?- L=[1,2,3,4,5],rotate_grid(L,R).
L = [1,2,3,4,5],
R = [1,5,2,3,4] ?
yes

это связано с неясным разделением между частью, которая перечисляет и" генерирует " конкретный элемент. Самый чистый способ-добавить length(Grid, 16) до цели bagof/3.

список постижений в прологе

в настоящее время только B-Prolog предлагает форму понимания списка:

R@=[E: A in 0..3,B in 0..3,[E,I],(I is A+4*B,nth0(I,L,E))].

однако он не решает вторую проблему:

| ?- L = [1,2,3], R@=[E: A in 0..3,B in 0..3,[E,I],(I is A+4*B,nth0(I,L,E))].
L = [1,2,3]
R = [1,2,3]
yes

используйте предикат цикла foreach / 4

Если понимание должно сохранять переменные, что, например, важно в программировании ограничений, система Prolog может предложить предикат foreach/4. Этот предикат является приятелем DCG foreach / 2.

вот как переменные не сохраняются через findall/3, результат R содержит свежие переменные в соответствии с ISO основная семантика findall / 3:

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.1)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.

?- functor(L,foo,5), findall(X, 
     (between(1,5,N), M is 6-N, arg(M,L,X)), R).
L = foo(_5140, _5142, _5144, _5146, _5148),
R = [_5210, _5204, _5198, _5192, _5186].

а вот как переменные могут быть сохраняется через foreach / 4, результирующий список имеет те же переменные, что и соединение мы начали с:

Jekejeke Prolog 3, Runtime Library 1.3.0
(c) 1985-2018, XLOG Technologies GmbH, Switzerland

?- [user].
helper(N,L) --> [X], {M is 6-N, arg(M,L,X)}.

Yes

?- functor(L,foo,5), foreach(between(1,5,N),helper(N,L),R,[]).
L = foo(_A,_G,_M,_S,_Y),
R = [_Y,_S,_M,_G,_A]

использование foreach / 4 вместо bagof/3 может показаться немного излишним. foreach/4, вероятно, покажет свой полный потенциал только при реализации циклов Picat, поскольку он может создавать ограничения, чего не может сделать bagof / 3.

foreach/4-это реализация без полной материализации всего решения, которое затем возвращается. Он делит с bagof / 3 реконструировать переменные, но все же позволяет отступать в сочетании замыканий.