Replacing substrings in Prolog [duplicate] - replace

This question already has answers here:
How do you do a search and replace of a list with another sublist in Prolog?
(2 answers)
Closed 4 years ago.
In order to replace a substring in a string, I wrote a predicate called replace_substring. It uses SWI-Prolog's append/3 predicate:
:- initialization(main).
:- set_prolog_flag(double_quotes, chars).
main :-
replace_substring("this is a string","string","replaced string",Result),
writeln(Result).
replace_substring(String,To_Replace,Replace_With,Result) :-
append(First,To_Replace,String),
append(First,Replace_With,Result).
Still, I'm not sure if this is the most efficient way to replace substrings in Prolog. Does Prolog have a built-in predicate that could be used for the same purpose?

The short answer is, no, Prolog does not have a built-in string replace predicate. What you show will only replace the substring if that substring is at the end of the original string. That is, it will replace "abc" in the string "xyzabc" but not in the string "xyabcz".
You can use append/2:
replace_substring(String, To_Replace, Replace_With, Result) :-
append([Front, To_Replace, Back], String),
append([Front, Replace_With, Back], Result).
If you want it to succeed without replacing on a non-match, then:
replace_substring(String, To_Replace, Replace_With, Result) :-
( append([Front, To_Replace, Back], String)
-> append([Front, Replace_With, Back], Result)
; Result = String
).
As #false hints in his question, do you want to handle replacing multiple occurrences? If so, the extension to your method would be:
replace_substring(String, To_Replace, Replace_With, Result) :-
( append([Front, To_Replace, Back], String)
-> append([Front, Replace_With, Back], R),
replace_substring(Back, To_Replace, Replace_With, Result)
; Result = String
).

Related

Prolog - first list is sublist of second list while maintaining order?

I want to check if elements of list L1 occur consecutively, and in the same order, in list L2.
For example - check([b,c],[a,b,c,d]) must return true while check([b,d],[a,b,c,d]) must return false
I looked at similar posts Prolog - first list is sublist of second list? and also tried out similar solutions but whenever i try to check if elements are present, i am unable to check if ordering is consecutive
check( [], _ ).
check( [X|XS], [X|XSS] ) :- sublist( XS, XSS ).
check( [X|XS], [_|XSS] ) :- sublist( [X|XS], XSS ).
and if i try to check if ordering is correct then my code is breaking.
check( [], _ ).
check( [X|XS], [X|XSS] ) :- sublist( XS, XSS ).
Interesting problem! I'm surprised at how much code it took, so there may be a better solution than this.
First we need a helper to insist that a list is a prefix of another list. The base case is that we ran out of a prefix list; the inductive case is that the current items match and the remainder of both lists is a prefix match.
prefix([X|Xs], [X|Ys]) :- prefix(Xs, Ys).
prefix([], _).
Now finding a consecutive sublist amounts to searching down a list for prefix matches. If the current items match, then having a prefix is a match:
consecutive_sublist([X|Xs], [X|Ys]) :- prefix(Xs, Ys).
Otherwise, we just discard this element of the search target and try again on the sublist:
consecutive_sublist(Prefix, [_|Ys]) :- consecutive_sublist(Prefix, Ys).
We can make use of append/2 [swi-doc] to write this with a one-liner:
subsequence(X, Y) :-
append([_,X,_], Y).
or we can implement a subsequence/4 that will unify two variables Prefix and Suffix with the list before and after the subsequence:
subsequence(X, Y, Prefix, Suffix) :-
append([Prefix,X,Suffix], Y).
Here we thus have two don't care variables that will collect the prefix and suffix before and after the subsequence.
An alternative solution using the de facto standard definition of the append/3 predicate:
check(SubList, List) :-
append(Prefix, _, List),
append(_, SubList, Prefix).
Sample calls:
| ?- check([b,d],[a,b,c,d]).
no
| ?- check([b,c],[a,b,c,d]).
true ? ;
no
| ?- check([b,c],[a,b,c,d,b,c,f]).
true ? ;
true ? ;
no
We can also use this definition to generate sublist-list pairs:
| ?- check(SubList, List).
SubList = [] ? ;
List = [A|_]
SubList = [A] ? ;
List = [_|_]
SubList = [] ? ;
List = [A,B|_]
SubList = [A,B] ? ;
List = [_,A|_]
SubList = [A] ? ;
List = [_,_|_]
SubList = [] ? ;
List = [A,B,C|_]
SubList = [A,B,C] ? ;
List = [_,A,B|_]
SubList = [A,B] ? ;
...
This problem also gives you the opportunity to learn about termination properties of predicates. As an experiment, exchange the order of the append/3 calls and then check what happens on backtracking for e.g. the two first sample calls.

