您的提示重复的原因是您的代码调用了两次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 尝试找到改进此代码设计的方法。到处散落着一些cmets:
;;; 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