【问题标题】:Recursive Factorial Function in Common-LispCommon-Lisp 中的递归阶乘函数
【发布时间】:2018-05-16 02:34:21
【问题描述】:

好的,我一直在学习 COMMON LISP 编程,并且正在开发一个非常简单的程序来计算给定整数的阶乘。很简单吧?

到目前为止的代码如下:

(write-line "Please enter a number...")  
(setq x  (read))
(defun factorial(n)
    (if (= n 1)
            (setq a 1)
    )
    (if (> n 1)
        (setq a (* n (factorial (- n 1))))
    )
    (format t "~D! is ~D" n a)
)

(factorial x)

问题是,当我在 CodeChef 或 Rexter.com 上运行此程序时,我收到类似的错误:“NIL 不是数字。”

我尝试使用 cond 而不是 if 无济于事。

顺便说一句,最令人困惑的是,我见过很多地方写这样的代码:

   (defun fact(n)
(if (= n 1)
    1
    (* n (fact (- n 1)))))

这对我来说甚至没有意义,1 只是漂浮在那里,周围没有括号。但是,只要稍加修改(在函数之外编写额外的行),我就可以让它执行(同样令人困惑!)。

但这不是我想要的!我希望阶乘函数可以打印/返回值,而无需在其外部执行其他代码。

我做错了什么?

【问题讨论】:

  • 你的函数factorialreturn有什么作用?
  • 其他问题:x 未定义,a 未定义,write-line 之后 I/O 缓冲区未清空。
  • 对于调试,您可能需要查看回溯。此外,STEP 和 TRACE 可能会为您提供有关计算的见解。

标签: recursion input output lisp common-lisp


【解决方案1】:

实际上需要使用FINISH-OUTPUT 刷新可移植代码中的I/O 缓冲区——否则Lisp 可能想要读取某些内容而提示尚未打印。最好将SETQ 替换为LET,因为SETQ 不引入变量,它只是设置它。

(defun factorial (n)
  (if (= n 1)              
      1                           
      (* n (factorial (- n 1))))) 

(write-line "Please enter a number...")
(finish-output)              ; this makes sure the text is printed now
(let ((x (read)))
 (format t "~D! is ~D" x (factorial x)))

