Search code examples
listfunctional-programmingprologinstantiation-error

Prolog - average predicate: Arguments not sufficiently instantiated


I have a list of cars (auto in german), where the first Variable is the license-plate and the second one the speed:

[auto(eu-ts884, 69), auto(dn-gh184, 64), auto(ac-lj123, 72)].

Now I try to write an average predicate but it fails with the error message:

ERROR: Arguments are not sufficiently instantiated

My code so far:

durchschnitt([], 0, 0).
durchschnitt([auto(_, X)|Tail], L, Y):-
                        Y is S/L,
                        L > 0,
                        cardinal([auto(_, X)|Tail], L),
                        sumKilometer([auto(_, X)|Tail], S).


sumKilometer([], 0).
sumKilometer([auto(_, X)|Tail], Sum) :-
            sumKilometer(Tail, N),
            Sum is N + X.


cardinal([], 0).
cardinal([_|Tail], Result) :-
  cardinal(Tail, N),
  Result is N + 1.

My code is quite equivalent to that post, although I cannot make out my mistake.

Note: sumKilometer and cardinal are working fine.


Solution

  • You write:

    durchschnitt([], 0, 0).
    durchschnitt([auto(_, X)|Tail], L, Y):-
        Y is S/L,
        L > 0,
        cardinal([auto(_, X)|Tail], L),
        sumKilometer([auto(_, X)|Tail], S).
    

    The first problem is that when you call durchschnitt([auto(foo,2)],L,Y), L is a free variable. As a result, you cannot calculate Y is S/L since both S and L are unknown here.

    You can however use:

    durchschnitt([], 0, 0).
    durchschnitt([auto(_, X)|Tail], L, Y):-
        cardinal([auto(_, X)|Tail], L),
        sumKilometer([auto(_, X)|Tail], S),
        Y is S/L.

    So here you calculate the average after both L and S are known. Furthermore you do not unify the list with [auto(_,X)|Tail], etc. A simple check like A = [_|_] is sufficient:

    durchschnitt([], 0, 0).
    durchschnitt(A, L, Y):-
        A = [_|_],
        cardinal(A, L),
        sumKilometer(A, S),
        Y is S/L.

    This will also reduce the amount of time spent packing and unpacking.

    Sum, Length and Average all concurrently

    You can construct a predicate that calculates the three all at the same time (so without looping twice over the list). You can simply use accumulators, like:

    durchschnitt(A,L,Y) :-
        durchschnitt(A,0,0,L,Y).

    Here the second and third element are the running sum and length respectively.

    Now for durchschnitt/5, there are two cases. In the first case we have reached the end of the list, and we thus have to calculate the average and return it, like:

    durchschnitt([],S,L,L,Y) :-
        (L \= 0
        -> Y is S/L
        ; Y = 0).
    

    So we use an if-then-else to check if the length is something different than 0 (in the case there are no autos in the list, we return 0 as average.

    In the recursive case, we simple increment the running length and update the running sum, like:

    durchschnitt([auto(_,Si)|T],RS,RL,L,Y) :-
        RSN is RS+Si,
        L1 is L+1,
        durchschnitt(T,RSN,L1,L,Y).
    

    Or putting it together:

    durchschnitt(A,L,Y) :-
        durchschnitt(A,0,0,L,Y).
    
    durchschnitt([],S,L,L,Y) :-
        (L \= 0
        -> Y is S/L
        ; Y = 0).
    durchschnitt([auto(_,Si)|T],RS,RL,L,Y) :-
        RSN is RS+Si,
        L1 is L+1,
        durchschnitt(T,RSN,L1,L,Y).