【问题标题】:Average of entered numbers输入数字的平均值
【发布时间】:2021-02-14 06:34:30
【问题描述】:

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

这是我的代码

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

【问题讨论】:

    标签: lisp common-lisp clisp


    【解决方案1】:

    您的提示重复的原因是您的代码调用了两次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
    

    【讨论】:

    • 谢谢!如何将输出分数转换为小数?
    • 如果您只想将平均值视为浮点数,请更改:(/ sum count) --> (float (/ sum count))。如果你想采用浮点输入,那就有点棘手了。您可能对@tfb's answer here 感兴趣
    • 我尝试在您给定的代码中添加 (min count) (max count)) 以获得最小值和最大值,但输出是总和。如何获得最小和最大数量?我不知道。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多