Как правильно написать это условие в цикле для Common Lisp? - PullRequest
0 голосов
/ 05 июня 2018
(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))

Я новичок в Common Lisp, и мне нужно знать, какой синтаксис подходит для этого цикла.

Я хочу получить циклический список, такой как (lista-n 0 5 2) => (0 2 4 1 3 5)

Список от 0 до 5 на 2. Если число> 5, то число - 5.

1 Ответ

0 голосов
/ 05 июня 2018

Проблемы с кодом

В отличие от большинства конструкций в Common Lisp, синтаксис LOOP специально использует несколько скобок.Часть (while ...) не подходит в этом контексте.Кроме того, вы можете использовать until (= x a), который я считаю более читабельным.Взгляните на §22.LOOP для черных поясов .

Кроме того, (- 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)))

Использование большего количества математик

Благодаря математике можно узнать размер периода из охватываемого диапазона и шага: Прогрессии по модулю 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)
...