Add two more occurrences using prolog
Asked Answered
C

4

5

I have a list [a, b, a, a, a, c, c] and I need to add two more occurrences of each element.

The end result should look like this:

[a, a, a, b, b, b, a, a, a, a, a, c, c, c, c]

If I have an item on the list that is the same as the next item, then it keeps going until there is a new item, when it finds the new item, it adds two occurrences of the previous item then moves on.

This is my code so far, but I can't figure out how to add two...

dbl([], []).
dbl([X], [X,X]).
dbl([H|T], [H,H|T], [H,H|R]) :- dbl(T, R).
Colcothar answered 21/12, 2013 at 20:56 Comment(6)
ok, hm the list should come out as a normal prolog list, that was just the syntax of the question stating that if given a list of, (...) the add two occurrences to make it like (...) using prolog. it doesn't mean to literally make the list in that exact syntax.Colcothar
Your first rule looks good (double an empty list is an empty list). Your second rule doesn't really double. It only copies X once at the end of the list. Your third rule has three parameters, and its semantics are unclear. Is this homework?Purapurblind
It's a mini final project, hang on, I think I figured it outColcothar
Cool. Leave a note what you find. If you get stuck, update (edit) your problem with your latest code and we can explore some clues. :)Purapurblind
i did it, turns out what kept causing all the problems was the second line. the solution is this... dbl([], []). dbl([X,X|T],[X|R]):-dbl([X|T],R). dbl([X|T],[X,X,X|R]):-dbl(T,R).Colcothar
No problem. Although check it. If you do, for example dbl([a,a],L). you get two solutions: the right one (L = [a,a,a,a]) and an incorrect one (L = [a,a,a,a,a,a]).Purapurblind
D
7

Your code looks a bit strange because the last rule takes three parameters. You only call the binary version, so no recursion will ever try to derive it.

You already had a good idea to look at the parts of the list, where elements change. So there are 4 cases:

1) Your list is empty. 2) You have exactly one element. 3) Your list starts with two equal elements. 4) Your list starts with two different elements.

Case 1 is not specified, so you might need to find a sensible choice for that. Case 2 is somehow similar to case 4, since the end of the list can be seen as a change in elements, where you need to append two copies, but then you are done. Case 3 is quite simple, we can just keep the element and recurse on the rest. Case 4 is where you need to insert the two copies again.

This means your code will look something like this:

% Case 1
dbl([],[]).
% Case 2
dbl([X],[X,X,X]).
% Case 3
dbl([X,X|Xs], [X|Ys]) :-
   % [...] recursion skipping the leading X
% Case 4
dbl([X,Y|Xs], [X,X,X|Ys]) :-
   dif(X,Y),
   % [...] we inserted the copies, so recursion on [Y|Xs] and Ys

Case 3 should be easy to finish, we just drop the first X from both lists and recurse on dbl([X|Xs],Ys). Note that we implicitly made the first two elements equal (i.e. we unified them) by writing the same variable twice.

If you look at the head of case 4, you can directly imitate the pattern you described: supposed the list starts with X, then Y and they are different (dif(X,Y)), the X is repeated 3 times instead of just copied and we then continue with the recursion on the rest starting with Y: dbl([Y|Xs],Ys).

So let's try out the predicate:

?- dbl([a,b,a,a,a,c,c],[a,a,a,b,b,b,a,a,a,a,a,c,c,c,c]).
true ;
false.

Our test case is accepted (true) and we don't find more than one solution (false). Let's see if we find a wrong solution:

?- dif(Xs,[a,a,a,b,b,b,a,a,a,a,a,c,c,c,c]), dbl([a,b,a,a,a,c,c],Xs).
false.

No, that's also good. What happens, if we have variables in our list?

?- dbl([a,X,a],Ys).
X = a,
Ys = [a, a, a, a, a] ;
Ys = [a, a, a, X, X, X, a, a, a],
dif(X, a),
dif(X, a) ;
false.

Either X = a, then Ys is single run of 5 as; or X is not equal to a, then we need to append the copies in all three runs. Looks also fine. (*)

Now lets see, what happens if we only specify the solution:

?- dbl(X,[a,a,a,b,b]).
false.

Right, a list with a run of only two bs can not be a result of our specification. So lets try to add one:

?- dbl(X,[a,a,a,b,b,b]).
X = [a, b] ;
false.

Hooray, it worked! So lets as a last test look what happens, if we just call our predicate with two variables:

?- dbl(Xs,Ys).
Xs = Ys, Ys = [] ;
Xs = [_G15],
Ys = [_G15, _G15, _G15] ;
Xs = [_G15, _G15],
Ys = [_G15, _G15, _G15, _G15] ;
Xs = [_G15, _G15, _G15],
Ys = [_G15, _G15, _G15, _G15, _G15] ;
Xs = [_G15, _G15, _G15, _G15],
Ys = [_G15, _G15, _G15, _G15, _G15, _G15] ;
[...]

It seems we get the correct answers, but we see only cases for a single run. This is a result of prolog's search strategy(which i will not explain in here). But if we look at shorter lists before we generate longer ones, we can see all the solutions:

 ?- length(Xs,_), dbl(Xs,Ys).
