Prolog: Replace list items using facts - prolog

im trying to make a function that loops through a list and replaces the element if it matches a fact.
I was able to implement a simple replacement that replaces every element in the list.
replace([X|T], Y, [Y|T2]) :- replace(T,Y,T2).
replace([],X,[X]).
so this just replaces every list item in X with Y.
Now i want to replace every list item in X using a fact like so:
replace([1,2,3], [ rule(1, [one]), rule(2, [two]) ], Result)
so if the list is [1,2,3], the result will be [one, two, 3]
how would I do this ?

I do prefer to use higher order library support
replace(In, Replacements, Out) :-
maplist(replace_one(Replacements), In, Out).
replace_one(RepList, Rep, Val) :-
memberchk(rule(Rep, [Val]), RepList) -> true ; Rep = Val.

I think it can be simply:
replace([], _, []).
replace([H|T], Rules, [R|TR]) :-
( memberchk(rule(H, [R]), Rules)
-> true
; H = R
),
replace(T, Rules, TR).

You can do it by adding a second rule that goes through the list of replacements, and either picks the first one that matches, or leaves the item unchanged, like this:
replace([],_,[]).
replace([H|T], L, [RH|RT]) :- replace(T,L,RT), replace_one(H, L, RH).
replace_one(H, [], H).
replace_one(H, [rule(H,B)|_], B).
replace_one(H, [rule(A,_)|T], R) :- H \= A, replace_one(H, T, R).
Demo on ideone.

Related

Append list of lists elements in Prolog

I'm learning Prolog for about a week, so I'm a newbie.
I'm trying to do a function, that appends, the elements of a list of lists.
So the input would be: [ [[a,b,c],[g,h,i]], [[j,k,l],[m,n,o]], [[s,t,u],[v,w,x]] ].
And the output would be : [ [a,b,c,j,k,l,s,t,u], [g,h,i,m,n,o,v,w,x] ].
Or
Input: [ [[a,b], [c,d]], [[e,f], [g,h]], [[i,j],[k,l]] ].
Output: [ [a,b,e,f,i,j], [c,d,g,h,k,l] ].
It would be important, that it has to work with a lot of elements, not only 3.
I wrote this, but it only works with 2 elements, so i can only do it, with pairs.
merge([],[],[]).
merge(L1,[],L1).
merge([H1|T1],[H2|T2],LL):-
append(H1, H2, HE),
merge(T1,T2,TE),
append([HE], TE, LL).
If I understand your question correctly...
First, if you know that your input has exactly two levels of nesting in it, and if your Prolog had higher-order predicates for mapping and for folding, and if you could compose them, you could simply write:
merge_foldl([], []).
merge_foldl([X|Xs], R) :-
reverse([X|Xs], [Y|Ys]),
foldl(maplist(append), Ys, Y, R).
This works as expected for SWI-Prolog.
Here it is with your two examples:
?- merge_foldl([ [[a,b,c],[g,h,i]], [[j,k,l],[m,n,o]], [[s,t,u],[v,w,x]] ], R).
R = [[a, b, c, j, k, l, s, t, u], [g, h, i, m, n, o, v, w, x]].
?- merge_foldl([ [[a,b], [c,d], [e,f]], [[g,h], [i,j], [k,l]] ], R).
R = [[a, b, g, h], [c, d, i, j], [e, f, k, l]].
If you don't have access to neither foldr nor foldl, you would have to hardcode the folding:
merge([], []).
merge([X|Xs], Result) :-
merge_maplist(Xs, X, Result).
merge_maplist([], Result, Result).
This is not all, but it says that if you are at the end of the list of lists, the last element is the result.
Now you have to define the step where you append to the front of each sublist. This is easier with maplist:
merge_maplist([X|Xs], Prev, Result) :-
merge_maplist(Xs, X, Result0),
maplist(append, Prev, Result0, Result).
Note that here we are "emulating" a right fold by using a non-tail-recursive definition: we are doing the appending in reverse, after the recursive step. For a tail-recursive definition (identical to hard-coded left fold), you would have to reverse the original list first!
So you keep on peeling off one list of lists from your input until you are done. Then, you use maplist to apply append/3 to each pair of lists from the previous element and the result so far, to get the final result.
If you don't have access to maplist either, you'd have to hardcode the mapping as well. For the three arguments that append/3 takes:
map_append([], [], []).
map_append([X|Xs], [Y|Ys], [Z|Zs]) :-
append(X, Y, Z),
map_append(Xs, Ys, Zs).
and your merge/2 and merge_/3 become:
merge([], []).
merge([X|Xs], Result) :-
merge_(Xs, X, Result).
merge_([], Result, Result).
merge_([X|Xs], Prev, Result) :-
merge_(Xs, X, Result0),
map_append(Prev, Result0, Result).
This is a lot of code for something that can be solved quite nicely if you have higher-order predicates.

