99 Lisp Prolog Erlang problems

Since I have been lapsing in my Erlang practice, I am playing with L-99: Ninety-Nine Lisp Problems, based on a Prolog problem list – which is fitting, since the first Erlang interpreter was written in Prolog…

I’ll post links to my solutions when I have enough – I have done P01 to P20, with the exception of P13. Feels good to get back in the groove…

Erlang

Update
I posted some sample code in the comments, but here is a permanent link to the source code.

9 Responses to “99 Lisp Prolog Erlang problems”

  1. Christian Says:

    A good source of exercises, indeed! I will try in Erlang and Prolog too.

    Maybe you can post a solution from time to time, so we can compare? There are many solutions sometimes, the simpler ones not being tail-recursive or too greedy in memory.

  2. dda Says:

    P01
    mylast(A) -> mylast(A,[]).
    mylast([],B) -> B;
    mylast([H|T],A) ->
      mylast(T,H).

    P02
    mybutlast(A) -> mybutlast(A, [], []).
    mybutlast([], A, B) -> A;
    mybutlast([H|T], A, B) ->
      mybutlast(T, B, H).

    P03
    kthelem(A, N) -> kthelem(A, 1, N).
    kthelem([], _, _) -> {error, out_of_range};
    kthelem([H|T], Current, Current) -> H;
    kthelem([H|T], Current, N) ->
      kthelem(T, Current+1, N).

    P04
    len(A) -> len(A, 0).
    len([], Len) -> Len;
    len([H|T], Len) -> len(T, Len+1).

    P05
    reverse([]) -> [];
    reverse([H|T]) -> reverse(T,[H]).
    reverse([], L) -> L;
    reverse([H|T], L) ->
      reverse(T, [H|L]).

    P06
    palindrome(A) ->
      B=reverse(A),
      case A of
       B -> true;
       _ -> false
      end.

    P07
    I had difficulties with this one, and ended up getting a hint or three from lists:flatten/1’s source code :-(
    flatten(List) when is_list(List) ->
        do_flatten(List, []).
    flatten(List, Tail) when is_list(List), is_list(Tail) ->
        do_flatten(List, Tail).
    do_flatten([H|T], Tail) when is_list(H) ->
        do_flatten(H, do_flatten(T, Tail));
    do_flatten([H|T], Tail) ->
        [H|do_flatten(T, Tail)];
    do_flatten([], Tail) ->
        Tail.

    P08
    compress(A) -> compress(A, [], []).
    compress([], L, _) -> flatten(L);
    compress([H|T], L, H) -> compress(T, L, H);
    compress([H|T], L, X) -> compress(T, [L,H], H).

    P09
    pack(A) -> pack(A,[],[],[]).
    pack([], X, Liste, Acc) -> reverse([flatten([Acc,X])|Liste]);
    pack([H|T], H, Liste,Acc) ->
      pack(T, H, Liste, [H,Acc]);
    pack([H|T], [], Liste, Acc) ->
      pack(T, H, Liste, [H]);
    pack([H|T], X, Liste, []) ->
      pack(T, H, [[X]|Liste], []);
    pack([H|T], X, Liste, Acc) ->
      pack(T, H, [flatten([Acc,X])|Liste], []).

    P10
    runlen(A) -> runlen(A,[],[],[]).
    runlen([], X, Liste, Acc) ->
      runlengths([flatten([Acc,X])|Liste],[]);
    runlen([H|T], H, Liste,Acc) ->
      runlen(T, H, Liste, [H,Acc]);
    runlen([H|T], [], Liste, Acc) ->
      runlen(T, H, Liste, [H]);
    runlen([H|T], X, Liste, []) ->
      runlen(T, H, [[X]|Liste], []);
    runlen([H|T], X, Liste, Acc) ->
      runlen(T, H, [flatten([Acc,X])|Liste], []).
    %% Compute sums
    runlengths([],Acc) -> Acc;
    runlengths([H|T],Acc) ->
      [Elem|_]=H,
      runlengths(T,[[len(H), Elem]|Acc]).

    P11
    runlen2(A) -> runlen2(A,[],[],[]).
    runlen2([], X, Liste, Acc) ->
      io:format(”list looks like: ~p~n”,[lists:reverse([flatten([Acc,X])|Liste])]),
      runlengths2([flatten([Acc,X])|Liste],[]);
    runlen2([H|T], H, Liste,Acc) ->
      runlen2(T, H, Liste, [H,Acc]);
    runlen2([H|T], [], Liste, Acc) ->
      runlen2(T, H, Liste, [H]);
    runlen2([H|T], X, Liste, []) ->
      runlen2(T, H, [[X]|Liste], []);
    runlen2([H|T], X, Liste, Acc) ->
      runlen2(T, H, [flatten([Acc,X])|Liste], []).
    %% Compute sums
    runlengths2([],Acc) -> Acc;
    runlengths2([H|T],Acc) ->
      [Elem|_]=H,
      Len=len(H),
      case Len of
        1 -> runlengths2(T,[[Elem]|Acc]);
        _ -> runlengths2(T,[[len(H), Elem]|Acc])
      end.

    P12
    decoderunlength(A) -> decoderunlength(A, []).
    decoderunlength([], Acc) -> reverse(Acc);
    decoderunlength([H|T], Acc) ->
      Len=len(H),
      case Len of
        1 -> decoderunlength(T, [H|Acc]);
        _ -> decoderunlength(T, [deflate(H)|Acc])
      end.

    deflate([Len, Elem]) -> deflate(Elem, 1, Len, [Elem]).
    deflate(Elem, Current, Current, Liste) -> Liste;
    deflate(Elem, Current, Len, Liste) ->
      deflate(Elem, Current+1, Len, [Elem|Liste]).

    P14
    dupli(A) -> dupli(A, []).
    dupli([], Acc) -> reverse(Acc);
    dupli([H|T], Acc) ->
      dupli(T, [H,H|Acc]).

    P15
    repli(A, Times) -> repli(A, Times, []).
    repli([], Times, Acc) -> reverse(flatten(Acc));
    repli([H|T], Times, Acc) ->
      repli(T, Times, [deflate([Times, H])|Acc]).

    P16
    %% I had first misunderstood the problem and wrote a routine
    %% that drops the nth element only
    drop(A, N) -> drop(A, 1, N, []).
    drop([], _, _, Acc) -> reverse(Acc);
    drop([H|T], Current, Current, Acc) -> flatten(reverse([T|Acc]));
    drop([H|T], Current, N, Acc) ->
      drop(T, Current+1, N, [H|Acc]).

    %% And now the correct solution
    dropnth(A, N) -> dropnth(A, 1, N, []).
    dropnth([], _, _, Acc) -> reverse(Acc);
    dropnth([H|T], Current, Current, Acc) ->
      dropnth(T, 1, Current, Acc);
    dropnth([H|T], Current, N, Acc) ->
      dropnth(T, Current+1, N, [H|Acc]).

    P17
    split(A, Len) -> split(A, 1, Len, []).
    split([], _, _, Acc) -> reverse(Acc);
    split([H|T], Current, Current, Acc) -> [reverse([H|Acc])|[T]];
    split([H|T], Current, Len, Acc) ->
      split(T, Current+1, Len, [H|Acc]).

    P18
    slice(A, Start, End) when Start > End ->
      {error, start_point_greater_than_endpoint};
    slice(A, 0, End) ->
      {error, start_point_is_null};
    slice(A, Start, End) -> slice(A, 1, Start, End, []).
    slice([], End, Start, End, Acc) -> reverse(Acc);
    slice([H|T], End, Start, End, Acc) -> reverse([H|Acc]);
    slice([H|T], Start, Start, End, Acc) ->
      slice(T, Start+1, Start, End, [H|Acc]);
    slice([H|T], Current, Start, End, _) when Current
      slice(T, Current+1, Start, End, []);
    slice([H|T], Current, Start, End, Acc) ->
      slice(T, Current+1, Start, End, [H|Acc]).

    P19
    lrot(A, N) -> lrot(A, 1, N, []).
    lrot([], _, _, Acc) -> {error};
    lrot([H|T], Current, Current, Acc) ->
      flatten([T|reverse([H|Acc])]);
    lrot([H|T], Current, N, Acc) ->
      lrot(T, Current+1, N, [H|Acc]).

  3. dda Says:

    P20 solved in P16

    P21
    insertAt(Elem, Liste, Position) -> insertAt(Liste, 1, Position, Elem, []).
    insertAt([], _, _, Elem, Acc) -> reverse(Acc);
    insertAt([H|T], Current, Current, Elem, Acc) -> flatten(reverse([T, H, Elem|Acc]));
    insertAt([H|T], Current, N, Elem, Acc) ->
      insertAt(T, Current+1, N, Elem, [H|Acc]).

    P22
    range(Start, End) ->
      if
       Start == End -> [Start];
       Start > End -> {error, “Start > End”};
       Start range(Start, End, [])
      end.
    range(End, End, Acc) -> reverse([End|Acc]);
    range(Start, End, Acc) ->
      range(Start+1, End, [Start|Acc]).

    P23
    rndSelect(Liste, Len) -> rndSelect(Liste, Len, []).
    rndSelect([], _, Acc) -> reverse(Acc);
    rndSelect(Liste, 0, Acc) -> reverse(Acc);
    rndSelect(Liste, N, Acc) ->
      L=len(Liste),
      Rnd=random:uniform(L),
      [NewListe, Number]=extract(Liste, 1, Rnd, []),
      rndSelect(NewListe, N-1, [Number|Acc]).

    extract([H|T], Current, Current, Acc) -> [flatten(reverse([T|Acc])), H];
    extract([H|T], Current, N, Acc) ->
      extract(T, Current+1, N, [H|Acc]).

    P24
    lottoSelect(N, Max) ->
      rndSelect(range(1,Max),N).

    P25
    rndPermu(Liste) -> rndSelect(Liste, len(Liste)).

  4. Christian Says:

    I haven’t finished yet, but here are my comments and code.

    P01
    The problem with your solution is that it returns the empty list when the input is the empty list, but also when the last element of a non-empty list is the empty list… In the first case, there should be an error, i.e. either a user-defined exception or a VM-level exception. If I understand the philosophy of Erlang, I would write

    mylast([Item|Items]) -> mylast(Items,Item).

    P02
    The English is broken in the question and the example is different from the Prolog version. In LISP, it seems the output is the list of the two last elements, whereas in Prolog it is indeed the last element which is the “result”. I assume the latter interpretation, as you did. You have the same problem as with P01. So I would rather write

    mybutlast([A,B|Items]) -> mybutlast(Items,A,B).

    P03
    Since the only possible error here is for index to be out of range, I would not bother to return an error, so the logic of the application is not obscured by the logic of errors (since there is only one possible here). It is unusual for me, but it seems this is the Erlang way, don’t you agree? But there is a logical error you do not avoid here, which happens when the index is negative. Also, if you decrement the index you don’t need another index. So I wrote:

    element_at([Item|Items],N) when N > 0 ->
    element_at__([Item|Items],N).
    element_at__([Item|_],1) -> Item;
    element_at__([_|Items],N) -> element_at__(Items,N-1).

    [I will submit this comment now to check whether the HTML is fine, and if so, resume.]

  5. Christian Says:

    P06
    Your solution is the simplest one but not the most efficient, I believe, since you construct the reversed list of the input. I have been thinking a little and came up with a different approach, that keeps the memory usage to a small constant plus the space of the input if the garbage collector is quick enough or if the input is big enough. The idea consists in splitting the input list. If there are an odd number of items, the middle item is discarded. The run-time do not rebuild individual items (just change some pointers) and the garbage collector will discover that the head of first half of the initial list is no more accessible during the splitting. If the list is big enough, it will trigger a collection. Then the two parts of the list are matched against each other, thus relying, as you do, on the run-time environment for that task. The first half of the input list is reversed by the splitting, so matching is immediate:

    palindrome(L) -> are_equal(split(L)).

    split(L) ->
    Length=ninety:length(L),
    case Length rem 2 of
    0 -> split(even,Length div 2,[],L);
    1 -> split(odd,Length div 2,[],L)
    end.

    split(even,0,RevFstHalf,SecHalf) -> {RevFstHalf,SecHalf};
    split(odd,0,RevFstHalf,[_|SecHalf]) -> {RevFstHalf,SecHalf};
    split(Mode,HalfLen,Left,[Item|Right]) -> split(Mode,HalfLen-1,[Item|Left],Right).

    are_equal({L,L}) when list(L) -> yes;
    are_equal(_) -> no.

    It would seem that the time complexity (counting a constant for each item traverses) is greater than in your solution, because I compute the length of the input list first and because we do not take into account the cost of the matching. But if we do, it is in fact the same because in your solution, the input list, of, say, N items, is reversed, which costs N, then the matching also costs N, thus total is 2N. In my code, the length computation costs N, then the half list is build at the cost of [N/2], then the matching costs [N/2], so total is 2N as well. What do you think?

    P07

    Why is there a flatten/2 in your code? I am learning Erlang, so I have a question: is the guard predicate is_list/1 or list/1? There is a source of inefficience with your solution:

    do_flatten([H|T], Tail) -> [H|do_flatten(T, Tail)];

    This is not a tail-recursive call, i.e. it is not compiled as a loop, so the size of the BEAM stack will grow in proportion to the number of items in the input list from the item which is not a list, until the end. If all items in the input list are lists, no problem, the stack usage will remain reasonable, since the other clause contains a tail recursive call and one which is not. But it is unfortunate that the non tail-recursive call implies T instead of H… So I propose a little modification to solve these two sources of inefficience at once:

    flatten(List) -> reverse(rev_flatten([],List)).

    rev_flatten(Acc,[]) -> Acc;
    rev_flatten(Acc,[Item|Items]) when list(Item) -> rev_flatten(rev_flatten(Acc,Item),Items);
    rev_flatten(Acc,[Item|Items]) -> rev_flatten([Item|Acc],Items).

    P08
    Your solution is complex (three parameters) and inefficient because it relies on flatten/1, which is not constant stack space (despite my efforts). There is a direct and fully tail recursive solution:

    compress(List) -> compress([],List).

    compress(Acc,[]) -> reverse(Acc);
    compress([Item|Acc],[Item|Items]) -> compress([Item|Acc],Items); % Skip item
    compress(Acc,[Other|Items]) -> compress([Other|Acc],Items).

    This is something I like about Erlang and Prolog: non-linear patterns, i.e. patterns with repeated variables.

    P09
    Again, your solution is very complex (didn’t check it fully) and not efficient (relies on flatten/1. Here is a better one

    pack(L) -> pack([],L).

    pack(Acc,[]) -> reverse(Acc);
    pack([[Item|Same]|Others],[Item|Items]) -> pack([[Item,Item|Same]|Others],Items);
    pack(Acc,[Item|Items]) -> pack([[Item]|Acc],Items).

    Again, tail-recursivity and non-linear patterns are our best friends.

    P10
    Your code is incredibly complex, I did not even bother checking it, sorry, because I have a tiny and efficient solution for you:

    encode(L) -> encode([],pack(fun (X) -> X end,[],L)).

    encode(Codes,[]) -> Codes;
    encode(Codes,[[Item|Items]|Lists]) ->
    encode([{ninety:length(Items)+1,Item}|Codes],Lists).

    You wonder what is this pack/3 function, thanks for asking:) The question requires to reuse pack, but I realized that pack/1 is reversing the accumulator and I need here the elements in the order of the accumulaor (which is the reversed order with respect to the input). So, instead of duplicating the code of pack/2 I instead rewrote pack/2 into a pack/3 as follows:

    pack(L) -> pack(fun (X) -> reverse(X) end,[],L).

    pack(Finalize,Acc,[]) -> Finalize(Acc);
    pack(Finalize,[[Item|Same]|Others],[Item|Items]) ->
    pack(Finalize,[[Item,Item|Same]|Others],Items);
    pack(Finalize,Acc,[Item|Items]) ->pack(Finalize,[[Item]|Acc],Items).

    Notice the extra first parameter which is the function to apply to the accumulator when we are done. This makes pack/3 a higher-order function, which is a cool name:) All this to be able to parameterize what we do to the accumulator at the end. Here, in pack/1, we want to reverse it, hence

    fun (X) -> reverse(X) end

    Now, in encode/2, we do not want to reverse it, thus

    fun (X) -> X end

    which is the identity function.

    By the way, ninety:length/1 is what you called ninety:len/1, obviously.

    Tell me what you think of this and I will come back for the rest. Note that dispite my professoral tone, I often make mistakes, so disbelief is very welcome:)

  6. Christian Says:

    I said

    But it is unfortunate that the non tail-recursive call implies T instead of H…

    but this is wrong. T may be shorter than H, in which case it was fortunate! The time complexity here does not depend on the number of the items which are not lists but on the whole structure as well (it is a tree, actually, because we deal with lists of lists, in general).

  7. Christian Says:

    My last comment was about P07.

  8. Christian Says:

    I would like to learn some Ruby. What resource do you recommend?

  9. dda Says:

    *The* Ruby book

Leave a Reply

You must be logged in to post a comment.