Replace portions of a list with another in Prolog

I want to replace b,c with x,y,z in a list in Prolog. I have a list [a,b,c,d,e,f] and result will be [a,x,y,z,d,e,f]. How can I write this in Prolog?
replace([],_,[]).
replace([x|T1],Var,[Y|T2]):-
member(X=Y,var),
!
; X=Y
),
replace(T1,Var,T2).
-? replace([a,b,c,d,e,f],[b,c=x,y,z],R).
This is essentially equivalent to the problem of replacing substrings in Prolog. Using the replace_substring/4 predicate, you can replace a subsequence of the list in this way:
:- initialization(main).
:- set_prolog_flag(double_quotes, chars).
main :-
replace_substring([a,b,c,d,e,f],[b,c],[x,y,z],Result),
writeln(Result).
replace_substring(String, To_Replace, Replace_With, Result) :-
append([Front, To_Replace, Back], String),
append([Front, Replace_With, Back], Result).
This program prints [a,x,y,z,d,e,f].

Prolog insert integer after every non integer element

I have the list [r,s,2,t,3,u,v] and I need to make a list that will look like [r,5,s,2,t,3,u,5,v,5]. The rule is: for every non integer that is not followed by an integer, a 5 will be added after that element.
I am new to Prolog and this is my code so far:
insertInL([],[]).
insertInL([F,S|Tail], [F,X|Rest]) :-
integer(S), X = S, insertInL(Tail, Rest).
I know there should be one case where S is not an integer but I don't know hot to treat it.
Edit:
I renewed my code:
insertInL([],[]).
insertInL([F,S|T1], [F,S|T2]) :-
integer(S), insertInL(T1, T2).
insertInL([F,S|T1], [F,1|T2]) :-
\+ integer(S), insertInL([S|T1], T2).
Now it does fine unless I have a non integer as last element.
Edit2:
Now it works properly.
insertInL([],[]).
insertInL([F],[F,1]) :-
\+ integer(F).
insertInL([F,S|T1], [F,S|T2]) :-
integer(S), insertInL(T1, T2),!.
insertInL([F,S|T1], [F,1|T2]) :-
\+ integer(S), insertInL([S|T1], T2).
Here's how you could do it while preserving logical-purity!
Based on if_/3 and integer_t/2 we define:
list_fived([], []).
list_fived([X|Xs], [X|Ys]) :-
if_(integer_t(X),
list_fived(Xs, Ys),
past_nonint(Xs, Ys)).
past_nonint([], [5]).
past_nonint([X|Xs], Ys0) :-
if_(integer_t(X),
(Ys0 = [X|Ys], list_fived(Xs, Ys)),
(Ys0 = [5|Ys], list_fived([X|Xs], Ys))).
Sample query using SICStus Prolog 4.3.2:
| ?- list_fived([r,s,2,t,3,u,v], Xs).
Xs = [r,5,s,2,t,3,u,5,v,5] ? ; % expected result as given by the OP
no

How can I split a list in Prolog to several lists containing 3 items?