Xs = Ys, Ys = [] ;
Xs = [_G16],
Ys = [_G16, _G16, _G16] ;
Xs = [_G16, _G16],
Ys = [_G16, _G16, _G16, _G16] ;
Xs = [_G86, _G89],
Ys = [_G86, _G86, _G86, _G89, _G89, _G89],
dif(_G86, _G89) ;
Xs = [_G16, _G16, _G16],
Ys = [_G16, _G16, _G16, _G16, _G16] ;
Xs = [_G188, _G188, _G194],
Ys = [_G188, _G188, _G188, _G188, _G194, _G194, _G194],
dif(_G188, _G194) ;
[...]

So it seems we have a working predicate (**), supposed you filled in the missing goals from the text :)

(*) A remark here: this case only works because we are using dif. The first predicates with equality, one usually encounters are =, == and their respective negations \= and \==. The = stands for unifyability (substituting variables in the arguments s.t. they become equal) and the == stands for syntactic equality (terms being exactly equal). E.g.:

?- f(X) = f(a).
X = a.

?- f(X) \= f(a).
false.

?- f(X) == f(a).
false.

?- f(X) \== f(a).
true.

This means, we can make f(X) equal to f(a), if we substitute X by a. This means if we ask if they can not be made equal (\=), we get the answer false. On the other hand, the two terms are not equal, so == returns false, and its negation \== answers true.

What this also means is that X \== Y is always true, so we can not use \== in our code. In contrast to that, dif waits until it can decide wether its arguments are equal or not. If this is still undecided after finding an answer, the "dif(X,a)" statements are printed.

