如何在 Common LISP 中执行 While 循环?

How to do a While loop in Common LISP?

嗨,我是 Common Lisp 的新手,我找不到任何与我当前问题相关的教程,我在 Java 方面有不错的知识,我尝试从 [=24= 转换简单的程序] Common LISP 作为练习,我不能做的一件事是 while 循环,我该怎么做?它总是导致 UNDEFINED-FUNCTION

TL;DR

如何在常见的 LISP 中使用某些条件(例如 Java 中的 while 循环,如下所示:

while(UserIn > 0)
{
    LastD = UserIn % 10;
    Sum = Sum + LastD;
    Product = Product * LastD; 
    UserIn = UserIn / 10;
}


if (Sum == Product) 
{
    System.out.println("\nGiven number is a Spy number");
}
else 
{
    System.out.println("\nGiven number is not a Spy number");
}

我对common LISP的尝试如下

  (while (> userIn 0)
        (setq LastD(mod 10 UserIn))
        (setq Product(* LastD Product))
        (setq Sum(+ LastD Sum))
        (setq UserIn(/ UserIn 10)))
    
    (terpri)
    
    (if (= a b)
    (format t "is a spy number")
    (format t "is not a spy number"))
    )
    (Spynumber)

它一直在说:调试器调用了一个未定义的函数,谢谢!

Common Lisp 没有 while 形式,但它有一个更强大的 loop macro which has the while 您想要的关键字:

(loop while ... do ...)

另见 How to do a while loop in LISP

正如其他人所说,您可以使用 loop 来做到这一点,这是惯用的方法。

但是 Lisp 是可编程编程语言:如果你想要while,你可以while:

