4

How would you combine two lists? This is what I tried but it doesn't give me the result I want,

Y = [1,2,3].
Z = [3,4,5].
X = [Y,Z].

This just gives a bigger list with a divided Head and Tail.

I want my output to look like this:

X = [1,2,3,4,5].
false
  • 10,264
  • 13
  • 101
  • 209
Jeff
  • 355
  • 2
  • 9
  • Just to be clear, the `3` appears in both lists, but you only want it once in the output? – Enigmativity Mar 03 '21 at 00:49
  • @Enigmativity Yes that's right. I was thinking of appending like the answer suggested below and making a predicate to delete one of the duplicates. – Jeff Mar 03 '21 at 01:02
  • You seem to have accepted an answer that keeps the duplicates. Can you confirm the desired behaviour? – Enigmativity Mar 03 '21 at 04:00
  • @Enigmativity the OP already confirmed it, both in the question and in the comments. their accept is contrary to the question's parameters, but that's an orthogonal (non-)issue. :) – Will Ness Mar 03 '21 at 08:25
  • also try `Y = [1,2,3], Z = [3,4,5], X = [Y,Z], append(X,Result).` – Will Ness Mar 03 '21 at 08:28
  • 1
    The bountytext should read: For a pure version of @gusbro's initial definition in [/a/66450073](https://stackoverflow.com/a/66450073/772868). Ideally enumerates also all answers for `length(Zs,_), combine(Xs, Ys, Zs).` Fails for `combine([a|_],_,[b|_]).` &ct – false Mar 05 '21 at 07:54
  • 1
    @false would you consider `(@<)/2` pure for the purposes of your bounty? – Isabelle Newbie Mar 05 '21 at 10:28
  • 1
    @IsabelleNewbie: `a @< X` fails. Yet, `X = b, a @< X` succeeds. You would need to hide it behind some clean interface – false Mar 05 '21 at 10:46

7 Answers7

4

If you want to combine two ground lists with a possible overlap into a third one keeping in the result only one copy of the overlap elements (i.e. the suffix elements of the first list which also form a prefix of the second), you can write it this way:

combine(A, B, C):-
  append(A1, Common, A),
  append(Common, B1, B),
  !,  % The cut here is to keep the longest common sub-list
  append([A1, Common, B1], C).

sample runs:

?- combine([1,2,3],[3,4,5], C).
C = [1, 2, 3, 4, 5].

?- combine([1,2,3,4],[3,4,5], C).
C = [1, 2, 3, 4, 5].

A slight modification to avoid using the cut:

combine(A, B, C):-
  append(A1, Common, A),
  append(Common, B1, B),
  bagof(NotCommonA-NotCommonB,
        not_common(A1, B1, NotCommonA, NotCommonB),
        LDifs),
  maplist(difpair, LDifs),
  append([A1, Common, B1], C).
  
not_common(L, R, [ItemA|NotCommonA], NotCommonB):-
  append(_, [ItemA|NotCommonA], L),
  length([ItemA|NotCommonA], LNotCommon),
  length(NotCommonB, LNotCommon),
  append(NotCommonB, _, R).

difpair(L-R):-
  dif(L, R).

Sample runs:

?- combine([1,2,3],[3,4,5], C).
C = [1, 2, 3, 4, 5] ;
false.

?- combine([1,2,3,X],[3,4,5], C).
X = 4,
C = [1, 2, 3, 4, 5] ;
X = 3,
C = [1, 2, 3, 3, 4, 5] ;
C = [1, 2, 3, X, 3, 4, 5],
dif(X, 3),
dif(X, 4) ;
;
false
gusbro
  • 22,357
  • 35
  • 46
  • I've edited and reverted, to demonstrate something. what's your opinion please, could such coding style be helpful for newbies, while learning? – Will Ness Mar 03 '21 at 08:37
  • 1
    @Will: What about a clean way to remove the cut instead of tabbing? – false Mar 03 '21 at 10:46
  • @false: how about the approach in the edited answer ? I wanted to use` foreach/2` but at least with my SWI it created copies of unbound variables, so i ended up with bagof+maplist. – gusbro Mar 03 '21 at 17:00
  • @false hmm, interesting challenge. I don't see a clean (positional / structural) way to do that atm. :) – Will Ness Mar 03 '21 at 17:42
  • 2
    Clearly bountyable! – false Mar 03 '21 at 20:48