(**) One last remark here: There is also a solution with the if-then-else construct (test -> goals_if_true; goals_if_false, which merges cases 3 and 4. Since i prefer this solution, you might need to look into the other version yourself.

Dapple answered 22/12, 2013 at 1:19 Comment(0)
S
4

TL;DR: From a declarative point of view, the code sketched by @lambda.xy.x is perfect. Its determinacy can be improved without sacrificing .


Code variant #0: @lambda.xy.x's code

Here's the code we want to improve:

dbl0([], []).
dbl0([X], [X,X,X]).
dbl0([X,X|Xs], [X|Ys]) :-
   dbl0([X|Xs], Ys).
dbl0([X,Y|Xs], [X,X,X|Ys]) :-
   dif(X, Y),
   dbl0([Y|Xs], Ys).

Consider the following query and the answer SWI-Prolog gives us:

?- dbl0([a],Xs).
Xs = [a,a,a] ;
false.

With ; false the SWI indicates a choicepoint was left when proving the goal.

  • For the first answer, Prolog did not search the entire proof tree.
  • Instead, it replied "here's an answer, there may be more".
  • Then, when asked for more solutions, Prolog traversed the remaining branches of the proof tree but finds no more answers.

In other words: Prolog needs to think twice to prove something we knew all along!

So, how can we give determinacy hints to Prolog? By utilizing:

  1. control constructs !/0 and / or (->)/2 (potentially impure)

  2. first argument indexing on the principal functor (never impure)

The code presented in the earlier answer by @CapelliC—which is based on !/0, (->)/2, and the meta-logical predicate (\=)/2—runs well if all arguments are sufficiently instantiated. If not, erratic answers may result—as @lambda.xy.x's comment shows.

Code variant #1: indexing

Indexing can improve determinacy without ever rendering the code non-monotonic. While different Prolog processors have distinct advanced indexing capabilities, the "first-argument principal-functor" indexing variant is widely available.

Principal? This is why executing the goal dbl0([a],Xs) leaves a choicepoint behind: Yes, the goal only matches one clause—dbl0([X],[X,X,X]).—but looking no deeper than the principal functor Prolog assumes that any of the last three clauses could eventually get used. Of course, we know better...

To tell Prolog we utilize principal-functor first-argument indexing:

dbl1([], []).
dbl1([E|Es], Xs) :-
   dbl1_(Es, Xs, E).

dbl1_([], [E,E,E], E).
dbl1_([E|Es], [E|Xs], E) :-
   dbl1_(Es, Xs, E).
dbl1_([E|Es], [E0,E0,E0|Xs], E0) :-
   dif(E0, E),
   dbl1_(Es, Xs, E).

Better? Somewhat, but determinacy could be better still...

Code variant #2: indexing on reified term equality

To make Prolog see that the two recursive clauses of dbl1_/3 are mutually exclusive (in certain cases), we reify the truth value of term equality and then index on that value:

This is where reified term equality (=)/3 comes into play:

dbl2([], []).
dbl2([E|Es], Xs) :-
   dbl2_(Es, Xs, E).

dbl2_([], [E,E,E], E).
dbl2_([E|Es], Xs, E0) :-
   =(E0, E, T),
   t_dbl2_(T, Xs, E0, E, Es).

t_dbl2_(true, [E|Xs], _, E, Es) :-
   dbl2_(Es, Xs, E).
t_dbl2_(false, [E0,E0,E0|Xs], E0, E, Es) :-
   dbl2_(Es, Xs, E).

Sample queries using SWI-Prolog:

?- dbl0([a],Xs).
Xs = [a, a, a] ;
false.

?- dbl1([a],Xs).
Xs = [a, a, a].

?- dbl2([a],Xs).
Xs = [a, a, a].

?- dbl0([a,b,b],Xs).
Xs = [a, a, a, b, b, b, b] ;
false.

?- dbl1([a,b,b],Xs).
Xs = [a, a, a, b, b, b, b] ;
false.

?- dbl2([a,b,b],Xs).
Xs = [a, a, a, b, b, b, b].

To make above code more compact, use control construct if_/3 .

Shower answered 20/5, 2016 at 18:44 Comment(0)
E
4

I was just about to throw this version with if_/3 and (=)/3 in the hat when I saw @repeat already suggested it. So this is essentially the more compact version as outlined by @repeat:

list_dbl([],[]).
list_dbl([X],[X,X,X]).
list_dbl([A,B|Xs],DBL) :-
   if_(A=B,DBL=[A,B|Ys],DBL=[A,A,A,B|Ys]),
   list_dbl([B|Xs],[B|Ys]).

It yields the same results as dbl2/2 by @repeat:

   ?- list_dbl([a],DBL).
DBL = [a,a,a]
   ?- list_dbl([a,b,b],DBL).
DBL = [a,a,a,b,b,b,b]

The example query by the OP works as expected:

   ?- list_dbl([a,b,a,a,a,c,c],DBL).
DBL = [a,a,a,b,b,b,a,a,a,a,a,c,c,c,c]

Plus here are some of the example queries provided by @lambda.xy.x. They yield the same results as @repeat's dbl/2 and @lambda.xy.x's dbl/2:

   ?- dif(Xs,[a,a,a,b,b,b,a,a,a,a,a,c,c,c,c]), list_dbl([a,b,a,a,a,c,c],Xs).
no

   ?- list_dbl(X,[a,a,a,b,b]).
no

   ?- list_dbl(L,[a,a,a,b,b,b]).
L = [a,b] ? ;
no

   ?- list_dbl(L,DBL).
DBL = L = [] ? ;
DBL = [_A,_A,_A],
L = [_A] ? ;
DBL = [_A,_A,_A,_A],
L = [_A,_A] ? ;
DBL = [_A,_A,_A,_A,_A],
L = [_A,_A,_A] ? ;
...

   ?- list_dbl([a,X,a],DBL).
DBL = [a,a,a,a,a],
X = a ? ;
DBL = [a,a,a,X,X,X,a,a,a],
dif(X,a),
dif(a,X)

   ?- length(L,_), list_dbl(L,DBL).
DBL = L = [] ? ;
DBL = [_A,_A,_A],
L = [_A] ? ;
DBL = [_A,_A,_A,_A],
L = [_A,_A] ? ;
DBL = [_A,_A,_A,_B,_B,_B],
L = [_A,_B],
dif(_A,_B) ? ;
DBL = [_A,_A,_A,_A,_A],
L = [_A,_A,_A] ? 
Enfeeble answered 21/5, 2016 at 0:6 Comment(6)
With the SWI toplevel choicepoints left behind are easy to see... with SICStus it's not to easy.Shower
You're on the right track! But the goal dbl([a],_) should better succeed deterministically.Shower
@repeat: I'm under the impression that it does: See the above query ?- list_dbl([a],DBL). It yields the answer DBL = [a,a,a] with no choicepoint left. The same goes for the query ?- list_dbl([a],_). with the difference that the answer is simply yes without a substitution for the second argument. But again no choicepoint left. Or am I misreading your comment?Enfeeble
Honestly, I don't know. Will try your code at home. Either way... No need to jump the gun and change anything. BTW which Prolog processor are you using?Shower
@repeat: YAP 6.2.2 (x86_64-linux): Sun Nov 24 20:27:47 UTC 2013 Pretty old, but as far as I know it's the latest stable. I like stable :-)Enfeeble
WOW! yap indexing does indeed handle these cases! I stand corrected. For more info look at #29823617 ... I have tried ?- call_cleanup(list_dbl([a],_), Det=true), Det==true. with yap, SICStus and SWI. With SICStus and SWI: fail. With yap: success.Shower
E
1
dbl([X,Y|T], [X,X,X|R]) :- X \= Y, !, dbl([Y|T], R).
dbl([H|T], R) :-
        T = []
    ->  R = [H,H,H]
    ;   R = [H|Q], dbl(T, Q).

The first clause handles the basic requirement, adding two elements on sequence change. The second one handles list termination as a sequence change, otherwise, does a plain copy.

Eupepsia answered 22/12, 2013 at 8:44 Comment(2)
This version does not take care of variables in the list: ?- dbl([A,B,C],Y). Y = [A, B, C, C, C]. The last one should only work if A=B and A=C, not for general A,B and C.Dapple
Crafty (+1), but unsound when terms are not instantiated enough. For an indexing-based pure code check out my answer...Shower

© 2022 - 2024 — McMap. All rights reserved.