Reputation: 35
I'm trying a college assignment in Prolog, and I have some questions. This is the exercise text, I hope it's clear I'm translating from Italian:
Write a predicate
arrange(List1, List2, K)
that when given aList1
of at least two elements with only numbers between the range[0..100]
, is satisfied whenList2
is a list obtained re-shuffling the elements ofList1
in a way that the absolute difference between two consecutive elements is always greater thanK
.
Example:
?- 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];
I've tried to create my own solution, but it doesn't give me all the possible results, instead, I get only one of the possible result. I also have a colleague's solution that gives all the results, but it has one problem that I was trying to fix.
Here's my solution:
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).
As you can see there is one major difference:
In the shuffle predicate He uses member(X1,L2)
, I useL4 = [X1 | L2],shuffle(Other,L4,L3)
. member(X1,L2)
gives an error if we have a list with two (or more) equal elements.
For Example: arrange([1,2,3,4,1], List2, 1)
. Since member(1, L2)
is already true
(the second time), and since L2
has initially the same number of elements of L1
(but not instantiated) with same_length
, it doesn't add another 1
in L2
and when we get to int_0_100
we get an element not enough instantiated
error.
This is the problem I was trying to fix, but it seems that if I don't specifically use member
, instead of my solution, it just gives one possible answer.
Can you explain to me why that is, and a possible solution of the problem that the predicate member
gives?
Thanks in advance for your time, I hope everything's clear enough: English is not my native language, and the problem itself is quite complicated to explain.
Upvotes: 2
Views: 96
Reputation: 15338
If one looks at the "shuffles" and extracts them:
% 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).
then one sees that your shuffle just reverses the list on first argument position, whereas your colleague's "shuffle" indeed shuffles:
Note that both shuffle predicates have to be called with a list of the correct length on second argument position:
?- 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.
In shuffle_a/2
there is only one way to proceed at any point.
But in shuffle_b/2
, the member/2
call is able to pick one of N possible elements X1
out of the list L2
of length N. So one can tell Prolog to "redo" its choice and pick another element.
Same as
?- member(X,[1,2,3]).
X = 1 ;
X = 2 ;
X = 3.
which gives three possible answers.
In order to correctly permute the list use permutation/2 as Guy Coder says or maybe proceed like this: A shuffle_c
that uses a list of indexes 0...N-1 given to member/2
to track picking elements from the original list exactly once, and then uses nth0/3
to unify elements of the first and second list:
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).
No special handling for identical elements in the original list is done.
This actually works for any of the lists uninstantiated:
?- 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]].
And
?- 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]].
This even works for "lists of uninstantiated elements". Feels like shuffling holes. Let's see what happens if two holes are identical
?- 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]].
This can be easily adapted to solve the original problem. We can use constraints (i.e. libray(clpfd)
): We set up a constraint among subsequent elements A
, B
of the target list enforcing the constraint:
abs(A - B) #> K
Whenever an unification during shuffle is attempted that would violate that constraint, the unification will fail, i.e. the
call nth0(ToIndex,OtherList,X)
will fail for reasons that are not apparent from the code alone: because the currently live constraints veto it.
In the tracer, one sees
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
So, the new code (not bothering about the "must be between 0 and 100" constraint here) (one can probably do it even simpler as there is a ready-made 'permutation' constraint 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).
And so:
?- 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]].
Upvotes: 2