3

The question is completely unclear: Is it about merging sorted lists? Sorted lists of numbers, maybe? Is it about a kind of append that only keeps one copy of a shared suffix of the first list/prefix of the second list? Is it about dropping duplicates in some more general sense?

Here is a solution that drops the shared suffix/prefix. It is similar to slago's solution, except that it uses two predicates for the two different states that the computation can be in: merge "copies" elements from the first argument to the third argument; at some point it switches to mergerest, which "keeps copying" but requires that its first argument be a non-empty prefix of the second argument.

merge(Xs, Ys, Zs) :-
    mergerest(Xs, Ys, Zs).
merge([X|Xs], [Y|Ys], [X|Zs]) :-
    merge(Xs, [Y|Ys], Zs).

mergerest([X], [X|Ys], [X|Ys]).
mergerest([X|Xs], [X|Ys], [X|Zs]) :-
    mergerest(Xs, Ys, Zs).

Using false's animal definitions:

?- animal(A), animal(B), dif(A, Mutation), merge(A, B, Mutation).
A = [a,l,l,i,g,a,t,o,r],
B = [t,o,r,t,u,e],
Mutation = [a,l,l,i,g,a,t,o,r,t,u,e] ;
A = [c,a,r,i,b,o,u],
B = [o,u,r,s],
Mutation = [c,a,r,i,b,o,u,r,s] ;
A = [c,h,e,v,a,l],
B = [a,l,l,i,g,a,t,o,r],
Mutation = [c,h,e,v,a,l,l,i,g,a,t,o,r] ;
A = [c,h,e,v,a,l],
B = [l,a,p,i,n],
Mutation = [c,h,e,v,a,l,a,p,i,n] ;
A = [v,a,c,h,e],
B = [c,h,e,v,a,l],
Mutation = [v,a,c,h,e,v,a,l] ;
false.

Behavior on only the third argument being bound, for example:

?- merge(Xs, Ys, [1, 2, 3]).
Xs = [1],
Ys = [1, 2, 3] ;
Xs = [1, 2],
Ys = [1, 2, 3] ;
Xs = Ys, Ys = [1, 2, 3] ;
Xs = [1, 2],
Ys = [2, 3] ;
Xs = [1, 2, 3],
Ys = [2, 3] ;
Xs = [1, 2, 3],
Ys = [3] ;
false.

More generally:

?- length(Zs,_), merge(Xs, Ys, Zs).
Zs = Xs, Xs = Ys, Ys = [_4262] ;
Zs = Ys, Ys = [_4262, _4268],
Xs = [_4262] ;
Zs = Xs, Xs = Ys, Ys = [_4262, _4268] ;
Zs = Xs, Xs = [_4262, _4268],
Ys = [_4268] ;
Zs = Ys, Ys = [_4262, _4268, _4274],
Xs = [_4262] ;
Zs = Ys, Ys = [_4262, _4268, _4274],
Xs = [_4262, _4268] ;
Zs = Xs, Xs = Ys, Ys = [_4262, _4268, _4274] ;
Zs = [_4262, _4268, _4274],
Xs = [_4262, _4268],
Ys = [_4268, _4274] ;
Zs = Xs, Xs = [_4262, _4268, _4274],
Ys = [_4268, _4274] ;
Zs = Xs, Xs = [_4262, _4268, _4274],
Ys = [_4274] ;
Zs = Ys, Ys = [_4262, _4268, _4274, _4280],
Xs = [_4262] ;
Zs = Ys, Ys = [_4262, _4268, _4274, _4280],
Xs = [_4262, _4268] .  % ad nauseam

Fast failure:

?- merge([a|_],_,[b|_]).
false.
Isabelle Newbie
  • 9,258
  • 1
  • 20
  • 32
2

Here is a test program (since Meloni, 1976), simplified to avoid conversion and trivial solutions. See the definition of iwhen/2 and printing chars as double quoted strings in answer substitutions.

:- set_prolog_flag(double_quotes, chars).

animal("alligator").
animal("tortue").
animal("caribou").
animal("ours").
animal("cheval").
animal("vache").
animal("lapin").
% animal("iguana").
% animal("anaconda").

