Khan Saab
Khan Saab

Reputation: 511

Prolog unification and optimization

I want to write merge function which merges two lists , i can do that by writing two (merge) predicates, one where element H1 =< H2 and other where H1 > H2 but however i want to write with if-the-else condition but then unification fails and i don't know how to do it. Here is my code:

merge([H1|T1],[H2|T2],List):-
  (
   H1 =< H2 ->
        NewList = [H1|List],
        merge(T1,[H2|T2],NewList)
  ;
        NewList = [H2|List],
        merge([H1|T1],T2,NewList)
  ).

merge([],L,L).
merge(L,[],L).

I know what is the problem and if i write two merge-predicates i can avoid it but i don't know how to do it here , i'm stucked :(

Pattern matching fails here : image

Upvotes: 2

Views: 216

Answers (3)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476547

You think of recursion the wrong way. The third parameter is the outcome of the merge.

Here you use:

merge([H1|T1],[H2|T2],List):-
  (
   H1 =< H2 ->
        NewList = [H1|List],
        merge(T1,[H2|T2],NewList)
  ;
        NewList = [H2|List],
        merge([H1|T1],T2,NewList)
  ).

merge([],L,L).
merge(L,[],L).

In other words you are not prepending the output of the recursive call, you are in fact expecting the recursive output to start with H1 and/or H2 and then pop that element from the result and pass the remainder (the tail of the list) as outcome.

So a quick fix is to reverse that:

merge([H1|T1],[H2|T2],List):-
  (
   H1 =< H2 ->
        List = [H1|NewList],
        merge(T1,[H2|T2],NewList)
  ;
        List = [H2|NewList],
        merge([H1|T1],T2,NewList)
  ).

merge([],L,L).
merge(L,[],L).

Now we notice however that there is some duplicate code, so we can rewrite it to:

merge([H1|T1],[H2|T2],[Min|NewList]):-
  (
   H1 =< H2 ->
        Min = H1,
        merge(T1,[H2|T2],NewList)
  ;
        Min = H2,
        merge([H1|T1],T2,NewList)
  ).

merge([],L,L).
merge(L,[],L).

But personally, I find a two-clause solution more elegant:

merge([H1|T1],[H2|T2],[H1|T3]) :-
   H1 =< H2,
   merge(T1,[H2|T2],T3).

merge([H1|T1],[H2|T2],[H2|T3]) :-
   H1 > H2,
   merge([H1|T1],T2,T3).

merge([],L,L).
merge(L,[],L).

Upvotes: 5

Daniel Lyons
Daniel Lyons

Reputation: 22803

You're actually pretty close. Look at this trace:

?- trace, merge([1,3,5,6], [2,4,7,8], X).
   Call: (9) merge([1, 3, 5, 6], [2, 4, 7, 8], _6684) ? creep
   Call: (10) 1=<2 ? creep
   Exit: (10) 1=<2 ? creep
   Call: (10) _7050=[1|_6684] ? creep
   Exit: (10) [1|_6684]=[1|_6684] ? creep
   Call: (10) merge([3, 5, 6], [2, 4, 7, 8], [1|_6684]) ? creep
   Call: (11) 3=<2 ? creep
   Fail: (11) 3=<2 ? creep
   Redo: (10) merge([3, 5, 6], [2, 4, 7, 8], [1|_6684]) ? creep
   Call: (11) _7062=[2, 1|_6684] ? creep
   Exit: (11) [2, 1|_6684]=[2, 1|_6684] ? creep
   Call: (11) merge([3, 5, 6], [4, 7, 8], [2, 1|_6684]) ? creep
   Call: (12) 3=<4 ? creep
   Exit: (12) 3=<4 ? creep
   Call: (12) _7074=[3, 2, 1|_6684] ? creep
   Exit: (12) [3, 2, 1|_6684]=[3, 2, 1|_6684] ? creep
   Call: (12) merge([5, 6], [4, 7, 8], [3, 2, 1|_6684]) ? creep
   Call: (13) 5=<4 ? creep
   Fail: (13) 5=<4 ? creep
   Redo: (12) merge([5, 6], [4, 7, 8], [3, 2, 1|_6684]) ? creep
   Call: (13) _7086=[4, 3, 2, 1|_6684] ? creep
   Exit: (13) [4, 3, 2, 1|_6684]=[4, 3, 2, 1|_6684] ? creep
   Call: (13) merge([5, 6], [7, 8], [4, 3, 2, 1|_6684]) ? creep
   Call: (14) 5=<7 ? creep
   Exit: (14) 5=<7 ? creep
   Call: (14) _7098=[5, 4, 3, 2, 1|_6684] ? creep
   Exit: (14) [5, 4, 3, 2, 1|_6684]=[5, 4, 3, 2, 1|_6684] ? creep
   Call: (14) merge([6], [7, 8], [5, 4, 3, 2, 1|_6684]) ? creep
   Call: (15) 6=<7 ? creep
   Exit: (15) 6=<7 ? creep
   Call: (15) _7110=[6, 5, 4, 3, 2, 1|_6684] ? creep
   Exit: (15) [6, 5, 4, 3, 2, 1|_6684]=[6, 5, 4, 3, 2, 1|_6684] ? creep

At this moment in the depth of the recursion, you almost have the solution. Then it starts to go awry:

   Call: (15) merge([], [7, 8], [6, 5, 4, 3, 2, 1|_6684]) ? creep
   Fail: (15) merge([], [7, 8], [6, 5, 4, 3, 2, 1|_6684]) ? creep
   Redo: (14) merge([6], [7, 8], [5, 4, 3, 2, 1|_6684]) ? creep
   Fail: (14) merge([6], [7, 8], [5, 4, 3, 2, 1|_6684]) ? creep
   Redo: (13) merge([5, 6], [7, 8], [4, 3, 2, 1|_6684]) ? creep
   Fail: (13) merge([5, 6], [7, 8], [4, 3, 2, 1|_6684]) ? creep
   Redo: (12) merge([5, 6], [4, 7, 8], [3, 2, 1|_6684]) ? creep
   Fail: (12) merge([5, 6], [4, 7, 8], [3, 2, 1|_6684]) ? creep
   Redo: (11) merge([3, 5, 6], [4, 7, 8], [2, 1|_6684]) ? creep
   Fail: (11) merge([3, 5, 6], [4, 7, 8], [2, 1|_6684]) ? creep
   Redo: (10) merge([3, 5, 6], [2, 4, 7, 8], [1|_6684]) ? creep
   Fail: (10) merge([3, 5, 6], [2, 4, 7, 8], [1|_6684]) ? creep
   Redo: (9) merge([1, 3, 5, 6], [2, 4, 7, 8], _6684) ? creep
   Fail: (9) merge([1, 3, 5, 6], [2, 4, 7, 8], _6684) ? creep

So what's wrong with your code? Two things mainly: you're building the result reversed (not uncommon) and you don't have a good way to pass it back. Arranging for a result variable is certainly one way to handle these problems, similarly to what Luai figured out:

merge(X,Y,Z) :- merge(X, Y, [], Z).

merge([H1|T1],[H2|T2],List,NewList):-
  (
   H1 =< H2 ->
        merge(T1,[H2|T2],[H1|List],NewList)
  ;
        merge([H1|T1],T2,[H2|List],NewList)
  ).

merge([],R,LRev,Res) :- reverse(LRev, L), append(L,R,Res).
merge(R,[],LRev,Res) :- reverse(LRev, L), append(L,R,Res).

Or you will have to figure out another way to do it, such as by passing down the tail of the list, like this:

merge2([H1|T1], [H2|T2], MT) :-
    (H1 =< H2 ->
        MT=[H1|MT2],
        merge2(T1, [H2|T2], MT2)
    ;
        MT=[H2|MT2],
        merge2([H1|T1], T2, MT2)
    ).
merge2([], T2, T2).
merge2(T2, [], T2).

I would favor this approach, personally.

Upvotes: 5

Luai Ghunim
Luai Ghunim

Reputation: 976

There should be a better way but this is also one option.

merge(List1,List2,Result):-
  merge(List1,List2,[],Result).

merge([],L,X,Result):-
   append(X,L,Result).
merge(L,[],X,Result):-
    append(X,L,Result).

merge([H1|T1],[H2|T2],Acc,Result):-
      (
       H1 =< H2 ->
            apend(Acc,H1,NewList),
            merge(T1,[H2|T2],NewList,Result)
      ;
           apend(Acc,H2,NewList),
           merge([H1|T1],T2,NewList,Result)
      ).

%i think prolog implementation of append/3 does not work with empty list so i implemented here.
apend([],X,[X]).

apend([H|T],X,[H|T2]):-
  apend(T,X,T2).

Upvotes: 1

Related Questions