(defmacro while (test &body decls/tags/forms)
  `(do () ((not ,test) (values))
     ,@decls/tags/forms))

现在

> (let ((i 0))
    (while (< i 10)
      (print i)
      (incf i)))

0 
1 
2 
3 
4 
5 
6 
7 
8 
9 

> 

您在 Java 中的示例程序分配了未在当前范围内声明的变量。你在 Lisp 中有同样的问题,你在没有声明的变量上调用 setq 。您需要在 Lisp 中声明变量,否则行为未指定。

声明变量的一种方法是使用 let 块:

(let ((product 1)
      (sum 0))
   ...

   (setq sum (+ sum d)) ;; <-- here SETQ is well-defined

   ...)
      

此外,您计算除法的商和余数:在 Common Lisp 函数中可以 return 多个值,特别是 truncate 除法和 returns余数作为次要值:

(multiple-value-bind (q r) (truncate u 10)

   ...)

递归方法

可以将循环编写为递归过程,您的示例是我发现递归方法更容易理解的案例之一。让我们定义 spy-compute 一个包含三个参数的函数:一个数字、当前总和和当前余数的乘积:

(defun spy-compute (u s p)
  ...)

基本情况对应于 (= u 0),在这种情况下,函数 return 是总和和乘积,作为两个值:

(defun spy-compute (u s p)
  (if (= u 0)
      (values s p)
      ...))

递归的一般情况包括将 u 除以 10,并使用修改后的总和和乘积递归调用 spy-number

(defun spy-compute (u s p)
  (if (= u 0)
      (values s p)
      (multiple-value-bind (q r) (truncate u 10)
        (spy-compute q (+ s r) (* p r)))))

调用此函数时需要将总和初始化为0,将乘积初始化为1。您可以为调用者提供一个更简单的接口:

(defun spy (n)
  (spy-compute n 0 1))

(我在这里修复了一个错误,我把0和1颠倒了)

如果你想检查这个号码是不是间谍号码,你可以定义这个函数(p后缀是“predicate”,return是函数的命名约定布尔值):

(defun spyp (n)
  (multiple-value-bind (s p) (spy n)
    (= s p)))
例子

定义了上面的三个函数,我们来跟踪一下,看看1124是不是间谍号(剧透,是):

* (trace spy-compute spy spyp)
* (spyp 1124)

这里是执行轨迹,我手动添加了注释:

  ;; root call to SPYP with 1124
  0: (SO::SPYP 1124)
    ;; it calls SPY
    1: (SO::SPY 1124)
      ;; ... which calls SPY-COMPUTE with sum 0 and product 1
      2: (SO::SPY-COMPUTE 1124 0 1)
        ;; DIVIDE by TEN, REMAINDER is 4
        ;; RECURSE With SUM = SUM + 4 and PRODUCT = PRODUCT * 4
        3: (SO::SPY-COMPUTE 112 4 4)
          ;; DIVIDE by TEN: 112 = 11 * 10 + 2, adjust counters
          4: (SO::SPY-COMPUTE 11 6 8)
            ;; ETC.
            5: (SO::SPY-COMPUTE 1 7 8)
              ;; BASE CASE OF RECURSION, RETURN BOTH COUNTERS
              6: (SO::SPY-COMPUTE 0 8 8)
              ;; NO CHANGE IS MADE TO THE RESULT, IT BUBBLES UP
              6: SPY-COMPUTE returned 8 8
            5: SPY-COMPUTE returned 8 8
          4: SPY-COMPUTE returned 8 8
        3: SPY-COMPUTE returned 8 8
      2: SPY-COMPUTE returned 8 8
    1: SPY returned 8 8
  ;; CHECK if (= P S), which is T here
  0: SPYP returned T

迭代

你的例子也可以用循环来写。除了其他标准的循环方式,您还可以使用 iterate 包,它与 LOOP 不同,它允许将测试子句 (while) 与迭代子句 (for) 混合使用:

(ql:quickload :iterate) ;; see https://www.quicklisp.org/beta/

(use-package :iterate)

(defun iter-spy (n)
  (iter
    (for u :initially n :then q)
    (while (> u 0))
    (for (values q r) = (truncate u 10))
    (sum r :into s)
    (multiply r :into p)
    (finally (return
               (values s p)))))

使用do可以并行循环和赋值变量,语法是:

(do ((<var1> <var1-initial-value> <var1-step>)
     (<var2> <var2-initial-value> <var2-step>)
     ...)
    ((<exit-condition>)
     (<final-statement1>)
     (<final-statement2>)
     ...)
  (<action1-during-loop>)
  (<action2-during-loop>)
  ...)

所以你的代码,或多或少:

(let* ((UserIn (read))
       (UI UserIn))
  (do* ((LastD (rem UserIn 10) (rem UserIn 10))
        (Sum 0 (+ Sum LastD))
        (Product 1 (* Product LastD))
        (UserIn UserIn (truncate UserIn 10)))
       ((<= UserIn 0)
        (format t "~&LastD: ~f, Sum: ~f, Product: ~f, UserIn: ~f"
                LastD Sum Product UserIn)
        (if (= Sum Product)
            (format t "~&~A is Spy number" UI)
            (format t "~&~A is Not Spy number" UI)))
    (format t "~&LastD: ~f, Sum: ~f, Product: ~f, UserIn: ~f" 
            LastD Sum Product UserIn)))

> LastD: 4.0, Sum: 0.0, Product: 1.0, UserIn: 1124.0
> LastD: 4.0, Sum: 4.0, Product: 4.0, UserIn: 112.0
> LastD: 2.0, Sum: 6.0, Product: 8.0, UserIn: 11.0
> LastD: 1.0, Sum: 7.0, Product: 8.0, UserIn: 1.0
> LastD: 1.0, Sum: 8.0, Product: 8.0, UserIn: 0.0
> 1124 is Spy number
> LastD: 2.0, Sum: 0.0, Product: 1.0, UserIn: 12.0
> LastD: 2.0, Sum: 2.0, Product: 2.0, UserIn: 1.0
> LastD: 1.0, Sum: 3.0, Product: 2.0, UserIn: 0.0
> 12 is Not Spy number

对于某些代码片段,您可以访问 http://rosettacode.org/wiki/Rosetta_Code

(defmacro while (condition &rest body)
  `(loop while ,condition
         do (progn
              ,@body)))