Common-Lisp中的递归因子函数

时间:2017-12-01 22:59:02

标签: recursion input output lisp common-lisp

好的,我一直在学习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只是漂浮在那里而周围没有括号。但是,稍微修补一下(在函数外部写下额外的行)我可以让它执行(同样令人困惑!)。

但那不是我想要的!我喜欢使用阶乘函数来打印/返回值,而不必在其外部执行其他代码。

我做错了什么?

4 个答案:

答案 0 :(得分:3)

实际上需要使用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)))

答案 1 :(得分:2)

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

在第2行,您为x提供了一个值,但尚未通过该名称声明存在变量。虽然名称(defvar x)被认为是单一的,但您可以x执行此操作。当您尝试设置尚未定义的内容时,许多实现都会发出警告并自动创建全局变量。

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

对于跟踪执行,您可以(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)))

答案 2 :(得分:2)

在回答你的问题之前,我想告诉你一些关于Lisp的基本知识。 (最后解决了你的解决方案)

  • 在Lisp中,每个函数的输出都是&#34;在函数&#34;中执行的最后一行。除非您使用某些语法操作,例如&#34; return&#34;或&#34; return-from&#34;,这不是Lisp-way。

  • (格式t&#34;您的字符串&#34;)将始终返回&#39; NIL作为其输出。但在返回输出之前,此功能&#34;打印&#34;字符串也是。 但格式功能的输出是“NIL。

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

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

这将返回&#39; NIL。

要说服自己,请按照定义的函数运行以下命令:

(equal (factorial 1) 'nil)

返回:

1! is 1
T

所以&#34;打印&#34;你的字符串,然后输出T.因此,你的函数的输出确实是'NIL。

因此,当您输入任何大于1的数字时,递归调用将运行并作为输入1到达结尾并返回&#39; NIL。 然后尝试执行此操作:

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

*的第二个参数是&#39; 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)

Neater代码(具有类似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))

答案 3 :(得分:1)

您可以将其拆分为多个部分,如下所示:

(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