mutation(C) :-
   animal(A),
   animal(B),
   dif(A, C),
   combine(A, B, C),
   iwhen(ground(A+B+C), \+ append(A, B, C) ).

?- mutation(C).
   C = "alligatortue"
;  C = "caribours"
;  C = "chevalligator"
;  C = "chevalapin"
;  C = "vacheval"
;  false.
false
  • 10,264
  • 13
  • 101
  • 209
2

Here's another take on this problem:

combine([A|As], [B|Bs], [A|Cs]):-
  dif(A, B),
  combine(As, [B|Bs], Cs).
combine(As, Bs, Cs):-
  suffix_prefix(As, Bs, Cs).
combine([A|As], [A|Bs], [A|Cs]):-
  not_suffix_prefix(As, Bs, Cs),
  combine(As, [A|Bs], Cs).
  
suffix_prefix([], Bs, Bs).
suffix_prefix([A|As], [A|Bs], [A|Cs]):-
  suffix_prefix(As, Bs, Cs).

not_suffix_prefix([_|_], [], [_|_]).
not_suffix_prefix([A|As], [A|Bs], [_|Cs]):-
  not_suffix_prefix(As, Bs, Cs).
not_suffix_prefix([A|_], [B|_], _):-
  dif(A, B).

Sample runs (from the tests answer), failure for combine([a|_], _, [b|_]) , generate all solutions and some other tests:

?- mutation(C).
C = [a, l, l, i, g, a, t, o, r, t, u, e] ;
C = [c, a, r, i, b, o, u, r, s] ;
C = [c, h, e, v, a, l, l, i, g, a, t, o, r] ;
C = [c, h, e, v, a, l, a, p, i, n] ;
C = [v, a, c, h, e, v, a, l] ;
false.

?- combine([a|_], _, [b|_]).
false.

?- combine([1,2,3], [3,4,5], Cs).
Cs = [1, 2, 3, 4, 5] ;
false.

?- combine([1,2,3,X], [3,4,5], Cs).
X = 4,
Cs = [1, 2, 3, 4, 5] ;
Cs = [1, 2, 3, '$VAR'('X'), 3, 4, 5],
dif('$VAR'('X'), 3),
dif('$VAR'('X'), 4) ;
;
X = 3,
Cs = [1, 2, 3, 3, 4, 5] ;
false.

?- combine([A,A], [A], C).
C = ['$VAR'('A'), '$VAR'('A')] ;
false.

?- length(Zs, Len), combine(Xs, Ys, Zs).
Zs = Xs, Xs = Ys, Ys = [],
Len = 0 ;
Zs = Ys, Ys = [_2419916],
Len = 1,
Xs = [] ;
Zs = Xs, Xs = Ys, Ys = [_2419916],
Len = 1 ;
Zs = [_2420492, _2420498],
Len = 2,
Xs = [_2420492],
Ys = [_2420498],
dif(_2420492, _2420498) ;
;
Zs = Xs, Xs = [_2420498, _2420504],
Len = 2,
Ys = [_2420504],
dif(_2420498, _2420504) ;
;
Zs = Ys, Ys = [_2419916, _2419922],
Len = 2,
Xs = [] ;
Zs = Ys, Ys = [_2419916, _2419922],
Len = 2,
Xs = [_2419916] ;
Zs = Xs, Xs = Ys, Ys = [_2419916, _2419922],
Len = 2 ;
Zs = Xs, Xs = [_2419916, _2419916],
Len = 2,
Ys = [_2419916] ;
Zs = [_2420690, _2420696, _2420702],
Len = 3,
Xs = [_2420690, _2420696],
Ys = [_2420702],
dif(_2420690, _2420702),
dif(_2420696, _2420702) ;