prolog - sublist of list - alternative approach

I implemented function to get sublist of list, for example:
sublist([1,2,4], [1,2,3,4,5,1,2,4,6]).
true
sublist([1,2,4], [1,2,3,4,5,1,2,6]).
false
look at my solution:
my_equals([], _).
my_equals([H1|T1], [H1|T2]) :- my_equals(T1, T2).
sublist([], _).
sublist(L1, [H2|T2]) :- my_equals(L1, [H2|T2]); sublist(L1, T2).
Could you give me another solution ? Maybe there is exists some predefined predicate as my_equals ?
You can unify a sublist using append/3, like this:
sublist(SubList, List):-
append(_, Tail, List),
append(SubList, _, Tail).
The first call to append/3 will split List into two parts (i.e. dismiss the some "leading" items from List.
The second call to append/3 will check whether SubList is itself a sublist of Tail.
As #false's suggests it would be better, at least for ground terms, to exchange goals,
sublist(SubList, List):-
append(SubList, _, Tail),
append(_, Tail, List).
There's also a DCG approach to the problem:
substr(Sub) --> seq(_), seq(Sub), seq(_).
seq([]) --> [].
seq([Next|Rest]) --> [Next], seq(Rest).
Which you would call with:
phrase(substr([1,2,4]), [1,2,3,4,5,1,2,4,6]).
You can define:
sublist(Sub, List) :-
phrase(substr(Sub), List).
So you could call it by, sublist([1,2,4], [1,2,3,4,5,1,2,4,6])..
Per #mat's suggestion:
substr(Sub) --> ..., seq(Sub), ... .
... --> [] | [_], ... .
Yes, you can have a predicate named .... :)
Per suggestions from #repeat and #false, I changed the name from subseq (subsequence) to substr (substring) since the meaning of "subsequence" embraces non-contiguous sequences.
This is an alternative solution to Lurkers, which is slightly faster,
assuming S is much shorter than L in length and thus the phrase/3 DCG
translation time is negligible:
sublist(S, L) :-
phrase((..., S), L, _).
If S=[X1,..,Xn] it will DCG translate this into a match I=[X1,..,Xn|O]
before execution, thus delegating my_equals/2 completely to Prolog
unification. Here is an example run:
?- phrase((..., [a,b]), [a,c,a,b,a,c,a,b,a,c], X).
X = [a, c, a, b, a, c] ;
X = [a, c] ;
false.
Bye
P.S.: Works also for other patterns S than only terminals.
Maybe there is exists some predefined predicate
If your Prolog has append/2 from library(lists):
sublist(S, L) :- append([_,S,_], L).
Another fairly compact definition, available in every (I guess) Prolog out there:
sublist(S, L) :- append(S, _, L).
sublist(S, [_|L]) :- sublist(S, L).
Solution in the original question is valid just, as has been said, remark that "my_equals" can be replaced by "append" and "sublist" loop by another append providing slices of the original list.
However, prolog is (or it was) about artificial intelligence. Any person can answer immediately "no" to this example:
sublist([1,1,1,2], [1,1,1,1,1,1,1,1,1,1] ).
because a person, with simple observation of the list, infers some characteristics of it, like that there are no a "2".
Instead, the proposals are really inefficient on this case. By example, in the area of DNA analysis, where long sequences of only four elements are studied, this kind of algorithms are not applicable.
Some easy changes can be done, with the objective of look first for the most strongest condition. By example:
/* common( X, Y, C, QX, QY ) => X=C+QX, Y=C+QY */
common( [H|S2], [H|L2], [H|C2], DS, DL ) :- !,
common( S2, L2, C2, DS, DL ).
common( S, L, [], S, L ).
sublist( S, L ) :-
sublist( [], S, L ).
sublist( P, Q, L ) :- /* S=P+Q */
writeln( Q ),
length( P, N ),
length( PD, N ), /* PD is P with all unbound */
append( PD, T, L ), /* L=PD+T */
common( Q, T, C, Q2, _DL ), /* S=P+C+Q2; L=PD+C+_DL */
analysis( L, P, PD, C, Q2 ).
analysis( _L, P, P, _C, [] ) :- !. /* found sublist */
analysis( [_|L2], P, _PD, C, [] ) :- !,
sublist( P, C, L2 ).
analysis( [_|L2], P, _PD, C, Q2 ) :-
append( P, C, P2 ),
sublist( P2, Q2, L2 ).
Lets us try it:
?- sublist([1,1,1,2], [1,1,1,1,1,1,1,1,1,1]).
[1,1,1,2]
[2]
[2]
[2]
[2]
[2]
[2]
[2]
[2]
false.
see how "analysis" has decided that is better look for the "2".
Obviously, this is a strongly simplified solution, in a real situation better "analysis" can be done and patterns to find must be more flexible (the proposal is restricted to patterns at the tail of the original S pattern).

