Вариадная функция в схеме

Мне нужно определить в схеме вариативную функцию, которая принимает следующую форму: (define (n-loop procedure [a list of pairs (x,y)]), где список пар может быть любой длины.

Каждая пара определяет нижнюю и верхнюю границы. То есть следующий вызов функции: (n-loop (lambda (x y) (inspect (list x y))) (0 2) (0 3)) производит:

(list x y) is (0 0)
(list x y) is (0 1)
(list x y) is (0 2)
(list x y) is (1 0)
(list x y) is (1 1)
(list x y) is (1 2)

Очевидно, что в моем решении должны быть задействованы car и cdr. Но условие, которое затрудняет это, заключается в следующем. Не должно быть никаких операторов присваивания или итерационных циклов (пока и для).

Я мог бы справиться с этим, используя while и for для индексации списка пар, но, похоже, мне нужно использовать рекурсию. Мне не нужны какие-либо кодовые решения, если вы не считаете это необходимым для объяснения, но есть ли у кого-нибудь предложения относительно того, как это можно атаковать?


person aquemini    schedule 08.10.2012    source источник


Ответы (1)


Стандартный способ зацикливания в Scheme — использовать хвостовую рекурсию. На самом деле, допустим, у вас есть этот цикл:

(do ((a 0 b)
     (b 1 (+ a b))
     (i 0 (+ i 1)))
    ((>= i 10) a)
  (eprintf "(fib ~a) = ~a~%" i a))

Это на самом деле становится макрорасширенным во что-то вроде следующего:

(let loop ((a 0)
           (b 1)
           (i 0))
  (cond ((>= i 10) a)
        (else (eprintf "(fib ~a) = ~a~%" i a)
              (loop b (+ a b) (+ i 1)))))

Который, кроме того, получает макрорасширение в это (я не буду макрорасширять cond, так как это не имеет отношения к моей точке зрения):

(letrec ((loop (lambda (a b i)
                 (cond ((>= i 10) a)
                       (else (eprintf "(fib ~a) = ~a~%" i a)
                             (loop b (+ a b) (+ i 1)))))))
  (loop 0 1 0))

Вы должны увидеть здесь letrec и подумать: «Ага! Я вижу рекурсию!». Действительно, вы это делаете (в частности, в этом случае хвостовая рекурсия, хотя letrec может использоваться и для нехвостовых рекурсий).

Любой итеративный цикл в Схеме можно переписать как этот (версия с именем let — это то, как идиоматически записываются циклы в Схеме, но если ваше назначение не позволяет вам использовать именованный let, расширьте еще один шаг и используйте letrec). Макрорасширения, которые я описал выше, просты и механистичны, и вы сможете увидеть, как одно трансформируется в другое.


Поскольку ваш вопрос задал вопрос о функциях с переменным числом аргументов, вы можете написать функцию с переменным числом аргументов следующим образом:

(define (sum x . xs)
  (if (null? xs) x
      (apply sum (+ x (car xs)) (cdr xs))))

(Кстати, это ужасно неэффективный способ написания функции sum; я просто использую его, чтобы продемонстрировать, как вы будете отправлять (используя apply) и получать (используя неправильный лямбда-список) произвольное количество аргументов.)


Обновлять

Итак, вот несколько общих советов: вам понадобятся два цикла:

  1. внешний цикл, который проходит через уровни диапазона (это ваш вариативный материал)
  2. внутренний цикл, который перебирает числа на каждом уровне диапазона

В каждом из этих циклов тщательно продумайте:

  1. какое начальное условие
  2. каково конечное условие
  3. что вы хотите делать на каждой итерации
  4. есть ли какое-либо состояние, которое вам нужно сохранить между итерациями

В частности, тщательно продумайте последний пункт, так как именно так вы будете вкладывать свои циклы, учитывая произвольное количество уровней вложенности. (В приведенном ниже примере решения это то, что представляет собой переменная cur.)

После того, как вы определились со всеми этими вещами, вы можете сформулировать общую структуру вашего решения. Я опубликую базовую структуру своего решения ниже, но вы должны хорошо подумать о том, как вы хотите решить проблему, прежде чем смотреть на мой код, потому что это даст вам хорошее представление о различиях между ваш подход к решению и мой, и это поможет вам лучше понять мой код.

Кроме того, не бойтесь сначала написать его, используя цикл в императивном стиле (например, do), а затем преобразовать его в эквивалент с именем let, когда все заработает. Просто перечитайте первый раздел, чтобы увидеть, как сделать это преобразование.

Все, что сказано, вот мое решение (с удаленной спецификой):

(define (n-loop proc . ranges)
  (let outer ((cur ???)
              (ranges ranges))
    (cond ((null? ranges) ???)
          (else (do ((i (caar ranges) (+ i 1)))
                    ((>= i (cadar ranges)))
                  (outer ??? ???))))))

Помните, как только вы заработаете, вам все равно нужно будет преобразовать цикл do в цикл, основанный на названии let. (Или вам, возможно, придется пойти еще дальше и преобразовать как внешний, так и внутренний циклы в их letrec формы.)

person Chris Jester-Young    schedule 08.10.2012
comment
Я понимаю, как я могу использовать хвостовую рекурсию в обычных условиях. Но логистика рассматриваемой проблемы, кажется, отговаривает от традиционной рекурсии. При этом мне придется каким-то образом использовать вариативные функции. Я просто не уверен, как вкладывать циклы, когда параметры задаются таким образом (список минус-ячеек). Есть ли у вас предложения по конкретным проблемам? - person aquemini; 09.10.2012
comment
@ConnorWoods Позвольте мне написать решение самому, затем я смогу отредактировать его фрагменты и вернуться к вам с скелетным решением для игры. :-) - person Chris Jester-Young; 09.10.2012
comment
Хорошо, обновил пост, добавив гораздо больше подробностей о том, как вы собираетесь решать проблему. - person Chris Jester-Young; 10.10.2012