Related
I like the idea of lazy_findall as it helps me with keeping predicates separated and hence program decomposition.
What are the cons of using lazy_findall and are there alternatives?
Below is my "coroutine" version of the branch and bound problem.
It starts with the problem setup:
domain([[a1, a2, a3],
[b1, b2, b3, b4],
[c1, c2]]).
price(a1, 1900).
price(a2, 750).
price(a3, 900).
price(b1, 300).
price(b2, 500).
price(b3, 450).
price(b4, 600).
price(c1, 700).
price(c2, 850).
incompatible(a2, c1).
incompatible(b2, c2).
incompatible(b3, c2).
incompatible(a2, b4).
incompatible(a1, b3).
incompatible(a3, b3).
Derived predicates:
all_compatible(_, []).
all_compatible(X, [Y|_]) :- incompatible(X, Y), !, fail.
all_compatible(X, [_|T]) :- all_compatible(X, T).
list_price(A, Threshold, P) :- list_price(A, Threshold, 0, P).
list_price([], _, P, P).
list_price([H|T], Threshold, P0, P) :-
price(H, P1),
P2 is P0 + P1,
P2 =< Threshold,
list_price(T, Threshold, P2, P).
path([], []).
path([H|T], [I|Q]) :-
member(I, H),
path(T, Q),
all_compatible(I, Q).
The actual logic:
solution([], Paths, Paths, Value, Value).
solution([C|D], Paths0, Paths, Value0, Value) :-
( list_price(C, Value0, V)
-> ( V < Value0
-> solution(D, [C], Paths, V, Value)
; solution(D, [C|Paths0], Paths, Value0, Value)
)
; solution(D, Paths0, Paths, Value0, Value)
).
The glue
solution(Paths, Value) :-
domain(D),
lazy_findall(P, path(D, P), Paths0),
solution(Paths0, [], Paths, 5000, Value).
Here is an alternative no-lazy-findall solution by #gusbro: https://stackoverflow.com/a/68415760/1646086
I am not familiar with lazy_findall but I observe two "drawbacks" with the presented approach:
The code is not as decoupled as one might want, because there is still a mix of "declarative" and "procedural" code in the same predicate. I am putting quotes around the terms because they can mean a lot of things but here I see that path/2 is concerned with both generating paths AND ensuring that they are valid. Similarly solution/5 (or rather list_price/3-4) is concerned with both computing the cost of paths and eliminating too costly ones with respect to some operational bound.
The "bounding" test only happens on complete paths. This means that in practice all paths are generated and verified in order to find the shortest one. It does not matter for such a small problem but might be important for larger instances. Ideally, one might want to detect for instance that the partial path [a1,?,?] will never bring a solution less than 2900 without trying all values for b and c.
My suggestion is to instead use clpfd (or clpz, depending on your system) to solve both issues. With clpfd, one can first state the problem without concern for how to solve it, then call a predefined predicate (like labeling/2) to solve the problem in a (hopefully) clever way.
Here is an example of code that starts from the same "setup" predicates as in the question.
state(Xs,Total):-
domain(Ds),
init_vars(Ds,Xs,Total),
post_comp(Ds,Xs).
init_vars([],[],0).
init_vars([D|Ds],[X|Xs],Total):-
prices(D,P),
length(D,N),
X in 1..N,
element(X, P, C),
Total #= C + Total0,
init_vars(Ds,Xs,Total0).
prices([],[]).
prices([V|Vs],[P|Ps]):-
price(V,P),
prices(Vs,Ps).
post_comp([],[]).
post_comp([D|Ds],[X|Xs]):-
post_comp0(Ds,D,Xs,X),
post_comp(Ds,Xs).
post_comp0([],_,[],_).
post_comp0([D2|Ds],D1,[X2|Xs],X1):-
post_comp1(D1,1,D2,X1,X2),
post_comp0(Ds,D1,Xs,X1).
post_comp1([],_,_,_,_).
post_comp1([V1|Vs1],N,Vs2,X1,X2):-
post_comp2(Vs2,1,V1,N,X2,X1),
N1 is N+1,
post_comp1(Vs1,N1,Vs2,X1,X2).
post_comp2([],_,_,_,_,_).
post_comp2([V2|Vs2],N2,V1,N1,X2,X1):-
post_comp3(V2,N2,X2,V1,N1,X1),
N3 is N2 + 1,
post_comp2(Vs2,N3,V1,N1,X2,X1).
post_comp3(V2,N2,X2,V1,N1,X1) :-
( ( incompatible(V2,V1)
; incompatible(V1,V2)
)
-> X2 #\= N2 #\/ X1 #\= N1
; true
).
Note that the code is relatively straightforward, except for the (quadruple) loop to post the incompatibility constraints. This is due to the way I wanted to reuse the predicates in the question. In practice, one might want to change the way the data is presented.
The problem can then be solved with the following query (in SWI-prolog):
?- state(Xs, T), labeling([min(T)], Xs).
T = 1900, Xs = [2, 1, 2] ?
In SICStus prolog, one can write instead:
?- state(Xs, T), minimize(labeling([], Xs), T).
Xs = [2,1,2], T = 1900 ?
Another short predicate could then transform back the [2,1,2] list into [a2,b1,c2] if that format was expected.
can you explain this piece of code for me?
teile_Liste([],[],[]).
teile_Liste([X],[X],[]).
teile_Liste([X,Y|Liste],[X|Liste1],[Y|Liste2]) :-
teile_Liste(Liste,Liste1,Liste2).
?- teile_Liste([a,b,c,d,e],X,Y).
X = [a, c, e],
Y = [b, d] .
I don't understands whats happing there. I even looked into it with trace but that didn't help me either.
Greetings
teile_Liste/3 is true for the special case of all arguments being empty lists:
teile_Liste([],[],[]).
teile_Liste/3 is also true for the special case of two identical one-element list on positions 1 and 2, and the empty list on position 3.
teile_Liste([X],[X],[]).
In the one other case that shall be true we use induction:
The case is described as
teile_Liste([X,Y|Liste],[X|Liste1],[Y|Liste2])
So the argument at position 1 must be a list of at least 2 elements, which also appear as element of lists at argument positions 2 and 3, distributed.
But we also want to say something about the smaller lists Liste, Liste1, Liste2, which cannot be whatever they are but which must follow the recursive relationship:
teile_Liste(Liste,Liste1,Liste2).
Evidently this is a specification to relate three lists in a way such that the first list's contents are distributed over the other two lists. This specification is complete enough for Prolog to build one list if two other are giving, or to fail the query if that is not possible (as in teile_Liste([a],[b],[X,Y]): NOPE!). Alternatively if all three lists are given, Prolog can verify that the relationship holds.
And so the proof proceeds:
QUERY
teile_Liste([a,b,c,d,e],X,Y).
Only the head of the 3rd clause matches that query. It's a rule. The rule's variables are set to be as follows by unification:
CLAUSE MATCH 1
teile_Liste([a,b|[c,d,e]],[a|Liste1],[b|Liste2]) :-
teile_Liste([c,d,e],Liste1,Liste2).
According to the principles of Prolog, we shall now prove the body of the matching rule:
teile_Liste([c,d,e],Liste1,Liste2).
CLAUSE MATCH 2
Again, only the 3rd clause matches (rule application B):
teile_Liste([c,d|[e]],[c|Liste1],[d|Liste2]) :-
teile_Liste([e],Liste1,Liste2).
According to the principles of Prolog, we shall now prove the body of the matching rule:
teile_Liste([e],Liste1,Liste2).
CLAUSE MATCH 3
Only the head of the second clause matches.
This is a "base case match", i.e. we match a fact instead of a rule; there won't be any recursion. All the rules encountered have also been completely worked off. So we are done with "query success". We just need to check what happens to the variables of the query. These are printed out by the Prolog Toplevel.
So we match the fact:
teile_Liste([e],[e],[])
thus forcing the previously-unknown values of List1 and List2 to be [e] and [], respectively.
These are same variables than the ones of CLAUSE MATCH 2, so the head there can now be written
teile_Liste([c,d|[e]],[c|[e]],[d|[]])
or simpler:
teile_Liste([c,d,e],[c,e],[d])
This means that for CLAUSE MATCH 1:
Liste1 = [c,e]
Liste2 = [d]
so the head there is equal to
teile_Liste([a,b|[c,d,e]],[a|[c,e]],[b|[d]])
or simpler:
teile_Liste([a,b,c,d,e],[a,c,e],[b,d])
and thus the variables of the query are:
X = [a,c,e]
Y = [b,d]
...which is what is printed out.
teile_Liste describes a relation, such that
the triple ( [], [], []) is in the relation;
the triple ( [X], [X], []) is in the relation, whatever value the variable X has (or none); this means the first two argument are the same, one-element, list, and the third argument is an empty list;
the triple ( [X, Y | Z], [X | Z1], [Y | Z2]) is in the relation, if
( Z, Z1, Z2) is in this relation, whatever the values of X, Y, Z, Z1 and Z2,
where each reference to a variable with the same name refers to the same value:
relation( [X, Y | Z], [X | Z1], [Y | Z2] ) :-
relation( Z, Z1 , Z2 ).
This means that relation teile_Liste( A, B, C) holds
teile_Liste( A, B, C) :-
whenever
A = [X, Y | Z], % and
B = [X | Z1], % and
C = [ Y | Z2], % and the relation
teile_Liste( Z, Z1, Z2 ). % holds.
meaning,
the first argument is a list starting with X and Y and having more elements in it after that, Z;
the second list argument starts with X and has more elements in it, Z1;
the third list argument starts with Y and has more elements in it, Z2;
Z, Z1, Z2 relate in the same fashion, so overall it is:
[X, Y, X1, Y1, X2, Y2, ....., XN, YN]
[X, X1, X2, ....., XN ]
[ Y, Y1, Y2, ....., YN]
(when recursion ends on the ([], [], []) case, so, the first list argument's length is even), or
[X, Y, X1, Y1, X2, Y2, ....., XN1, YN1, XN]
[X, X1, X2, ....., XN1 , XN]
[ Y, Y1, Y2, ....., YN1 ]
(when recursion ends on the ([XN], [XN], []) case, so, the first list argument's length is odd).
I wanted to ask how I can simplify expressions like:
1+2+a*5+0/b-c*0
= 3+a*5
And especially how can I separate such expressions in lists.
It's actually kind of fun in Prolog, because you don't need to do anything too magical to make it work.
?- X-Y = 1+2+a*5+0/b-c*0.
X = 1+2+a*5+0/b,
Y = c*0.
So you could start by doing something like this:
simplify(C, C) :- atom(C) ; number(C).
simplify(X+Y, X1+Y1) :- simplify(X, X1), simplify(Y, Y1).
simplify(X*Y, X1*Y1) :- simplify(X, X1), simplify(Y, Y1).
simplify(X/Y, X1/Y1) :- simplify(X, X1), simplify(Y, Y1).
simplify(X-Y, X1-Y1) :- simplify(X, X1), simplify(Y, Y1).
This is an identity transform: it doesn't do anything.
?- simplify(1+2+a*5+0/b-c*0, Result).
Result = 1+2+a*5+0/b-c*0.
Now you can add rules for specific cases:
simplify(X*0, 0).
simplify(0*X, 0).
Now you get multiple results:
?- simplify(1+2+a*5+0/b-c*0, Result).
Result = 1+2+a*5+0/b-c*0 ;
Result = 1+2+a*5+0/b-0 ;
You could add a rule for constant folding:
simplify(X+Y, C) :- number(X), number(Y), C is X+Y.
You know, just have fun with it.
Lists aren't really any easier to work with, but you can make them using the "univ" operator: =..:
?- 1+2+a*5+0/b-c*0 =.. R.
R = [-, 1+2+a*5+0/b, c*0].
It's possible to simplify expressions in Prolog using unification, but this sometimes leads to unexpected results. In this example, two different variables in an expression are unified when "matching" a pattern, even if they were intended to be distinct:
:- initialization(main).
simplify(A+A,2*A).
main :-
simplify(A+B,C),
writeln(C).
In this case, simplify(A+B,C) would unify A with B.
To solve this problem, I use subsumes_term/2 to match a pattern without unifying the variables in an expression. subsumes_term(A+A,Input) will not match A+B unless A is already unified with B:
simplify(Input,2*A) :-
subsumes_term(A+A,Input).
This subsumes_term/2 predicate is often useful for metaprogramming: I used it to write a Prolog-to-Minizinc compiler.
Let's say I have Prolog facts such as:
fact1(x, [y1, y2, y3, y4]).
fact2(z1, [s, t, u, **y3**).
fact2(z2, [o, p, **y1**, q, r]).
fact2(z3, [**y1**, m, **y3**, n]).
fact2(z4, [j, k, **y4**, l]).
fact2(z5, [**y2**, d, e, f, g, h, i]).
fact2(z6, [a, b, c, **y4**]).
And, I want a query such that I get all the 'z' who are related to x.
Thus, this query should output z1, z2, z3, z4, z5, z6 because they all contain an element y1, y2, y3, and/or y4 because they are related to x in the first Prolog fact.
The following code outputs all of z's relation's of fact2:
fact2(_,X).
And the following code outputs x's relation of fact1:
fact1(x,X).
Thus, I figured that I would need to get the intersection of the two sets with the following code, but it doesn't work.
xyz(X):-
intersection(fact2(_,X),fact2(x,X),X).
This doesn't work, can someone lead me in the right direction?
With the query, what I need to get is: z1, z2, z3, z4, z5, z6 because they all contain either y1, y2, y3, and/or y4 due to the fact that those are all related to x int he first fact.
If you need clarification, please let me know. Thank you.
this snippet
xyz(L):-
fact1(x, L1),
setof(K, L2^I^(fact2(K, L2), intersection(L1,L2,I), I \= []), L).
yields
?- xyz(L).
L = [z1, z2, z3, z4, z5, z6].
You must instantiate each term on fact2 and fact1 in a different variable, otherwise you are forcing the instances to match identically. Also, lose the asterics on the facts "fact2", and close the clasp in the first fact "fact2", after the element y3.
If you wanna keep the asterics, then enclose them between single quotation mark.
fact1(x, [y1, y2, y3, y4]).
fact2(z1, [s, t, u, '**y3**']).
fact2(z2, [o, p, y1, q, r]).
fact2(z3, [y1, m, y3, n]).
fact2(z4, [j, k, y4, l]).
fact2(z5, [y2, d, e, f, g, h, i]).
fact2(z6, [a, b, c, y4]).
xyz(Z):- fact2(Z,X),fact1(_,Y), intersection(X,Y,I), I\=[].
Then try asking:
?- xyz(X)
or this if you want all the answers in a single list L:
?- setof(Z, xyz(Z), L).
I have some cartesian coordinates definition:
point(a, 5, 1).
point(b, 4, 2).
point(c, 3, 3).
point(d, 2, 4).
And a "route" definition:
route(path1, [a, c, b, d]).
Then I have a function to calculate the distance between two points, like this:
distance(P1, P2, D):-point(P1, X1, Y1), point(P2, X2, Y2),
Z is ((X2-X1)*(X2-X1))+((Y1-Y2)*(Y1-Y2)),
Z >= 0,
D is sqrt(Z).
How can I calculate the full distance of a route?
Also, if I have several routes how can I find the longest one?
Edit: should be correct now
First, to find the length of a given route, using the distance function you have provided:
path_len(Path, D) :- route(Path, [First|Points]), path_len(Points, First, 0, D).
path_len([], _Last, Dist, Dist).
path_len([P|Points], Prev, DAcc, Dist) :-
distance(Prev, P, D),
NDAcc is DAcc+D,
path_len(Points, P, NDAcc, Dist).
where DAcc is an accumulator for the distance so far (initialized with 0). You can query this with
?- path_len(path1, D).
If you have several routes defined already (do I understand this correctly?), path_len/2 will calculate the total distance of all of them by backtracking. You can then build distance-path pairs and use keysort/2 to sort them and maybe reverse/2 to put the last one first:
longest_path(Path, Dist) :-
bagof(D-P, path_len(P, D), DPPairs),
keysort(DPPairs, Sorted),
reverse(Sorted, [Dist-Path|_]).
You could have also used last/2 instead of reverse/2 on the last line of the predicate definition:
last(Sorted, Dist-Path).