问题描述
在这个程序中,我想获得输入数字的平均值,不仅是总和,而且我无法获得平均值,并且“输入数字:”不断重复。
这是我的代码
(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
- 使用更好的变量名(
A
和NUM
在这里不是很具有描述性) - 使用
~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
只是用户想要输入的值的数量。原始代码直接对输入的值求和;更灵活的方法是将输入收集到一个列表中,然后对该列表进行操作以获得所需的结果。 MIN
和 MAX
函数对多个值进行操作,而不是对列表进行操作,因此我们需要使用 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
。进行所需的计算,并打印结果。
这行得通,但它确实在乞求更好的设计;在一个函数中发生了太多不同的事情。您可以将显示代码移动到另一个函数中,以列表或多个值的形式返回 MIN
、MAX
等。但是,如果计算独立于收集输入的代码,允许 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