Reputation: 419
I recently encounter the following problem in Prolog: Define a predicate avg_num/0
that interactively prompts the user to input a series of numbers (or the word "stop" without quotation marks to end the process). The program outputs the average of these numbers.
In Prolog terminal/interpreter, the program should run like this:
?- avg_sum.
?- Enter a number (or stop to end): 2.
?- Enter a number (or stop to end): |: 3.
?- Enter a number (or stop to end): |: 7.
?- Enter a number (or stop to end): |: stop.
The average is 4.
true.
My idea is to store the value into a list, and then compute the average of numbers of that list using the following code.
% finding the sum of numbers of a list
sumlist([],0).
sumlist([H|T],Sum):- sumlist(T,SumTail), Sum is H + SumTail .
% finding the average of the numbers in a list
average(List,A):- sumlist(List,Sum), length(List,Length), A is Sum / Length .
The problem is then reduced to: how to store the number from user input to a list? Somehow I feel that I need to initialize the empty list at the beginning, but I don't know where to put it. I've tried the following piece of code (this is incomplete though):
avg_sum:- write('Enter a number (or stop to end): '), process(Ans).
process(Ans):- read(Ans),Ans \= "stop", append(X,[Ans],L).
% this part is still incomplete and incorrect.
It seems that I need to initialize the empty list, but I don't know where.
Upvotes: 0
Views: 1372
Reputation: 58324
I would separate out a predicate that reads the list, then handle the list afterwards.
Here's one way to read in the list until an ending term, End
:
% read_list_until
%
read_list_until(L, End) :-
( read_element(E, End)
-> L = [E|L1],
read_list_until(L1, End)
; L = []
).
read_element(E, End) :-
read(E),
dif(E, End).
This has the following behavior:
2 ?- read_list_until(L, stop).
|: 1.
|: 2.
|: 3.
|: stop.
L = [1, 2, 3].
3 ?-
Then you can just use standard Prolog predicates to take the average:
avg_list(L, Avg) :-
read_list_until(L, stop),
sum_list(L, Sum),
length(L, Num),
Avg is Sum / Num.
Upvotes: 1
Reputation: 9378
You're almost there. As you said, you need to keep a list of inputs and then do a final computation once the entire list is known. Such a list (or other object keeping intermediate data) is often called an accumulator.
Here is one way of implementing this. It is based on two auxiliary predicates, one for doing the I/O part and one for doing the computation.
We start the computation with an empty list, as you said.
avg_num :-
avg_num([], Average),
write('The average is '), write(Average), writeln('.').
(Recall that in Prolog you can overload predicate names with different arities, i.e., avg_num/0
is a separate predicate from avg_num/2
.)
Now avg_num/2
can ask for an input and hand off processing of that input to process/3
:
avg_num(Accumulator, Average) :-
write('Enter a number (or stop to end): '),
read(Answer),
process(Answer, Accumulator, Average).
Only in process/3
do we either consume the accumulator by computing its average if stop
was input, or (if different from stop
) by simply adding the answer to the accumulator and continuing.
process(stop, Accumulator, Average) :-
average(Accumulator, Average).
process(Answer, Accumulator, Average) :-
dif(Answer, stop),
avg_num([Answer | Accumulator], Average).
Note that the order of the numbers in the list does not matter for computing the average, so we can add the input at the front of the list, which is easier than appending at the end.
The mutual recursion between the avg_num/2
and process/3
predicates is not necessarily easy to understand, especially because the names are not very well chosen.
In practice, I would get rid of process/3
by simplifying avg_num/2
like this:
avg_num(Accumulator, Average) :-
write('Enter a number (or stop to end): '),
read(Answer),
( Answer = stop
-> average(Accumulator, Average)
; avg_num([Answer | Accumulator], Average) ).
Upvotes: 1