Search code examples
lispcommon-lisp

How to do a While loop in Common LISP?


Hi I am new in Common Lisp and there aren't any tutorials I can find that relates to my current problem, I have decent knowledge in Java and I tried converting simple programs from Java to Common LISP as an exercise and one particular thing I can't do is a while loop, how do I do it? it always results in UNDEFINED-FUNCTION

TL;DR

how do I do a while loop in common LISP with certain conditions like in Java like this:

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");
}

my attempt in common LISP is as follows

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

and it keeps on saying: debugger invoked on a UNDEFINED-FUNCTION, thank you!


Solution

  • Your example program in Java assigns variables that are not declared in the current scope. You have the same problem in Lisp, where you call setq on variables that are not declared. You need to declare variables in Lisp otherwise the behavior is unspecified.

    One way to declare variables is to have a let block:

    (let ((product 1)
          (sum 0))
       ...
    
       (setq sum (+ sum d)) ;; <-- here SETQ is well-defined
    
       ...)
          
    

    Also, you compute both quotient and the remainder of a division: in Common Lisp functions can return more than one values, and in particular, truncate divides and returns the remainder as a secondary value:

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

    Recursive approach

    It is possible to write loops as recursive procedures, and your example is one of the cases where I find the recursive approach easier to understand. Let's define spy-compute a function of three arguments: a number, the current sum, and the current product of the remainders:

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

    The base case corresponds to (= u 0), and in this case the function returns both the sum and the product, as two values:

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

    The generat case of recursion consists in dividing u by 10, and calling spy-number recursively with modified sum and products:

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

    This functions needs to be called with the sum initialized to 0 and the product initialized to 1. You can provide a simpler interface to the caller:

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

    (I fixed a bug here, I had 0 and 1 reversed)

    And if you want to check if the number is a spy number, you can define this functions (the p suffix is for "predicate", the naming convention for functions that returns a boolean value):

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

    Having the three functions defined as above, let's trace them and check if 1124 is a spy number (spoiler alert, it is):

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

    Here is the execution trace, I added comments manually:

      ;; 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

    Your example can also be written using a loop. In addition other the standard ways of looping, you can also use the iterate package, which contrary to LOOP allows to mix the test clauses (while) with iteration clauses (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)))))