How to split list in Prolog?

I want to write a predicate split(List, Pivot, Result) holds when Result is a list of sublists that List divided by Pivot. For example split(['_', '_', '#', '_', '#', '_'], '#', [['_','_'], ['_'], ['_']]) is true.
My code is like this and it doesn't work:
split(List, Pivot, Result) :-
split(List, Pivot, _, _, Result).
split(List, Pivot, Left, Right, X|Xs) :-
append(Left, [Pivot|Right], List),
!,
member(Pivot, Right)
-> X = [Left],
split(Right, Pivot, _, _, Xs)
; X = [[Left]|[Right]].
I don't think my approach is clever either. Can someone give me some advice? Thank you.
We can preserve logical-purity and get the job done in no time, simply by using the right tools!
Let's use the meta-predicate splitlistIf/3 and the reified equality predicate (=)/3 in a query:
?- Xs = ['_','_',#,'_',#,'_'], splitlistIf(=(#),Xs,Ys).
Xs = [ '_','_' ,#,'_',#,'_' ]
Ys = [['_','_'], ['_'],['_']]. % succeeds deterministically
Here is one way of doing it:
split(L,P,R):-split(L,P,[],R).
split([],_,[],[]).
split([],_,S,[S]) :- S \= [].
split([P|T],P,[],R) :- split(T,P,[],R).
split([P|T],P,L,[L|R]) :- L \= [], split(T,P,[],R).
split([H|T],P,S,R) :- H \= P, append(S, [H], S2), split(T,P,S2,R).
Demo.
split/4 predicate adds a parameter at position 3 which means "list constructed so far". split/3 is a simple redirection with the "list so far" set to [].
Clauses on lines 2 and 4 handle situations when two Pivots are in a row, and when the pivot is detected at the end of the sequence. These clauses prevent insertion of empty lists.

Prolog programs - how to make it work?

I have these two programs and they're not working as they should. The first without_doubles_2(Xs, Ys)is supposed to show that it is true if Ys is the list of the elements appearing in Xs without duplication. The elements in Ys are in the reversed order of Xs with the first duplicate values being kept. Such as, without_doubles_2([1,2,3,4,5,6,4,4],X) prints X=[6,5,4,3,2,1] yet, it prints false.
without_doubles_2([],[]).
without_doubles_2([H|T],[H|Y]):- member(H,T),!,
delete(H,T,T1),
without_doubles_2(T1,Y).
without_doubles_2([H|T],[H|Y]):- without_doubles_2(T,Y).
reverse([],[]).
reverse([H|T],Y):- reverse(T,T1), addtoend(H,T1,Y).
addtoend(H,[],[H]).
addtoend(X,[H|T],[H|T1]):-addtoend(X,T,T1).
without_doubles_21(X,Z):- without_doubles_2(X,Y),
reverse(Y,Z).
The second one is how do I make this program use a string? It's supposed to delete the vowels from a string and print only the consonants.
deleteV([H|T],R):-member(H,[a,e,i,o,u]),deleteV(T,R),!.
deleteV([H|T],[H|R]):-deleteV(T,R),!.
deleteV([],[]).
Your call to delete always fails because you have the order of arguments wrong:
delete(+List1, #Elem, -List2)
So instead of
delete(H, T, T1)
You want
delete(T, H, T1)
Finding an error like this is simple using the trace functionality of the swi-prolog interpreter - just enter trace. to begin trace mode, enter the predicate, and see what the interpreter is doing. In this case you would have seen that the fail comes from the delete statement. The documentation related to tracing can be found here.
Also note that you can rewrite the predicate omitting the member check and thus the third clause, because delete([1,2,3],9001,[1,2,3]) evaluates to true - if the element is not in the list the result is the same as the input. So your predicate could look like this (name shortened due to lazyness):
nodubs([], []).
nodubs([H|T], [H|Y]) :- delete(T, H, T1), nodubs(T1, Y).
For your second question, you can turn a string into a list of characters (represented as ascii codes) using the string_to_list predicate.
As for the predicate deleting vovels from the string, I would implement it like this (there's probably better solutions for this problem or some built-ins you could use but my prolog is somewhat rusty):
%deleteall(+L, +Elems, -R)
%a helper predicate for deleting all items in Elems from L
deleteall(L, [], L).
deleteall(L, [H|T], R) :- delete(L, H, L1), deleteall(L1, T, R).
deleteV(S, R) :-
string_to_list(S, L), %create list L from input string
string_to_list("aeiou", A), %create a list of all vovels
deleteall(L, A, RL), %use deleteall to delete all vovels from L
string_to_list(R, RL). %turn the result back into a string
deleteV/2 could make use of library(lists):
?- subtract("carlo","aeiou",L), format('~s',[L]).
crl
L = [99, 114, 108].
while to remove duplicates we could take advantage from sort/2 and select/3:
nodup(L, N) :-
sort(L, S),
nodup(L, S, N).
nodup([], _S, []).
nodup([X|Xs], S, N) :-
( select(X, S, R) -> N = [X|Ys] ; N = Ys, R = S ),
nodup(Xs, R, Ys).
test:
?- nodup([1,2,3,4,4,4,5,2,7],L).
L = [1, 2, 3, 4, 5, 7].
edit much better, from ssBarBee
?- setof(X,member(X,[1,2,2,5,3,2]),L).
L = [1, 2, 3, 5].

Prolog Subtract List Unification

I am trying to subtract one list from another in prolog. In my program the input list have blank spaces in them (e.g. [1,2,_,4])
I am getting the following output:
?- subtract([1,2,3,4],[3,4,_],L).
L = [2].
when I want my output to be
L = [1,2].
So my question is how can I prevent the blank spaces from unifying with other elements? Have been stuck on this for a while.
Assuming you want the "blank spaces" to be ignored, you can simply make a version of each list with those removed and compute their difference:
listWOblanks( [], [] ).
listWOblanks( [H|T], Tx ) :- var(H), !, listWOblanks( T, Tx ).
listWOblanks( [H|T], [H|Tx] ) :- listWOblanks( T, Tx ).
If, when the first list has a blank and the second does not, you need the result to still have a blank, you could modify the above to add a 3rd argument that tells you if any blanks were removed so you can correct the difference accordingly. I believe SWI-Prolog has a predicate, ground, which will tell you if a term has no variables in it, which would do the job w/o needing to modify listWOblanks.
larsmans is correct, the _ is an anonymous variable, and the definition of lists:subtract/3 (which I'm assuming you're using in SWI-Prolog) will always unify them to ground list members because of it's definition using memberchk/2.
If you want subtract behaviour where variables are to be treated like ground terms, then you can redefine it like this:
subtract2([], _, []) :- !.
subtract2([A|C], B, D) :-
var_memberchk(A, B), !,
subtract2(C, B, D).
subtract2([A|B], C, [A|D]) :-
subtract2(B, C, D).
Note that subtract2/3 here is nearly the same as the definition of lists:subtract/3 (try listing(subtract). to see for yourself). The only difference is the list membership predicate, var_memberchk/2, which is defined like this:
var_memberchk(A0, [A1|_]) :-
A0 == A1, !.
var_memberchk(A0, [_|R]) :-
var_memberchk(A0, R).
This checks to see if a variable, atom or term is in the list. So, trying this we get:
?- subtract2([1,2,3,4],[3,4,_],L).
L = [1, 2].
Note that it still works if we name the variables, as you'd expect:
?- subtract2([1,2,A,3,B,4],[3,A,4],L).
L = [1, 2, B].
It also works if we explicitly give names to anonymous variables, like this:
?- subtract2([1,2,_A,3,_B,4],[3,_A,4],L).
L = [1, 2, _B].
Finally, note that since _ doesn't have a name, subtract2/3 will never be able to match it to other anonymous variables in either list, for example:
subtract2([1,2,_,4],[3,_,4],L).
L = [1, 2, _G415].
Where _G415 is the anonymous global variable denoted by the _ in the first input list. The second is a different global variable (like _G416, for instance), so could never match the anonymous variable in the first list.
Another way:
% Uses list catenation to generate sublists /subtraction
conc([], L, L).
conc([X|L1], L2, [X|L3]) :-
conc(L1, L2, L3).
% Finds all list members that have values and then
% use list catenation to generate the sublists
subtract(L1, L2, L3) :-
findall(D, (nth0(N, L2, D), nonvar(D)), PureL2),
conc(L3, PureL2, L1).
This assumes that only one list has '_', but you could do the same findall for L1 if both lists have the same problem.

Resources