I'm new to Prolog. I managed to learn C and Java relatively quickly but and Prolog is giving me a lot of trouble. My trouble is understanding lists and writing functions? For example. We have this automaton:
I can do this task in C and Java, no problems. But the course wants Prolog. With my current knowledge I could do things like this:
% 1. Check whether all integers of the list are < 10.
less_than_10([]).
less_than_10([Head|Tail]) :-
Head < 10,
less_than_10(Tail).
Just so you know where my knowledge is at. Very basic. I did read the list chapter in Learn Prolog Now but it's still confusing me. They gave us a hint:
Every node should be presented like:
delta(1, d, 2)
% or
alpha(2, a, 2)
They also told us to pass the list in questions to a predicate that returns true if the list fits the automaton and false if not:
accept([d,a,b,a,b,b,b,c,d,c]).
The output is true.
Where to go from here? I'm guessing the first step is to check if the Head of the list is 1. How do I do that? Also, should I add every node as fact into the knowledge base?
So that's pretty easy. Super-direct, much more than if you were using C or Java.
Let's write an interpreter for this graph that:
Is given a list of named transitions ;
Walks the transitions using the given graph along a path through that graph ;
Accepts (Succeeds) the list if we end up at a final state ;
Rejects (Fails) the list if we do not ;
And.. let's say throws an exception if the list cannot be generated by the given graph.
Prolog gives us nondeterminism for free in case there are several paths. Which is nice.
We do not have an class to describe the automaton. In a sense, the Prolog program is the automaton. We just have a set of predicates which describe the automaton via inductive definitions. Actually, if you slap a module definition around the source below, you do have the object.
First describe the graph. This is just a set of Prolog facts.
As required, we give the transitions (labeled by atoms) between nodes (labeled by integers), plus we indicate which are the start and end nodes. There is no need to list the nodes or edges themselves.
delta(1,d,2).
delta(2,a,2).
delta(2,b,2).
delta(2,d,4).
delta(2,e,5).
delta(2,c,3).
delta(3,d,6).
delta(6,c,5).
start(1).
end(4).
end(5).
A simple database. This is just one possible representation of course.
And now for the graph walker. We could use Definite Clause Grammars here because we are handling a list, but lets' not.
First, a predicate which "accepts" or "rejects" a list of transitions.
It looks like:
% accepts(+Transitions)
It starts in a start state, then "walks" by removing transitions off the list until the list is empty. Then it checks whether it is at an end state.
accepts(Ts) :- % accept the list of transitions if...
start(S), % you can accept the list starting
accepts_from(S,Ts). % from a start state
accepts_from(S,[T|Ts]) :- % accepts the transitions when at S if...
delta(S,T,NextS), % there is a transition S->NextS via T
accepts_from(NextS,Ts). % and you can accept the remaining Ts from NextS. (inductive definition)
accepts_from(S,[]) :- % if there is no transition left, we accept if...
end(S). % we are a final state
Ah, we wanted to throw if the path was impossible for that graph. So a little modification:
accepts(Ts) :- % accept the list of transitions if...
start(S), % you can accept the list starting
accepts_from(S,Ts). % from a start state
accepts_from(S,[T|Ts]) :- % accepts the transitions when at S if...
delta(S,T,NextS), % there is a transition S->NextS via T
accepts_from(NextS,Ts). % and you can accept the remaining Ts from NextS.
accepts_from(S,[T|Ts]) :- % accepts the transitions when at S if...
\+ delta(S,T,NextS), % there is NO transition S->NextS via T
format(string(Txt),"No transition at ~q to reach ~q",[S,[T|Ts]]),
throw(Txt).
accepts_from(S,[]) :- % if there is no transition left, we accept if...
end(S). % we are a final state
And so:
?- accepts([d,a,b,a,b,b,b,c,d,c]).
true ; % yup, accepts but maybe there are other paths?
false. % nope
?- accepts([d,a,a,a,a,e]).
true ;
false.
?- accepts([d,a,a,a,a]).
false.
?- accepts([d,c,e,a]).
ERROR: Unhandled exception: "No transition at 3 to reach [e,a]"
The above code should also be able to find acceptable paths through the graph. But it does not:
?- accepts(T).
... infinite loop
This is not nice.
The primary reason for that is that accept/2 will immediately generate an infinite path looping at state 2 via transitions a and b. So one needs to add a "depth limiter" (the keyword is "iterative deepening").
The second reason would be that the test \+ delta(S,T,NextS) would succeed at node 4 for example (because there is nowhere to go from that node) and cause an exception before trying out the possibility of going nowhere (the last clause). So when generating, throwing is a hindrance, one just wants to reject.
Addendum: Also generate
The following only accepts/rejects and does not throw, but can also generate.
:- use_module(library(clpfd)).
accepts(Ts,L) :- % Accept the list of transitions Ts of length L if
start(S), % ...starting from a start state S
accepts_from(S,Ts,L). % ...you can accept the Ts of length L.
accepts_from(S,[T|Ts],L) :- % Accept the transitions [T|Ts] when at S if
(nonvar(L)
-> L >= 1
; true), % L (if it is bound) is at least 1 (this can be replaced by L #> 0)
delta(S,T,SN), % ...and there is a transition S->SN via T
Lm #= L-1, % ...and the new length is **constrained to be** 1 less than the previous length
accepts_from(SN,Ts,Lm). % ...and you can accept the remaining Ts of length Lm from SN.
accepts_from(S,[],0) :- % If there is no transition left, length L must be 0 and we accept if
end(S). % ...we are a final state.
delta(1,d,2).
delta(2,a,2).
delta(2,b,2).
delta(2,d,4).
delta(2,e,5).
delta(2,c,3).
delta(3,d,6).
delta(6,c,5).
start(1).
end(4).
end(5).
generate :-
between(0,7,L),
findall(Ts,accepts(Ts,L),Bag),
length(Bag,BagLength),
format("Found ~d paths of length ~d through the graph\n",[BagLength,L]),
maplist({L}/[Ts]>>format("~d : ~q\n",[L,Ts]),Bag).
And so:
?- accepts([d,a,b,a,b,b,b,c,d,c],_).
true ;
false.
?- accepts([d,a,a,a,a],_).
false.
?- accepts([d,c,e,a],_).
false.
?- generate.
Found 0 paths of length 0 through the graph
true ;
Found 0 paths of length 1 through the graph
true ;
Found 2 paths of length 2 through the graph
2 : [d,d]
2 : [d,e]
true ;
Found 4 paths of length 3 through the graph
3 : [d,a,d]
3 : [d,a,e]
3 : [d,b,d]
3 : [d,b,e]
true ;
Found 9 paths of length 4 through the graph
4 : [d,a,a,d]
4 : [d,a,a,e]
4 : [d,a,b,d]
4 : [d,a,b,e]
4 : [d,b,a,d]
4 : [d,b,a,e]
4 : [d,b,b,d]
4 : [d,b,b,e]
4 : [d,c,d,c]
true
Here's my answer. I sought to completely separate the data from the logic.
There are rules to infer the possible paths, start and end nodes.
The edge/2 predicate stands for either an alpha or a delta line.
The path (DCG) predicate describes a list of edges that ends with an end node.
The start and end nodes are inferred using the start_node/1 and end_node/1 predicates.
Finally, the phrase/3 is used to describe the list of paths that are valid automata.
delta(1, d, 2).
delta(2, d, 4).
delta(2, e, 5).
delta(2, c, 3).
delta(3, d, 6).
delta(6, c, 5).
alpha(2, a, 2).
alpha(2, b, 2).
edge(Node, Node, Via) :-
alpha(Node, Via, Node).
edge(From, To, Via) :-
delta(From, Via, To).
path(From, To) -->
{ end_node(To),
dif(From, To),
edge(From, To, Via)
},
[Via].
path(From, To) -->
{edge(From, Mid, Via)},
[Via],
path(Mid, To).
start_node(Node) :-
node_aux(start_node_aux, Node).
end_node(Node) :-
node_aux(end_node_aux, Node).
start_node_aux(Node) :-
edge(Node, _, _),
\+ edge(_, Node, _).
node_aux(Goal, Node) :-
setof(Node, call(Goal, Node), Nodes),
member(Node, Nodes).
end_node_aux(Node) :-
edge(_, Node, _),
\+ edge(Node, _, _).
automaton -->
{start_node(Start)},
path(Start, _End).
accept(Steps) :-
length(Steps, _N),
phrase(automaton, Steps).
I suspect that David did not use Definite Clause Grammars because you should be familiar with the basics before learning DCGs.
Related
Having a background in Prolog, I'm struggling to convert this DLV (which has builtin predicates to handle lists similarly to Prolog) program into CLINGO.
path(X,Y,[X|[Y]]) :- rel(X,Y).
path(X,Y,[X|T]) :- rel(X,Z), path(Z,Y,T), not #member(X,T).
rel(X,Y) :- edge(X,Y).
rel(X,Y) :- edge(Y,X).
edge(a,b).
edge(a,c).
edge(b,a).
edge(b,c).
edge(e,c).
So far I managed to achieve this:
path(X,Y,cons(X,cons(Y,empty))) :- edge(X,Y).
path(X,Y,cons(X,L)) :- edge(X,Z), path(Z,Y,L), not member(X,path(Z,Y,L)).
member(X,path(X,T,cons(X,Y))) :- path(X,T,cons(X,Y)).
member(X,path(S,X,cons(S,L))) :- path(S,X,cons(S,L)).
member(X,path(S,T,cons(S,cons(Z,L)))) :- member(X,path(Z,T,cons(Z,L))).
% same edges
but I get the error unsafe variables in in file - at line 7 and column 1-72 and I don't fully understand why. I was wondering if anyone could help.
You never defined what S could be.
Add edge(S,Z) to the rule body on line 7 to get rid of that error. Or if you want to define a vertex predicate you could use vertex(S) as well.
So I fixed your code with the cons-lists. This approach is unusual because lists are not a key feature in asp like they are in prolog. Ok, here is my solution (works for directed graphs):
edge(a,b).
edge(a,c).
edge(b,c).
edge(c,a).
edge(c,d).
edge(d,b).
node(X) :- edge(X,_). % get nodes
node(X) :- edge(_,X). % get nodes
num(N):- {node(X)} == N. % count nodes
step(1..N) :- num(N). % mark possible steps
path(X,Y,2,cons(X,cons(Y,empty))) :- edge(X,Y).
path(A,C,NN+1,cons(A,L)) :- edge(A,B), path(B,C,NN,L), step(NN+1).
member(X,path(X,Y,N,cons(X,L))) :- path(X,Y,N,cons(X,L)).
member(Y,path(X,Y,N,cons(X,L))) :- path(X,Y,N,cons(X,L)).
member(M,path(S,T,NN+1,cons(S,cons(Z,L)))) :- member(M,path(Z,T,NN,cons(Z,L))), path(S,T,NN+1,cons(S,cons(Z,L))).
track(Y,Z,N,L):- {member(X,path(Y,Z,N,L)):node(X)} == N, path(Y,Z,N,L).
#show track/4.
At first you need to know all the of vertices to calculate their number. Also I introduced the predicate step to validate a depth for the path. path also has a depth-counter now as third argument. All possible paths are stored within path/4, cycles are allowed. The member/2 predicate is generated to show all the member vertices within a path/4. In a last step all path's are forwarded to the predicate track/4 if and only if the number of distinct member vertices equals the path length. Since duplicates will not be counted this condition makes sure that only paths without cycles are forwarded. Please note that all of the above steps are forced. There is exactly one answer set for every graph.
So let's have a look at a more ASP like solution. Normally you would ask a specific question ('path from a to b with length n') instead of a generic one ('all possible paths from all nodes to all nodes with every possible length'). The following code needs to have a start node (start/1) and an end node (end/1). The program forces a (random) order by assigning exactly one index number to each vertex within the predicate order/2. The order predicate is copied to the path predicate as long as the index is not larger than the index of the end node (path(S,N):- order(S,N), maxZ(Z), S<=Z.). The only constrains is that within the order of path every 2 neighboring vertices are connected with an edge. The constraint line is read as It can not ne the case that there is a node S1 on position N within a path and a node S2 on position N+1 within a path and there is no edge from S1 to S2.
edge(a,b).
edge(a,c).
edge(b,c).
edge(c,a).
edge(c,d).
edge(d,b).
start(a).
end(d).
node(X) :- edge(X,_). % get nodes
node(X) :- edge(_,X). % get nodes
num(N):- {node(X)} == N. % count nodes
step(1..N) :- num(N). % mark possible steps
order(1,A):- start(A). % start has index 1
maxZ(Z):- end(E), order(Z,E), step(Z). % end has index maxZ
{order(S,N):node(N)} == 1 :- step(S). % exactly one assignment per step
{order(S,N):step(S)} == 1 :- node(N). % exactly one assignment per node
path(S,N):- order(S,N), maxZ(Z), S<=Z. % copy order when index is not largter than end node index
:- path(N, S1), path(N+1, S2), not edge(S1,S2). % successing indices are connected through edges
#show path/2.
Given a list of lists of integers, e.g. [[3,10],[3,10,2],[5],[5,2],[5,3],[5,3,2],[5,3,10]], I want to go over each sublist and count how many of them sum to 15. In this case that would be 1, for the sublist [3,10,2].
I am aware of the predicate aggregate_all/3, but I'm having trouble writing a predicate to check each element of the list, what I have now is something like
fifteens([X|Xs]) :-
sum_list(X, 15),
fifteens(Xs).
and within another predicate I have:
aggregate_all(count, fifteens(Combinations), Value).
where Combinations is the list of lists of integers in question.
I know my fifteens predicate is flawed since it's saying that all elements of the nested list must sum to 15, but to fix this how do I take out each element of Combinations and check those individually? Do I even need to? Thanks.
First of all your fifteens/2 predicate has no because for empty list and thus it will always fails because due to the recursion eventually fifteens([]) will be called and fail.
Also you need to change completely the definition of fifteens, currently even if you add base case, it says check ALL elements-sublists to see if they sum to 15. That's Ok but I don't see how you could use it with aggregate.
To use aggregate/3 you need to express with fifteens/2, something like: for every part of my combinations list check separately each sublist i.e each member:
ifteens(L) :-
member(X,L),
sum_list(X, 15).
Now trying:
?- aggregate_all(count, ifteens([[3,10],[3,10,2],[5],[5,2],[5,3],[5,3,2],[5,3,10]]), Value).
Value = 1.
This is a job for ... foldl/4. Functional programming idioms in logic programming languages? Yes, we can!
First, summing the summable values of a list:
sum_them(List,Sum) :-
foldl(sum_goal,List,0,Sum).
sum_goal(Element,FromLeft,ToRight) :-
must_be(number,Element),
must_be(number,FromLeft),
ToRight is Element+FromLeft.
Then, counting the ones that sum to 15:
count_them(List,Count) :-
foldl(count_goal,List,0,Count).
count_goal(Element,FromLeft,ToRight) :-
must_be(list(number),Element),
must_be(number,FromLeft),
sum_them(Element,15) -> succ(FromLeft,ToRight) ; FromLeft = ToRight.
Does it work? Let's write some unit tests:
:- begin_tests(fifteen_with_foldl).
test("first test",true(R==1)) :-
count_them([[3,10],[3,10,2],[5],[5,2],[5,3],[5,3,2],[5,3,10]],R).
test("test on empty",true(R==0)) :-
count_them([],R).
test("test with 2 hist",true(R==2)) :-
count_them([[15],[],[1,1,1,1,1,10]],R).
:- end_tests(fifteen_with_foldl).
And so:
% PL-Unit: fifteen_with_foldl ... done
% All 3 tests passed
true.
I'm a very newbie to Prolog and I already need help. I looked up other similar questions but it didn't answer my question.
The problem is;
I have a list of mixed elements [Y, rat, gorilla, 30, mother(alex)]. I want to make a new list out of this with exclusively atoms.
So query should look like this.
?- atoms([Y, rat, gorilla, 30, mother(alex)], Result).
Result = [rat, gorilla].
I tried but I have no idea how to solve this. I think it should be recursive because it needs to check each item weather it's an atom or not.
atoms([], []).
atoms([H | T], Result) :-
atom(H),
append(H, [], Result).
What you want to do is called "filtering" and there is a ready-made "higher-level predicate" for this already. Why "higher level"? Because it doesn't deal in first-order "objects" only, but takes an executable goal that it calls.
Note that this is an eminently functional approach to programming and there is nothing wrong with that: fat chunks of a "logic program" are actually written in functional style. Here we go:
In SWI-Prolog, the predicate that filters is called include/3 or exclude/3.
% atoms/2 filters list Li into list Lo using the predicate atom/1
% This only works in direction Li-->Lo.
atoms(Li,Lo) :- include(atom,Li,Lo).
And a bit of unit test code:
:- begin_tests(filtering).
test("basic test", true(Result = [rat, gorilla])) :-
atoms([Y, rat, gorilla, 30, mother(alex)], Result).
:- end_tests(filtering).
And so:
?- run_tests.
% PL-Unit: filtering . done
% test passed
true.
It works.
Of course, you can always write your own atoms/2 using a recursive call (aka. using an inductive definition)
atoms_i([], []).
atoms_i([H|T], [H|Result]) :- % retain the H in the result list
atom(H), % the "guard" passes if H is atom
!, % then we commit to this branch
atoms_i(T, Result).
atoms_i([H|T], Result) :- % do not retain H in the result list
\+atom(H), % the "guard" passes if H is not atom
!, % then we commit to this branch
atoms_i(T, Result).
People will say that you can leave out the \+atom(H),! in the third clause for efficieny reasons. Although they are right, I find doing that extremely annoying as I prefer symmetry in the source code and cuts that can in principle be removed at a whim. Plus it's about time the compiler start doing some work to find that efficiency itself. It's 2020, not 1980.
Let's add a bit of unit test code:
:- begin_tests(filtering_i).
test("basic test", true(Result = [rat, gorilla])) :-
atoms_i([Y, rat, gorilla, 30, mother(alex)], Result).
:- end_tests(filtering_i).
And so:
?- run_tests.
% PL-Unit: filtering_i . done
% test passed
true.
Good.
So basically my task is to create a Set out of a given List with a predicate containing 2 parameter.
The first one is the list and the second is the Set´s value.
However somehow it gives me a List which contains the Set as the Head and a Tail with a variable:
2 ?- list2set([2,3,4,4] , X).
X = [2, 3, 4|_G2840] .
thats the code:
list2set( [] , _).
list2set([ListH|ListT] , Set ) :- member(ListH, Set) , list2set(ListT , Set).
It seems to be a really basic mistake I made.
First, there are no sets in Prolog. We have only lists1. So what you can do is to relate a list with duplicate elements to a list without. list_nub/2 would be such a definition.
To your current definition:
Already list2set([], [a]) succeeds, which can't be right. So your definition is too general. You need to replace list2set([],_) by list2set([],[]).
Then, replace member(ListH, Set) by member(ListH,ListT).
And you need another rule for the case where the element is not present:
list2set([] , []).
list2set([E|Es] , Set ) :-
member(E, Es) ,
list2set(Es , Set).
list2set([E|Es] , [E|Set] ) :-
maplist(dif(E), Es),
list2set(Es , Set).
A more compact definition that avoids redundant answers is list_nub/2.
1) Strictly speaking, one could extend unification via attributed variables2 to implement ACI-unification to have real sets.
2) To my—rough—understanding this would require the implementation of attributed variables in SICStus. Other interfaces like the current in SWI or YAP are most probably insufficient ; as they already are for CLP(B). See this discussion for more.
Here is a definition that just uses member/2.
% base case
set([], []).
% here we say that if the head is in the tail of the list
% we discard the head and create a set with the tail
% the exclamation mark is a "cut" which means that if member(H, T) was true
% prolog cannot backtrack from set([H|T], X) to set([H|T], [H|X]).
% this prevents giving extra answers that aren't sets, try removing it.
set([H|T], X):- member(H, T), !, set(T, X).
% and here we say that if the previous clause didn't match because
% head is not a member of tail then we create a set of the tail adding head.
set([H|T], [H|X]):- set(T, X).
Hope it helps!
Nice way to populate a unique list, keeping it open-ended.
You can close it with a call length(Set, _), or a hand-coded equivalent (make it deterministic, too), when you're finished:
list2set([], S):-
% length( S, _), !
close_it(S). % use var/1
Also, consider calling memberchk/2 instead of member/2.
You could also give a "smart" answer, by defining
list2set(X, X).
and saying that you allow duplicates in your representation for sets.
I want to clear a list without cutting. I tried:
filter([],[]).
filter([H|T],[H|S]) :-
H<0,
filter(T,S).
filter([H|T],S) :-
H>=0,
filter(T,S).
But it doesn't work.
Here is what happened when I tried:
?- filter([1,0,-6,7,-1],L).
L = [-6,-1]; %false
no
L=[0,-6,-1] %true
Here's one way to do it:
filter([ ],[ ]).
filter([H|T],X) :-
( H > 0 -> X = Y ; X = [H|Y] ),
filter(T,Y).
Because the if-then-else construct in Prolog is sometimes described as having a "hidden cut", meaning that Prolog will not retry (backtrack) the logical outcome in the "if" portion of this construct (it commits to the first and only outcome), it's conceivable that your course instructor might object to this solution (even though no actual cut is used).
But your solution is partially wrong. You lump the zero elements with the positive ones, where your Question's wording suggests only the positive entries need to be "cleared" from the list.