Hana
Hana

Reputation: 101

Prolog: replacing an element in a list with another list

For the following query (and the predicates defined in the following) I get an unexpected answer:

?- rep([1,2,3], 3, [2,3,4], L).
L = [1, 2, 2, 3, 4] ;
L = [1, 2, 3].                           % unexpected answer

The first result is the one I want. The second one I do not want...

How can I prevent the second one? Probably by adding ! somewhere?

concat([], L, L).
concat([H|T], L, [H|Res]) :-
   concat(T, L, Res).

repl([], _, _, []).
repl([Val|T], Val, Repl, Res) :-
   repl(T, Val, Repl, Temp),
   concat(Repl, Temp, Res).
repl([H|T], Val, Repl, [H|Res]) :-
   repl(T, Val, Repl, Res).

Upvotes: 1

Views: 1879

Answers (3)

CapelliC
CapelliC

Reputation: 60014

edit

this is getting hairy, and my answer didn't accounted precisely for request... so let's see your code with minimal change:

concat([], L, L).
concat([H|T], L, [H|Res]) :-
   concat(T, L, Res).

repl([], _, _, []).
repl([Val|T], Val, Repl, Res) :- !, % as noted by @repeat, better to commit early...
   repl(T, Val, Repl, Temp),
   concat(Repl, Temp, Res). % !.
repl([H|T], Val, Repl, [H|Res]) :-
   repl(T, Val, Repl, Res).

the cut simply commits the second clause...

resume old answer

your concat/3 is the same as the well known append/3, so consider this approach

repl(ListOrig, Element, Replace, ListUpdated) :-
      append(H, [Element|T], ListOrig),
      append(H, Replace, Temp),
      append(Temp, T, ListUpdated).

?- repl([1, 2, 3], 3, [2, 3, 4], L).
L = [1, 2, 2, 3, 4] ;
false.

edit

as requested by comments, this extension handles a list of Element to match for change, with simple pattern matching (note: add before the previous clause)

repl(ListOrig, [], _Replace, ListOrig).
repl(ListOrig, [E|Es], Replace, ListUpdated) :-
    repl(ListOrig, E, Replace, Temp),
    repl(Temp, Es, Replace, ListUpdated).

test

?- repl([1,2,3],[2,3],[x,y,z],R).
R = [1, x, y, z, x, y, z] ;
false.

edit

I didn't noticed that if Element is not found it should not fail... a last 'catchall' clause could handle this case:

repl(ListOrig, _Element, _Replace, ListOrig).

or better, extend the original like

repl(ListOrig, Element, Replace, ListUpdated) :-
      (  append(H, [Element|T], ListOrig)
      -> append(H, Replace, Temp),
         append(Temp, T, ListUpdated)
      ;  ListOrig = ListUpdated
      ).

Upvotes: 0

mat
mat

Reputation: 40768

As @repeat has already nicely shown, you should use the constraint dif/2 to describe that two terms are different. This avoids the unexpected and wrong second solution.

In addition, as always when describing lists, also consider using notation: You can use the nonterminal list//1 do declaratively describe a list in such a way that it can be easily and efficiently spliced into other lists at specific positions.

Consider:

replacement([], _, _) --> [].
replacement([L|Ls], L, Rs) -->
    list(Rs),
    replacement(Ls, L, Rs).
replacement([L|Ls], R, Rs) --> [L],
    { dif(L, R) },
    replacement(Ls, R, Rs).

list([]) --> [].
list([L|Ls]) --> [L], list(Ls).

We use the interface predicate phrase/2 to use the DCG. For example:

?- phrase(replacement([1,2,3], 3, [2,3,4]), Ls).
Ls = [1, 2, 2, 3, 4] ;
false.

It is a true relation that works in all directions. It can answer quite general questions, such as: Which item has been replaced by another list? Example:

?- phrase(replacement([1,2,3], E, [2,3,4]), [1,2,2,3,4]).
E = 3 ;
false.

Upvotes: 2

repeat
repeat

Reputation: 18726

To allow for multiple matches per list, use maplist/3 and proceed like this:

item_replacement_item_mapped(E, Es, E, Es).
item_replacement_item_mapped(X,  _, E, [E]) :-
   dif(X, E).

repl(Es0,X,Xs,Es) :-
   maplist(item_replacement_item_mapped(X,Xs), Es0, Ess1),
   append(Ess1, Es).

Sample queries:

?- repl([1,2,3], 3, [2,3,4], L).
   L = [1,2,2,3,4]
;  false.

?- repl([x,y,x,y,x], x, [x,y,x], L).
   L = [x,y,x,y,x,y,x,y,x,y,x]
;  false.

Upvotes: 2

Related Questions