输入数字的平均值

问题描述

在这个程序中,我想获得输入数字的平均值,不仅是总和,而且我无法获得平均值,并且“输入数字:”不断重复。

这是我的代码

(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:   <-------

解决方法

提示重复的原因是您的代码调用了 NUM 两次。每次调用 NUM 时,它都会要求更多的输入。一个基本的解决方法是将显示结果的程序部分包装在一个函数中,只需调用一次 NUM 并将结果绑定到一个变量:

(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))))

这“有效”,但是当加载代码时,系统会提示用户输入以设置 A 参数;那么用户需要调用STATS来输入数据并查看结果。至少可以说这很尴尬。但是这个程序有很多小问题。以下是一些建议:

  • 完全避免使用全局参数
  • 调用 READ-LINE 而不是 READ 来获取输入元素的数量
  • 始终使用*QUERY-IO*
  • 始终如一地调用 FINISH-OUTPUT
  • 使用更好的变量名(ANUM 在这里不是很具有描述性)
  • 使用 ~A 而不是 ~D

平均值可能不是整数;它可能是一小部分。当它的参数不是整数时,FORMAT 无论如何都使用 ~A 代替 ~D,但最好明确说明这一点。我会在两行输出中都使用 ~A

您可以编写一个函数来合并上述所有建议:

(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*)))

这里的COUNT替换了之前的A,它是STATS函数中的一个局部变量。使用 LET* 代替 LET 以便 SUM 可以使用 COUNT,但可以使用嵌套的 LET 表达式代替。请注意,SUM 绑定到循环的结果,即 SUMMING 关键字的结果。 *QUERY-IO* 始终贯穿始终,当其顺序很重要时,打印输出后始终跟有 FINISH-OUTPUT

可以做很多事情来进一步改进此代码。此代码中没有输入验证;应该加上。将 STATS 分解为将输入、计算和输出操作分开的较小函数可能会很好。能够处理输入和输出中的浮点数可能会很好。

示例交互:

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 在评论中询问:

我试图在给定的代码中添加 (min count) (max count)) 以获得 最小值和最大值,但输出是总和。我怎样才能得到 最小和最大数量?

这确实是一个新问题,但它指出了上述函数设计中的缺点,这些缺点用“STATS 分解成更小的函数可能是好的...."

首先,请注意 (MIN COUNT)(MAX COUNT) 不会有帮助,因为我们想要输入数据的最小值或最大值; COUNT 只是用户想要输入的值的数量。原始代码直接对输入的值求和;更灵活的方法是将输入收集到一个列表中,然后对该列表进行操作以获得所需的结果。 MINMAX 函数对多个值进行操作,而不是对列表进行操作,因此我们需要使用 APPLY 将它们应用于结果列表。我们还可以将 + 应用于输入值列表以获取列表中元素的总和:

(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*)))

这里的循环使用 COLLECTING 关键字收集列表中的输入,并将结果绑定到 DATA。进行所需的计算,并打印结果。

这行得通,但它确实在乞求更好的设计;在一个函数中发生了太多不同的事情。您可以将显示代码移动到另一个函数中,以列表或多个值的形式返回 MINMAX 等。但是,如果计算独立于收集输入的代码,允许 STATS 返回输入列表,那么它会更加灵活。 AVG 的计算需要 COUNT;新代码也可以返回 COUNT,但无论如何都不需要它,因为我们可以在输入列表上调用 LENGTH 来获取计数。有人想知道为什么我们需要用户输入大量元素。如果我们从用户那里获取数字直到输入非数字值会怎样?

这个答案已经很长了,但下面是一些将 STATS 函数分解为更小的部分并在此过程中对其进行细化的代码。通过使用更专注于其任务的更小的函数定义,这些函数可以被重用或与其他函数结合以更容易地完成其他目标,并且在需要更改时更容易修改定义。最后一个函数 PROMPT-FOR-STATS-REPORT 基本上完成了之前的 STATS 函数所做的工作。现在通过修改 PROMPT-FOR-STATS 可以更容易地添加新功能。扩充 PROMPT-FOR-STATS 时可以根据需要添加新的访问器函数,并且可以修改 PROMPT-FOR-STATS-REPORT 以不同方式显示结果,或访问和显示新添加的功能。

如果存在“最佳”解决方案,这绝不是 OP 问题的最佳解决方案。我鼓励 OP 尝试寻找改进此代码设计的方法。散落着一些评论:

;;; 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*)))

示例交互:

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