#list #prolog #shuffle
Вопрос:
Я пытаюсь получить задание в колледже по Прологу, и у меня есть несколько вопросов. Это текст упражнения, надеюсь, вам ясно, что я перевожу с итальянского:
Напишите предикат
arrange(List1, List2, K)
, который при заданииList1
по крайней мере двух элементов с только числами между диапазонами[0..100]
удовлетворяется, когдаList2
получается список, перетасовывающий элементыList1
таким образом, чтобы абсолютная разница между двумя последовательными элементами всегда была больше, чемK
.
Пример:
?- arrange([1, 2, 3], L2, 0).
L2 = [1, 2, 3];
L2 = [1, 3, 2];
L2 = [2, 1, 3];
L2 = [2, 3, 1];
L2 = [3, 1, 2];
L2 = [3, 2, 1];
Я пытался создать свое собственное решение, но оно не дает мне всех возможных результатов, вместо этого я получаю только один из возможных результатов. У меня также есть решение коллеги, которое дает все результаты, но у него есть одна проблема, которую я пытался решить.
Вот мое решение:
arrange(L1,L3,Diff):-
arrange(L1,[],L3,Diff).
arrange(L1,L2,L3,Diff):-
same_length(L1,L3),
shuffle(L1,L2,L3),
constraint(L3,Diff).
constraint([X1,X2],Diff):-
int_0_100(X1),
int_0_100(X2),
diff(X1,X2,Diff).
constraint([X1,X2|Others],Diff):-
int_0_100(X1),
int_0_100(X2),
diff(X1,X2,Diff),
constraint([X2|Others],Diff).
shuffle([],L2,L2).
shuffle([X1|Others],L2,L3):-
L4 = [X1 | L2],
shuffle(Others,L4,L3).
diff(X1,X2,Diff):-
X1 >= X2,
X1 - X2 > Diff.
diff(X1,X2,Diff):-
X1 < X2,
X2 - X1 > Diff.
int_0_100(N):-
N >= 0, N =< 100.
same_length([],[]).
same_length([_|Others1],[_|Others2]):-
same_length(Others1,Others2).
I guess that the problem is that I instantiate a L4
list that can’t change in the shuffle
predicate.
Now my colleague’s solution:
arrange(L1,L2,Diff):-
same_length(L1,L2),
shuffle(L1,L2),
constraint(L2,Diff).
constraint([X1,X2],Diff):-
int_0_100(X1),
int_0_100(X2),
diff(X1,X2,Diff).
constraint([X1,X2|Others],Diff):-
int_0_100(X1),
int_0_100(X2),
diff(X1,X2,Diff),
constraint([X2|Others],Diff).
shuffle([],_).
shuffle([X1|Others],L2):-
member(X1,L2),
shuffle(Others,L2).
diff(X1,X2,Diff):-
X1 >= X2,
X1 - X2 > Diff.
diff(X1,X2,Diff):-
X1 < X2,
X2 - X1 > Diff.
int_0_100(N):-
N >= 0, N =< 100.
same_length([],[]).
same_length([_|Others1],[_|Others2]):-
same_length(Others1,Others2).
Как вы можете видеть, есть одно существенное различие:
- В предикате перемешивания , который Он использует
member(X1,L2)
, я используюL4 = [X1 | L2],shuffle(Other,L4,L3)
.member(X1,L2)
выдает ошибку, если у нас есть список с двумя (или более) равными элементами. - Например:
arrange([1,2,3,4,1], List2, 1)
. Такmember(1, L2)
как ужеtrue
(во второй раз), и такL2
как изначально имеет одинаковое количество элементовL1
(но не экземпляров) сsame_length
, он не добавляет еще1
L2
один, и когда мы добираемсяint_0_100
, мы получаемelement not enough instantiated
ошибку.
Это проблема, которую я пытался решить , но, похоже, если я специально не использую member
вместо своего решения, это просто дает один возможный ответ.
Можете ли вы объяснить мне, почему это так, и возможное решение проблемы, которую дает предикат member
?
Заранее спасибо за ваше время, я надеюсь, что все достаточно ясно: английский не мой родной язык, и сама проблема довольно сложна для объяснения.
Комментарии:
1. Разве это не перестановка списка? См. перестановку/2
2. @GuyCoder: конечно, используя перестановку/2, для всего/2,добавления/3 и абс/1, это один лайнер…
3. Смотрите мой комментарий выше, где я отвечаю на вопрос @GuyCoder. Не могли бы вы превратить это в решение вашей проблемы ?
Ответ №1:
Если посмотреть на «перетасовки» и извлечь их:
% Your shuffle:
shuffle_a(LI,LO) :-
shuffle_help(LI,[],LO).
shuffle_help([],L2,L2).
shuffle_help([X1|Other],L2,L3):-
shuffle_help(Other,[X1|L2],L3).
% Colleague's shuffle
shuffle_b([],_).
shuffle_b([X1|Others],L2):-
member(X1,L2),
shuffle_b(Others,L2).
затем можно увидеть, что ваша тасовка просто меняет список в позиции первого аргумента, в то время как «тасовка» вашего коллеги действительно тасует:
Обратите внимание, что оба предиката shuffle должны вызываться со списком правильной длины во второй позиции аргумента:
?- shuffle_a([1,2,3],[S1,S2,S3]).
S1 = 3,
S2 = 2,
S3 = 1.
?- shuffle_b([1,2,3],[S1,S2,S3]).
S1 = 1, S2 = 2, S3 = 3 ;
S1 = 1, S2 = 3, S3 = 2 ;
S1 = 2, S2 = 1, S3 = 3 ;
S1 = 3, S2 = 1, S3 = 2 ;
S1 = 2, S2 = 3, S3 = 1 ;
S1 = 3, S2 = 2, S3 = 1 ;
false.
В shuffle_a/2
любой момент есть только один способ действовать.
Но в shuffle_b/2
member/2
вызове можно выбрать один из N возможных элементов X1
из списка L2
длины N. Таким образом, можно сказать Прологу «повторить» свой выбор и выбрать другой элемент.
Так же, как
?- member(X,[1,2,3]).
X = 1 ;
X = 2 ;
X = 3.
что дает три возможных ответа.
Дополнение
Чтобы правильно переставить список, используйте перестановку/2, как говорит кодер, или, может быть, поступите так: A shuffle_c
, который использует список индексов 0…N-1, заданный member/2
для отслеживания элементов выбора из исходного списка ровно один раз, а затем использует nth0/3
для объединения элементов первого и второго списка:
shuffle_c(List,OtherList) :-
same_length(List,OtherList),
length(List,Length),
build_index_list(Length,IndexList),
shuffle_by_index(IndexList,[],List,OtherList).
build_index_list(Length,IndexList) :-
LengthAdj is Length-1,
bagof(Index,between(0,LengthAdj,Index),IndexList).
% shuffle_by_index(IndexList,UsedIndexList,List,OtherList).
shuffle_by_index(IndexList,UsedIndexList,_,_) :-
same_length(IndexList,UsedIndexList). % Success, shuffle_c will succeed with another answer
shuffle_by_index(IndexList,UsedIndexList,List,OtherList) :-
same_length(IndexList,UsedIndexList), % Not done yet
member(ToIndex,IndexList), % Pick an "ToIndex" from IndexList
member(ToIndex,UsedIndexList), % ...that hasn't been used yet
length(UsedIndexList,FromIndex), % The "FromIndex" is just monotonically increasing
format("'to index' = ~d, 'from index' = ~d. The used index list is currently: ~q~n",[ToIndex,FromIndex,UsedIndexList]),
nth0(FromIndex,List,X), % Unifies List[FromIndex] and
nth0(ToIndex,OtherList,X), % OtherList[ToIndex]
shuffle_by_index(IndexList,[ToIndex|UsedIndexList],List,OtherList).
Никакой специальной обработки идентичных элементов в исходном списке не производится.
Это действительно работает для любого из удаленных списков:
?- bagof(List,
shuffle_c(List,[1,2,3,4]),
Bag).
Bag = [[1,2,3,4],[1,2,4,3],[1,3,2,4],[1,3,4,2],
[1,4,2,3],[1,4,3,2],[2,1,3,4],[2,1,4,3],
[2,3,1,4],[2,3,4,1],[2,4,1,3],[2,4,3,1],
[3,1,2,4],[3,1,4,2],[3,2,1,4],[3,2,4,1],
[3,4,1,2],[3,4,2,1],[4,1,2,3],[4,1,3,2],
[4,2,1,3],[4,2,3,1],[4,3,1,2],[4,3,2,1]].
И
?- bagof(List,
shuffle_c([1,2,3,4],List),
Bag).
Bag = [[1,2,3,4],[1,2,4,3],[1,3,2,4],[1,4,2,3],
[1,3,4,2],[1,4,3,2],[2,1,3,4],[2,1,4,3],
[3,1,2,4],[4,1,2,3],[3,1,4,2],[4,1,3,2],
[2,3,1,4],[2,4,1,3],[3,2,1,4],[4,2,1,3],
[3,4,1,2],[4,3,1,2],[2,3,4,1],[2,4,3,1],
[3,2,4,1],[4,2,3,1],[3,4,2,1],[4,3,2,1]].
Это работает даже для «списков неустановленных элементов». Похоже на перетасовку отверстий. Давайте посмотрим, что произойдет, если две дырки окажутся одинаковыми
?- bagof(List,shuffle_c([A,X,X,D],List),Bag).
Bag = [[A,X,X,D],[A,X,D,X],[A,X,X,D],[A,D,X,X],
[A,X,D,X],[A,D,X,X],[X,A,X,D],[X,A,D,X],
[X,A,X,D],[D,A,X,X],[X,A,D,X],[D,A,X,X],
[X,X,A,D],[X,D,A,X],[X,X,A,D],[D,X,A,X],
[X,D,A,X],[D,X,A,X],[X,X,D,A],[X,D,X,A],
[X,X,D,A],[D,X,X,A],[X,D,X,A],[D,X,X,A]].
Это может быть легко адаптировано для решения исходной проблемы. Мы можем использовать ограничения (т. Е. libray(clpfd)
): Мы устанавливаем ограничение среди последующих элементов A
B
целевого списка , применяя это ограничение:
abs(A - B) #> K
Всякий раз, когда будет предпринята попытка объединения во время перетасовки, которая нарушит это ограничение, объединение завершится неудачей, т. Е.
Вызов nth0(ToIndex,OtherList,X)
завершится неудачей по причинам, которые не видны только из кода: потому что текущие ограничения накладывают на него вето.
В трассировщике можно увидеть
Call: (14) lists:nth0(1,[50,33,11,78],_40136) ? creep
Exit: (14) lists:nth0(1,[50,33,11,78],33) ? creep
Call: (14) lists:nth0(1,[50,_34818{clpfd = ...},_35522{clpfd = ...},_36226{clpfd = ...}],33) ? creep
Fail: (14) lists:nth0(1,[50,_34818{clpfd = ...},_35522{clpfd = ...},_36226{clpfd = ...}],33) ? creep
Итак, новый код (не беспокоясь о ограничении «должно быть от 0 до 100» здесь) (вероятно, это можно сделать еще проще, так как есть готовое ограничение «перестановка» IIRC):
:- use_module(library(clpfd)).
arrange(List,OtherList,K) :-
same_length(List,OtherList),
apply_constraints(OtherList,K), % <----- HERE
length(List,Length),
build_index_list(Length,IndexList),
shuffle_by_index(IndexList,[],List,OtherList).
% ADD THIS
apply_constraints([_],_).
apply_constraints([A,B|More],K) :-
abs(A - B) #> K,
apply_constraints([B|More],K).
% NOTHING BELOW HAS CHANGED
build_index_list(Length,IndexList) :-
LengthAdj is Length-1,
bagof(Index,between(0,LengthAdj,Index),IndexList).
% shuffle_by_index(IndexList,UsedIndexList,List,OtherList).
shuffle_by_index(IndexList,UsedIndexList,_,_) :-
same_length(IndexList,UsedIndexList). % Success, shuffle_c will succeed with another answer
shuffle_by_index(IndexList,UsedIndexList,List,OtherList) :-
same_length(IndexList,UsedIndexList), % Not done yet
member(ToIndex,IndexList), % Pick an "ToIndex" from IndexList
member(ToIndex,UsedIndexList), % ...that hasn't been used yet
length(UsedIndexList,FromIndex), % The "FromIndex" is just monotonically increasing
format("'to index' = ~d, 'from index' = ~d. The used index list is currently: ~q~n",[ToIndex,FromIndex,UsedIndexList]),
nth0(FromIndex,List,X), % Unifies List[FromIndex] and
nth0(ToIndex,OtherList,X), % OtherList[ToIndex]
shuffle_by_index(IndexList,[ToIndex|UsedIndexList],List,OtherList).
И поэтому:
?- bagof(List,arrange([50,33,11,78],List,20),Bag).
Bag = [[50,11,33,78],[50,78,33,11],[50,11,78,33],
[50,78,11,33],[11,50,78,33],[78,50,11,33],
[33,11,50,78],[33,78,50,11],[33,11,78,50],
[33,78,11,50],[11,33,78,50],[78,33,11,50]].
Комментарии:
1. Спасибо. Есть ли способ (во втором решении) избежать ошибки «Аргументы недостаточно созданы», которая возникает при предоставлении списка с двумя (или более) равными элементами в качестве входных данных? Поскольку элемент(элемент, список) завершается неудачно, если элемент уже находится в списке, элемент остается НЕ созданным, и при проверке диапазона [0..100] элемента вы получаете ошибку. например, упорядочить([1,2,3,1], Список).
2. @BlueRyse На самом деле участник/2 завершается успешно, как и ожидалось, когда в первом списке два одинаковых элемента. Однако при использовании он сопоставляет два элемента с одним и тем же элементом во втором списке. Предположим, их два
1
: при первой встрече member/2 выбирает первый неинсталлированный элемент, найденный во втором списке, и объединяет его1
. Когда1
найден второй, участник не выбирает первый неустановленный элемент, потому что он доволен выбором1
вместо этого, который появляется раньше. Это оставляет неустановленный элемент в конце.3. Да, и это проблема, потому что список с двумя или более равными элементами будет правильным списком входных данных. Что я могу использовать вместо члена/2, который выбирает оба значения 1 в вашем примере?
4. @BlueRyse Держись, сочиняю что-нибудь
5. @BlueRyse Зацени это