【讨论】:

    【解决方案2】:

    在回答你的问题之前,我想告诉你一些关于 Lisp 的基本知识。 (最后对您的解决方案进行巧妙的修复)

    • 在 Lisp 中,每个函数的输出都是“函数中执行的最后一行”。除非您使用诸如“return”或“return-from”之类的语法操作,否则这不是 Lisp 方式。

    • (format t "your string") 将始终返回 'NIL 作为其输出。但是在返回输出之前,这个函数也会“打印”字符串。 但是格式化函数的输出是'NIL。

    现在,代码的问题在于函数的输出。同样,输出将是最后一行,在您的情况下是:

    (format t "~D! is ~D" n a)
    

    这将返回 'NIL。

    要说服自己,请根据您定义的函数运行以下命令:

    (equal (factorial 1) 'nil)
    

    这会返回:

    1! is 1
    T
    

    所以它“打印”你的字符串,然后输出 T。因此你的函数的输出确实是 'NIL。

    因此,当您输入任何大于 1 的数字时,递归调用将运行并作为输入 1 到达末尾并返回 'NIL. 然后尝试执行:

    (setq a (* n (factorial (- n 1))))
    

    * 的第二个参数是 'NIL,因此是错误。

    解决方案的快速解决方法是将最后一行添加为输出:

    (write-line "Please enter a number...")  
    (setq x  (read))
    (defun factorial(n)
        (if (= n 1)
                (setq a 1)
        )
        (if (> n 1)
            (setq a (* n (factorial (- n 1))))
        )
        (format t "~D! is ~D" n a)
        a  ;; Now this is the last line, so this will work
    )
    
    (factorial x)
    

    更整洁的代码(带有类似 Lisp 的缩进)

    (defun factorial (n)
      (if (= n 1)              
          1                           
          (* n (factorial (- n 1))))) 
    
    (write-line "Please enter a number...")  
    (setq x  (read))
    (format t "~D! is ~D" x (factorial x))
    

    【讨论】:

    • X 仍未定义,您需要确保输出到达用户。
    • 感谢详细的解释!输出有一个小问题(正如 Rainer Joswig 警告的那样),所以我决定将第二个代码示例的最后一行添加到调用中。再次感谢!
    【解决方案3】:

    Common Lisp 是为编译而设计的。因此,如果您需要全局或局部变量,则需要在设置它们之前对其进行定义。

    在第 2 行,您给 x 一个值,但没有声明该名称的变量的存在。您可以使用 (defvar x) 这样做,尽管名称 x 被认为是单调的。当您尝试设置尚未定义的内容时,许多实现会发出警告并自动创建一个全局变量。

    在您的factorial 函数中,您尝试设置a。这被视为错误或全局变量。请注意,在您的递归调用中,您正在更改 a 的值,尽管这实际上不会产生太大影响,因为您的函数的其余部分是正确的。您的功能也不是可重入的,没有理由这样做。您可以使用let 引入局部变量。或者,您可以将其作为(n &aux a) 添加到您的 lambda 列表中。其次,您的阶乘函数不会返回有用的值,因为 format 不会返回有用的值。在(隐式)progn 中的 Common Lisp 中,返回最终表达式的值。您可以通过在 format 下方的行中添加 a 来解决此问题。

    对于跟踪执行,您可以执行(trace factorial) 以自动打印正确的跟踪信息。然后你可以摆脱你的format 声明。

    最后值得注意的是,整个函数非常单一。你的语法不正常。 Common Lisp 实现带有漂亮的打印机。 Emacs 也是如此(绑定到M-q)。通常不会对全局变量进行大量读取和设置(偶尔在 repl 时除外)。 Lisp 并没有真正用于这种风格的脚本,并且有更好的控制范围的机制。其次,通常不会在这样的函数中使用如此多的状态突变。这是一种不同的阶乘方式:

    (defun factorial (n)
      (if (< n 2)
          1
          (* n (factorial (1- n)))))
    

    并递归地拖尾:

    (defun factorial (n &optional (a 1))
      (if (< n 2) a (factorial (1- n) (* a n))))
    

    并且迭代地(带有打印):

    (defun factorial (n)
       (loop for i from 1 to n
             with a = 1
             do (setf a (* a i))
                (format t “~a! = ~a~%” i a)
             finally (return a)))
    

    【讨论】:

    • 是的,我正在尝试用 C++ 的背景自学 Lisp,所以我的很多语法、样式和格式可能并不典型。感谢您的所有解释和见解!
    • 在c++中你需要声明变量。在 Lisp 中,您还需要声明变量。想想let,就像在 c++ 中引入块作用域一样。在 Lisp 中,一切都是表达式而不是语句(有一些奇怪的例外两者都不是),例如,Lisp 中的 if 在某种程度上更像 c++ 三元运算符 ?: 而不是 c++ if 语句跨度>
    【解决方案4】:

    你可以把它分成几部分,像这样:

    (defun prompt (prompt-str)
      (write-line prompt-str *query-io*)
      (finish-output)
      (read *query-io*))
    
    (defun factorial (n)
      (cond ((= n 1) 1)
            (t (* n
                  (factorial (decf n)))))
    
    (defun factorial-driver ()
      (let* ((n (prompt "Enter a number: "))
             (result (factorial n)))
        (format *query-io* "The factorial of ~A is ~A~%" n result)))
    

    然后以(factorial-driver) 运行整个事情。

    示例交互:

    CL-USER 54 > (factorial-driver)
    Enter a number: 
    4
    The factorial of 4 is 24
    

    【讨论】:

    • 你的prompt函数真的应该在连线和读取之间FINISH-OUTPUT。此外,使用*QUERY-IO* 而不是*STANDARD-INPUT**STANDARD-OUTPUT* 更可取
    • 完成,始终使用 `*query-io*。
    猜你喜欢
    • 2019-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多