2

I'm writing a program that checks if a list contains sub-lists of unique length:

e.g. diffLengthLists([[2],[3,4]]) should return true whereas diffLengthLists([[3],[4]]) should return false.

Here's my code:

diffLengthLists(A):- is_list(A),
                     diffLength(A,[_]).

diffLength([A|B], [C|D]):- length(A, C), 
                           diffLength(B, D), 
                           unique([C|D]).

unique([A|B]):- \+member(A, B), unique(B).
unique([]).

So I'm basically adding the length of each sub-list to another list, [C|D], and then check if the elements in [C|D] are unique.

However, my program doesn't work the way expected. What am I doing wrong here? Is there a better (clearer) way to write this program?

Thanks for your help in advance!

EDIT: I tested the helper predicates and the issue seems to be arising from diffLength however, I don't understand why it isn't working. I added unique([]). to the code and now that predicate works properly.

false
  • 10,264
  • 13
  • 101
  • 209
Radix
  • 1,317
  • 2
  • 17
  • 32
  • 1
    Did you try evaluating your helper predicates (`diffLength`, `unique`) to see if their implementations match your expectations? – reuben Feb 20 '12 at 00:01
  • I totally forgot to do it, thanks for mentioning it. I updated the question. – Radix Feb 20 '12 at 00:17
  • 1
    It sounds like you found a missing base case. A good idea is to always make sure the degenerate cases work with all of your predicate definitions. Once you're happy there, you might consider mentally walking through a few sample cases to understand why they're not coming out as expected? (Also, you never mentioned why you thought they weren't working. Did you identify values for which the predicates were not coming up with the correct answer?) – reuben Feb 20 '12 at 00:26
  • thanks for your help. I figured out what the problem was :) I'll post the answer in a few hours. (basically I only needed to add another base case for diffLength, plus a couple other minor edits) – Radix Feb 20 '12 at 01:08

2 Answers2

3
diffLengthLists(Xss) :-
   maplist(length,Xss,Ls),
   alldifferent(Ls).

alldifferent([]).
alldifferent([E|Es]) :-
   maplist(=\=(E),Es),
   alldifferent(Es).

This solution terminates, if all lengths of all lists are known. That is what your homework was about.

But it does not terminate for diffLengthLists([[],[],_]) when it should fail. It is very difficult to accomplish an implementation that would terminate in all possible cases.

In SWI, the following is predefined, so no need to define it. But other systems need it:

maplist(_Cont_1, []).
maplist( Cont_1, [A|As]) :-
   call(Cont_1, A),
   maplist(Cont_1, As).

For maplist/3 see this post.

And here is another solution, that terminates for above goal:

diffLengthLists([]).
diffLengthLists([L|Ls]) :-
   maplist(diffLength(L),Ls),
   diffLengthLists(Ls).

diffLength([], [_|_]).
diffLength([_|_], []).
diffLength([_|Es], [_|Fs]) :-
   diffLength(Es, Fs).

Find out a case where this predicate does not terminate, when it should fail!

Community
  • 1
  • 1
false
  • 10,264
  • 13
  • 101
  • 209
  • Actually the changes I made to the code resolves this issue, (and returns false for the example you gave, as expected). "That is what your homework was about", how do you know what my homework "was" about though? lol – Radix Feb 20 '12 at 01:49
  • (I will comment your code separately) But for the homework issue: Writing a definition that does terminate in all cases where there is no solution is definitely far beyond a homework assignment. – false Feb 20 '12 at 01:54
  • @Amino Acid: Your code still does not work - I do not see any significant changes. I.e. your definition still does not succeed for `diffLengthLists([[]])`. – false Feb 20 '12 at 01:59
  • 1
    `diffLengthLists([[a],[a,b|_]]).` should succeed (or loop), but it must not fail. Your implementation fails on it. – false Feb 20 '12 at 02:11
  • 1
    The minimal counterexample is `diffLengthLists([[],_]).` which should succeed or loop, but must not fail. – false Feb 20 '12 at 02:12
  • Oh I understand what you mean now. thanks! I thought you were my TA when you first posted this answer. haha – Radix Feb 20 '12 at 02:14
2

Now you've had time to chew on it and come up with a solution, here's another way of attacking it.

I always try to state in plain english what the solution is, then code that. In this case:

'uniqll is true if the length of a sublist not a member of a list containing the lengths for the rest of the list'

uniqll([],[]).

uniqll([H|T], [LenH|LensSoFar]) :-
  uniqll(T, LensSoFar),
  length(H, LenH),
  not(member(LenH, LensSoFar)).

If it's not clear what it happening, the prolog 'trace' predicate is your friend.. in fact it's most important when you're trying to get prolog to do the dirty work for you / come up with a minimal solution.

[trace] 10 ?-  uniqll([[2,3],[4,5]],X).
   Call: (6) uniqll([[2, 3], [4, 5]], _G1276) ? creep
   Call: (7) uniqll([[4, 5]], _G1360) ? creep
   Call: (8) uniqll([], _G1363) ? creep
   Exit: (8) uniqll([], []) ? creep
   Call: (8) length([4, 5], _G1362) ? creep
   Exit: (8) length([4, 5], 2) ? creep
^  Call: (8) not(member(2, [])) ? creep
^  Exit: (8) not(user:member(2, [])) ? creep
   Exit: (7) uniqll([[4, 5]], [2]) ? creep
   Call: (7) length([2, 3], _G1359) ? creep
   Exit: (7) length([2, 3], 2) ? creep
^  Call: (7) not(member(2, [2])) ? creep
^  Fail: (7) not(user:member(2, [2])) ? creep
   Fail: (6) uniqll([[2, 3], [4, 5]], _G1276) ? creep
false.

And for success:

[trace] 11 ?-  uniqll([[2,3],[4]],X).         
   Call: (6) uniqll([[2, 3], [4]], _G1479) ? creep
   Call: (7) uniqll([[4]], _G1560) ? creep
   Call: (8) uniqll([], _G1563) ? creep
   Exit: (8) uniqll([], []) ? creep
   Call: (8) length([4], _G1562) ? creep
   Exit: (8) length([4], 1) ? creep
^  Call: (8) not(member(1, [])) ? creep
^  Exit: (8) not(user:member(1, [])) ? creep
   Exit: (7) uniqll([[4]], [1]) ? creep
   Call: (7) length([2, 3], _G1559) ? creep
   Exit: (7) length([2, 3], 2) ? creep
^  Call: (7) not(member(2, [1])) ? creep
^  Exit: (7) not(user:member(2, [1])) ? creep
   Exit: (6) uniqll([[2, 3], [4]], [2, 1]) ? creep
X = [2, 1].

As a side effect, the list when True, the list at the end is the list of the lengths.

Prolog is just so damn cool and elegant.

magus
  • 1,347
  • 7
  • 13