如何在Loop for Common Lisp中正确编写这个条件?

J O*_*tiz 2 loops common-lisp while-loop

(defun lista-n (a b c)   
  (loop repeat 10
        for x = (+ a c) then (+ x c)                            
                             (while (/= x a) 
                                    do (if (> x b) 
                                       (- x b))   ;then               
                             collect x))
Run Code Online (Sandbox Code Playgroud)

我是Common Lisp的新手,我需要知道哪个是这个Loop的正确语法.

我希望能够获得一个循环列表,如(lista-n 0 5 2)=>(0 2 4 1 3 5)

0到5之间的列表2.如果Number> 5则为Number - 5.

cor*_*ump 5

代码问题

与Common Lisp中的大多数构造相反,LOOP的语法故意使用很少的括号.该(while ...)部分不适合这种情况.此外,您可以使用until (= x a),我觉得更具可读性.看看§22.黑带环圈.

此外,(- x b)仅计算减法,但不影响任何变量.如果你想减少x,就像x -= b在C中一样,使用(decf x b).

函数和变量的名称也无助于理解应该发生的事情.

最后,如果你有一个非常大的步骤,你的代码可能行为不正确,因为简单的计算(- x b)可能会给出一个仍然大于的结果b.此外,负输入可能存在问题.

第一次尝试

我试图处理我想到的所有极端情况,例如消极步骤等.还有一个测试通过检查当前数字是否已经存在于列表中来阻止无限循环.检查是时间线性的,这使得整个循环是二次的.对于非常大的列表,这可能是一个问题.

(defun circular-range (from to step)
  (loop
     with low = (min from to) and high = (max from to)
     with divisor = (- high low)
     for value = from then (+ wrapped step)
     for wrapped = (if (<= low value high) 
                       value
                       (+ low (mod (- value low) divisor)))
     until (member wrapped numbers)
     collect wrapped into numbers
     until (= wrapped to)
     finally (return numbers)))
Run Code Online (Sandbox Code Playgroud)

使用更多的数学

多亏了数学,可以从所涵盖的范围和步骤中获知周期的大小:进度模数n.这允许删除一些检查,特别是已经看到的数字列表.

(defun circular-range (from to step)
  (loop
     with low = (min from to) and high = (max from to)
     with range = (- high low)
     with period = (/ range (gcd range step))
     repeat (1+ period)
     for value = from then (+ wrapped step)
     for first = t then nil
     for wrapped = (if (<= low value high) 
                       value
                       (+ low (mod (- value low) range)))
     when (or first (/= wrapped from))
       collect wrapped))
Run Code Online (Sandbox Code Playgroud)

然而,我们需要再次重复以满足规范并收集to值,除非该值等于from.

测试

以下结果与两个版本相同.

(circular-range 0 5 0)
=> (0)

(circular-range 0 5 2)
=> (0 2 4 1 3 5)

(circular-range 0 -5 2)
=> (0 -3 -1 -4 -2)

(circular-range 10 -5 2)
=> (10 -3 -1 1 3 5 7 9 -4 -2 0 2 4 6 8)

(circular-range 10 50 13)
=> (10 23 36 49 22 35 48 21 34 47 20 33 46 19 32 45 18 31 44 17 30 43 16 29 42 15
   28 41 14 27 40 13 26 39 12 25 38 11 24 37 50)

(circular-range 30 35 -2)
=> (30 33 31 34 32)

(circular-range 30 30 -5)
=> (30)
Run Code Online (Sandbox Code Playgroud)

  • 当WITH子句和FOR子句是独立的时,你可以用AND标记它:FOR a from b to c and d from to f ... WITH foo = 1 and bar = 2 .... (2认同)