My problem is, that I want to make a rule for splitting a list to several lists, containing only 3 items from the original, in order.
For example:
/*original list:*/
Fruits=[apple,banana,orange,pear, lemon, melon]
?-Split(Fruits).
/*results:*/
[apple,banana,orange];
[banana,orange,pear];
[orange,pear,lemon];
[pear,lemon,melon].
Is there any way to do this? :S
Prolog is excellent for this task. Just observe that append/3 can be used
in various directions:
% append(+List,+List,-List)
% append(-List,-List,+List)
append([], X, X).
append([X|Y], Z, [X|T]) :-
append(Y, Z, T).
Now simply define split/2 as follows. It will find _1 and _2 such that L = _1 ++ S ++ _2, where ++ is the list concatenation:
% split(+List,-Sublist)
split(L, S) :-
append(_, H, L),
append(S, _, H).
And here you go with your problem:
?- Fruits=[apple,banana,orange,pear,lemon,melon], Split=[_,_,_], split(Fruits,Split).
Fruits = [apple,banana,orange,pear,lemon,melon],
Split = [apple,banana,orange] ;
Fruits = [apple,banana,orange,pear,lemon,melon],
Split = [banana,orange,pear] ;
Fruits = [apple,banana,orange,pear,lemon,melon],
Split = [orange,pear,lemon] ;
Fruits = [apple,banana,orange,pear,lemon,melon],
Split = [pear,lemon,melon] ;
No
Bye
Best Regards
You can refer to this excellent answer #false provided some times ago.
Quickly adapting his solution, you could write:
seq([]) --> [].
seq([E|Es]) --> [E], seq(Es).
split_3(List, Result) :-
length(Result, 3),
phrase((seq(_),seq(Result),seq(_)),List).
Note that you could achieve the same thing with append/2 (or append/3 with one more call):
split_3(List, Result) :-
length(Result, 3),
append([_, Result, _], List).
But append/2 is not really intended for such manipulations. DCG use difference lists, which are more efficient.

RegEx Parser written in Prolog

I've been banging my head against the wall on this homework problem for a few hours now. We have to parse a regular expression with Prolog. For the most part, the predicates I have work, but there's a few regular expression and string combos which cause them to run out of stack space in SWI-Prolog. Here's a sample with two of the Regex string combinations, one that works and one that doesn't:
star(star(char(a))), []
star(star(char(a))), [a]
The first one works and the second one runs out of stack.
Here's the predicates I'm using:
re_match(epsilon, []).
re_match(char(Letter), [Letter]).
re_match(star(_), []).
re_match(seq(Rx1, Rx2), List) :- append(List1, List2, List), re_match(Rx2, List2), re_match(Rx1, List1).
re_match(alt(Rx1, Rx2), List) :- re_match(Rx1, List); re_match(Rx2, List).
re_match(star(Rx), List) :- append(List1, List2, List), re_match(Rx, List1), re_match(star(Rx), List2).
I'm not sure what change I need to make to get it to work right, but I'm not sure what else to do.
Also, changing List :- append(List1, List2, List) to [H|T] does not evaluate to true for one of the examples.
Consider using DCG notation for better readability and to more easily reason about termination properties:
:- op(100, xf, *).
rexp(eps) --> [].
rexp([T]) --> [T].
rexp(_*) --> [].
rexp(R*) --> rexp(R), rexp(R*).
rexp(s(R1,R2)) --> rexp(R1), rexp(R2).
rexp((R1|R2)) --> ( rexp(R1) ; rexp(R2) ).
Example using length/2 to generate increasingly longer lists to generate strings that are matched by the regexp:
?- length(Ls, _), phrase(rexp(s(([a]|[b]),[c]*)), Ls).
Ls = [a] ;
Ls = [b] ;
Ls = [a, c] ;
Ls = [b, c] ;
Ls = [a, c, c] ;
etc.
I don't have access to SWI Prolog right now, but here is a guess:
Try changing
re_match(star(Rx), List) :- append(List1, List2, List),
re_match(Rx, List1),
re_match(star(Rx), List2).
to
re_match(star(Rx), List) :- append([H|List1], List2, List),
re_match(Rx, [H|List1]),
re_match(star(Rx), List2).
to force re_match to "eat something" when it iterates on the star construct.