.... continues ...
gusbro
  • 22,357
  • 35
  • 46
  • and what about the `combine([1,2,3,X],[3,4,5], C).` as in your first answer, can we see its results here, too, for comparison? – Will Ness Mar 07 '21 at 08:41
  • @WillNess: Running that example I saw a problem with the base case of `combine/3` (was missing some solutions). Fixed that and rerun all the samples including my sample run from the first answer. – gusbro Mar 07 '21 at 17:39
  • As noted in my answer, I think this call should succeed: `combine2([A,A],[A],C)` but does not according to your predicate (unless I misunderstood the definition). – jnmonette Mar 07 '21 at 19:38
  • @jnmonette: Now it is working right. I needed to add a clause to not_suffix_prefix/3 for when the "suffix" started with the "prefix" but was longer – gusbro Mar 07 '21 at 22:39
  • Where does this `'$VAR'('X')` come from? – false Mar 08 '21 at 07:11
  • @false: If you mean the result from `combine([1,2,3,X],[3,4,5], Cs)`, I believe its from using `dif/2` with SWI. I don't recall seeing answers like that but they are appearing with my copy of SWI 8.0.2 when I use `dif/2`, and I suppose `'$VAR'('X')` meant to be just `X`. – gusbro Mar 08 '21 at 13:04
  • The latest addition created another very minor issue: Two of the solutions to `length(Zs, Len), combine(Xs, Ys, Zs).` are `combine([A,A],[A],[A,A])` and `combine([A,B],[B],[A,B]), dif(A,B)`. In my approach, I managed to get rid of this redundancy and only have a single `combine([A,B],[B],[A,B])` (without `dif` condition), which required the introduction of `longer/2` and `not_longer/2`. – jnmonette Mar 08 '21 at 15:13
  • @jnmonette honestly I don't see the problem in giving two separate answers in that case, the set of possible values for `A` and `B` that make the query succeed should be the same. Or maybe I misunderstood you but I don't see this as a redundancy. – gusbro Mar 08 '21 at 18:12
  • Yes, that's why I said it was very minor: I find it better to have less (and more general) answers, but I consider your version correct as well. – jnmonette Mar 08 '21 at 18:18
2

Here is another solution, relatively similar to gusbro's but fixing some missing cases (e.g. combine([A,A],[A],C) should succeed with C=[A,A]).

% Third is the concatenation of First and Second but without the longest suffix
combine(As,Bs,Bs):-         %     of First that is also a prefix of Second
    prefix(As,Bs).
combine([A|As],Bs,[A|Cs]):-
    prefix(As, Cs),         % Necessary when As is var to avoid
    not_prefix([A|As], Bs), %          generating too long lists
    combine(As,Bs,Cs).

% First is prefix of Second
prefix([],_).
prefix([A|As],[A|Bs]):-
    prefix(As,Bs).

% First is not prefix of Second
not_prefix(As,Bs):-
    longer(As,Bs).
not_prefix(As,Bs):-
    not_longer(As,Bs),
    not_prefix2(As,Bs).

% First is not prefix of Second, knowing it is not longer
not_prefix2([A|As],[A|Bs]):-
    not_prefix2(As,Bs).
not_prefix2([A|_],[B|_]):-
    dif(A,B).

% First is longer than Second
longer([_|_], []).
longer([_|As],[_|Bs]):-
    longer(As,Bs).

% First is not longer than Second
not_longer([], _).
not_longer([_|As],[_|Bs]):-
    not_longer(As,Bs).

Some tests:

?- combine([1,2,3], [3,4,5], Cs).
Cs = [1,2,3,4,5] ? ;
no

?- combine([1,2,3,X], [3,4,5], Cs).
X = 4,
Cs = [1,2,3,4,5] ? ;
X = 3,
Cs = [1,2,3,3,4,5] ? ;
Cs = [1,2,3,X,3,4,5],
prolog:dif(X,4),
prolog:dif(X,3) ? ;
no

?- combine([a|_], _, [b|_]).
no

