Search code examples
lispcommon-lispclisp

Average of entered numbers


In this program I want to get the average of the entered numbers not only the sum but I can't get the average and the "Enter a number: " keeps repeating.

here's my code

(princ"Enter how many numbers to read: ")
(defparameter a(read))

(defun num ()
  (loop repeat a
         sum (progn 
               (format *query-io* "Enter a number: ")
               (finish-output)
               (parse-integer (read-line *query-io* )))))


(format t "Sum: ~d ~%" (num))
(format t "Average: ~d ~%" (/ (num) a)) ;; I can't get the output for the average  and the "Enter a number: " keeps repeating.


Enter how many numbers to read: 5
Enter a number: 4
Enter a number: 3
Enter a number: 2
Enter a number: 1
Enter a number: 3
Sum: 13

Enter a number:   <-------

Solution

  • The reason that your prompt is repeating is because your code is calling NUM twice. Each time NUM is called, it asks for more input. A basic fix is to wrap the part of your program that shows the results in a function, just calling NUM once and binding the results to a variable:

    (princ "Enter how many numbers to read: ")
    (defparameter a (read))
    
    (defun num ()
      (loop repeat a
             sum (progn 
                   (format *query-io* "Enter a number: ")
                   (finish-output)
                   (parse-integer (read-line *query-io*)))))
    
    (defun stats ()
      (let ((sum (num)))
        (format t "Sum: ~d ~%" sum)
        (format t "Average: ~d ~%" (/ sum a))))
    

    This "works", but when the code is loaded the user is prompted for input to set the A parameter; then the user needs to call STATS to enter data and see the results. This is awkward to say the least. But there are lots of little problems with the program. Here are some suggestions:

    • Avoid using a global parameter at all
    • Call READ-LINE instead of READ to get the count of input elements
    • Use *QUERY-IO* consistently
    • Call FINISH-OUTPUT consistently
    • Use better variable names (A and NUM are not very descriptive here)
    • Use ~A instead of ~D

    The average may not be an integer; it could be a fraction. When its argument is not an integer, FORMAT uses ~A in place of ~D anyway, but it is probably better to be explicit about this. I would just use ~A in both lines of output.

    You can write one function to incorporate all of the above suggestions:

    (defun stats ()
      (format *query-io* "Enter how many numbers to read: ")
      (finish-output *query-io*)
      (let* ((count (parse-integer (read-line *query-io*)))
             (sum
               (loop repeat count
                     do (format *query-io* "Enter a number: ")
                        (finish-output *query-io*)
                     summing (parse-integer (read-line *query-io*)))))
        (format *query-io* "Sum: ~A~%Average: ~A~%" sum (/ sum count))
        (finish-output *query-io*)))
    

    Here COUNT replaces the earlier A, and it is a local variable within the STATS function. LET* is used instead of LET so that SUM can make use of COUNT, but nested LET expressions could be used instead. Note that SUM is bound to the result of the loop, which is the result from the SUMMING keyword. *QUERY-IO* is used consistently throughout, and printed output is always followed by FINISH-OUTPUT when its sequencing is important.

    There is a lot that could be done to further improve this code. There is no input validation in this code; that should be added. It might be good to break STATS into smaller functions that separate input, calculation, and output operations. It might be nice to be able to handle floats in input and output.

    Sample interaction:

    CL-USER> (stats)
    Enter how many numbers to read: 3
    Enter a number: 1
    Enter a number: 2
    Enter a number: 2
    Sum: 5
    Average: 5/3
    

    OP has asked in a comment:

    I tried to add (min count) (max count)) in your given code to get the minimum and maximum value but the output is the sum. How can I get minimum and maximum number?

    This is really a new question, but it points to shortcomings in the design of the above function that were hinted at with "It might be good to break STATS into smaller functions...."

    Firstly, note that (MIN COUNT) or (MAX COUNT) will not be helpful, since we want the minimum or maximum of the data entered; COUNT is just the number of values that the user wants to enter. The original code directly summed the values as they were input; a more flexible approach would be to collect the input in a list, and then to operate on that list to get the desired results. The MIN and MAX functions operate on a number of values, not on a list, so we will need to use APPLY to apply them to a list of results. We can also apply + to the list of input values to get the sum of the elements in the list:

    (defun stats ()
      (format *query-io* "Enter how many numbers to read: ")
      (finish-output *query-io*)
      (let* ((count (parse-integer (read-line *query-io*)))
             (data
               (loop repeat count
                     do (format *query-io* "Enter a number: ")
                        (finish-output *query-io*)
                     collecting (parse-integer (read-line *query-io*))))
             (min (apply #'min data))
             (max (apply #'max data))
             (sum (apply #'+ data))
             (avg (float (/ sum count))))
        (format *query-io* "Minimum: ~A~%" min)
        (format *query-io* "Maximum: ~A~%" max)
        (format *query-io* "Sum: ~A~%" sum)
        (format *query-io* "Average: ~A~%" avg)
        (finish-output *query-io*)))
    

    Here the loop collects input in a list by using the COLLECTING keyword, and the result is bound to DATA. The desired calculations are made, and the results are printed.

    This works, but it is really begging for a better design; there are too many different things happening in one function. You could move the display code into another function, returning MIN, MAX, etc. in a list or as multiple values. But then it would be even more flexible if the calculations were made independent of the code that gathers input by allowing STATS to return a list of input. The calculation of AVG requires COUNT; the new code could return COUNT too, but it is not needed anyway, since we can call LENGTH on the input list to get a count. One wonders why we need the user to enter a number of elements at all. What if we took numbers from the user until a non-numeric value is entered?

    This answer has already gotten quite long, but below is some code that breaks the STATS function into smaller parts, refining it in the process. By using smaller function definitions that are more focused on their tasks, the functions can be reused or combined with other functions to accomplish other goals more easily, and it will be easier to modify the definitions when changes are required. The final function, PROMPT-FOR-STATS-REPORT does essentially what the earlier STATS function did. It is now much easier to add new functionality by modifying PROMPT-FOR-STATS. New accessor functions can be added as needed when PROMPT-FOR-STATS is augmented, and PROMPT-FOR-STATS-REPORT can be modified to display results differently, or to access and display newly added functionality.

    This is by no means the optimal solution to OP's problem, if there is an "optimal" solution. I encourage OP to try to find ways to improve upon the design of this code. There are some comments scattered throughout:

    ;;; Here is a function to prompt for an integer. Since we want to receive
    ;;; non-numeric input `:JUNK-ALLOWED` is set to `T`. When input that can not be
    ;;; parsed into an integer is provided, `PARSE-INTEGER` will now return `NIL`
    ;;; instead of signalling an error condition.
    (defun prompt-for-int (msg)
      (format *query-io* "~A" msg)
      (finish-output *query-io*)
      (parse-integer (read-line *query-io*) :junk-allowed t))
    
    ;;; Here is a function to prompt for input until some non-numeric value
    ;;; is given. The results are collected in a list and returned.
    (defun prompt-for-ints (msg)
      (format *query-io* "~A~%" msg)
      (finish-output *query-io*)
      (let (input-num)
        (loop
          do (setf input-num (prompt-for-int "Enter a number ENTER to quit: "))
          while input-num
          collecting input-num)))
    
    ;;; Some functions to calculate statistics:
    (defun count-of-nums (xs)
      (length xs))
    
    (defun min-of-nums (xs)
      (apply #'min xs))
    
    (defun max-of-nums (xs)
      (apply #'max xs))
    
    (defun sum-of-nums (xs)
      (apply #'+ xs))
    
    (defun avg-of-nums (xs)
      (float (/ (sum-of-nums xs)
                (count-of-nums xs))))
    
    ;;; A function to prompt the user for input which returns a list of statistics.
    (defun prompt-for-stats (msg)
      (let ((data (prompt-for-ints msg)))
        (list (count-of-nums data)
              (min-of-nums data)
              (max-of-nums data)
              (sum-of-nums data)
              (avg-of-nums data))))
    
    ;;; Accessor functions for a list of statistics:
    (defun get-stats-count (stats)
      (first stats))
    
    (defun get-stats-min (stats)
      (second stats))
    
    (defun get-stats-max (stats)
      (third stats))
    
    (defun get-stats-sum (stats)
      (fourth stats))
    
    (defun get-stats-avg (stats)
      (fifth stats))
    
    ;;; A function that prompts for input and displays results.
    (defun prompt-for-stats-report ()
      (let ((results (prompt-for-stats "Enter some integers to view statistics")))
        (format *query-io* "Count: ~A~%" (get-stats-count results))
        (format *query-io* "Minimum: ~A~%" (get-stats-min results))
        (format *query-io* "Maximum: ~A~%" (get-stats-max results))
        (format *query-io* "Sum: ~A~%" (get-stats-sum results))
        (format *query-io* "Average: ~A~%" (get-stats-avg results))
        (finish-output *query-io*)))
    

    Sample interaction:

    CL-USER> (prompt-for-stats-report)
    Enter some integers to view statistics
    Enter a number ENTER to quit: 1
    Enter a number ENTER to quit: 2
    Enter a number ENTER to quit: 1
    Enter a number ENTER to quit: 
    Count: 3
    Minimum: 1
    Maximum: 2
    Sum: 4
    Average: 1.3333334