代码问题
与 Common Lisp 中的大多数结构相反,LOOP 的语法故意使用了少量括号。 (while ...) 部分在这种情况下不合适。此外,您可以使用until (= x a),我觉得它更具可读性。看看§22. LOOP for Black Belts。
另外,(- x b) 只计算减法,但不影响任何变量。如果你想减少x,比如C语言中的x -= b,使用(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)))
使用更多数学
借助数学,可以从所覆盖的范围和步长中知道周期的大小:Progressions modulo 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))
然而,我们需要再次重复以满足规范并收集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)