?- length(Cs, Len), combine(As, Bs, Cs).
Cs = [],
Len = 0,
As = [],
Bs = [] ? ;
Cs = [_A],
Len = 1,
As = [],
Bs = [_A] ? ;
Cs = [_A],
Len = 1,
As = [_A],
Bs = [_A] ? ;
Cs = [_A],
Len = 1,
As = [_A],
Bs = [] ? ;
Cs = [_A,_B],
Len = 2,
As = [],
Bs = [_A,_B] ? ;
Cs = [_A,_B],
Len = 2,
As = [_A],
Bs = [_A,_B] ? ;
Cs = [_A,_B],
Len = 2,
As = [_A,_B],
Bs = [_A,_B] ? ;
Cs = [_A,_B],
Len = 2,
As = [_A],
Bs = [_B],
prolog:dif(_A,_B) ? ;
Cs = [_A,_B],
Len = 2,
As = [_A,_B],
Bs = [] ? ;
Cs = [_A,_B],
Len = 2,
As = [_A,_B],
Bs = [_B] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [],
Bs = [_A,_B,_C] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A],
Bs = [_A,_B,_C] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B],
Bs = [_A,_B,_C] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B,_C],
Bs = [_A,_B,_C] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A],
Bs = [_B,_C],
prolog:dif(_A,_B) ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B],
Bs = [_C],
prolog:dif(_B,_C) ? ;
Cs = [_A,_A,_B],
Len = 3,
As = [_A,_A],
Bs = [_A,_B],
prolog:dif(_A,_B) ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B],
Bs = [_B,_C],
prolog:dif(_A,_B) ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B,_C],
Bs = [] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B,_C],
Bs = [_C] ? ;
Cs = [_A,_B,_C],
Len = 3,
As = [_A,_B,_C],
Bs = [_B,_C] ? ;
Cs = [_A,_B,_C,_D],
Len = 4,
As = [],
Bs = [_A,_B,_C,_D] ? 
yes
Will Ness
  • 70,110
  • 9
  • 98
  • 181
jnmonette
  • 1,794
  • 4
  • 7
1

That's not really how prolog works (what you're doing is not assignment as you may think).

But in any case, here's how you append two lists:

?- append([1,2,3],[3,4,5],X).
returns: X = [1, 2, 3, 3, 4, 5].
Tasos Papastylianou
  • 21,371
  • 2
  • 28
  • 57
  • as gusbro points out below, you may also be interested in the _union_ of two lists: `union( [1,2,3], [3,4,5], X).` returns `X = [1,2,3,4,5]`. – Tasos Papastylianou Mar 03 '21 at 08:47
  • but `union( [1,2,3], [5,3,4,5], X)` ==> `X = [1, 2, 5, 3, 4, 5]` instead of `X = [1, 2, 3, 5, 3, 4, 5]`. – Will Ness Mar 03 '21 at 17:44
  • 1
    @WillNess indeed, but this is only relevant at this point because the question has been re-interpreted at increasing levels of complexity about 3 times by now. On a question that effectively demonstrates the asker is still trying to understand the effective difference between unification and assignment :p – Tasos Papastylianou Mar 04 '21 at 08:00
1

A possible solution is:

combine([], B, B).
combine(A, [], A).
combine([X|A], [X|B], [X|C]) :- combine(A, B, C).
combine([X|A], [Y|B], [X|C]) :- dif(X,Y), combine(A, [Y|B], C).

Some queries:

?- combine([1,2,3,4,5], [3,4,5,6,7], C).
C = [1, 2, 3, 4, 5, 6, 7] ;
false.

?- combine([1,2,3,4,5], [4,5,6,7], C).
C = [1, 2, 3, 4, 5, 6, 7] ;
false.

?- combine([1,2,3,4,5], [5,6,7], C).
C = [1, 2, 3, 4, 5, 6, 7] ;
false.

?- combine([1,2,3,4,5], [6,7], C).
C = [1, 2, 3, 4, 5, 6, 7] ;
false.

?- combine([1,2,3,4,5], [], C).
C = [1, 2, 3, 4, 5] ;
false.

?- combine([], [3,4,5,6,7], C).
C = [3, 4, 5, 6, 7] ;
false.
slago
  • 5,025
  • 2
  • 10
  • 23
  • Succeeds incorrectly for `combine("vacheval","vel","vacheval").` – false Mar 06 '21 at 15:36
  • In one of the comments in the question, the asker said: "I was thinking of appending (...) and making a predicate to delete one of the duplicates". Thus, I think the answer is correct for the question posed by the asker. – slago Mar 06 '21 at 18:37
  • The meaning - at least for the bounty - is given by gusbro's 1st definition. And, "delete one of the duplicates" is clearly not a precise requirement either and I wonder what is actually deleted in the case above. – false Mar 06 '21 at 19:27
  • @false I did not intend to receive the bounty when answering the question. And as for what was removed, it's easy to see that ```[v, a, c, h, e, v, a, l]``` contains ```[v, e, l]```, then the items deleted were ```v```, ```e``` and ```l```. – slago Mar 06 '21 at 21:48