Схема / Lisp вложенные циклы и рекурсия
Я пытаюсь решить проблему в схеме, которая требует от меня, чтобы использовать вложенный цикл или вложенной рекурсии.
например, у меня есть два списка, которые мне нужно проверить условие на их декартовом произведении.
каков наилучший способ подхода к этим типам проблем? Любые указатели на то, как упростить эти типы функций?
Я немного поясню, так как мои намерения могут быть недостаточно ясны.
обычная рекурсивная функция может выглядеть так:
(define (factorial n)
(factorial-impl n 1))
(define (factorial-impl n t)
(if (eq? n 0)
t
(factorial-impl (- n 1) (* t n))))
попытка написать подобную функцию, но с вложенной рекурсией вводит новый уровень сложности кода, и мне было интересно, какой базовый шаблон для этих типов функций, так как он может стать очень уродливым, очень быстро.
в качестве конкретного примера я ищу самый простой способ посетить все элементы в декартовом произведении двух списков.
3 ответов
В Схеме, Функция "карта" часто удобна для вычисления одного списка на основе другого.
фактически, в схеме map принимает функцию "N-аргумента "и списки" n " и вызывает функция для каждого соответствующего элемента каждого списка:
> (map * '(3 4 5) '(1 2 3))
(3 8 15)
но очень естественным дополнением к этому будет функция "декартовой карты", которая вызовет вашу функцию" N-аргумента " со всеми различными способами выбора одного элемента из каждого списка. Мне потребовалось время, чтобы понять. точно, как это сделать, но вот вы идете:
; curry takes:
; * a p-argument function AND
; * n actual arguments,
; and returns a function requiring only (p-n) arguments
; where the first "n" arguments are already bound. A simple
; example
; (define add1 (curry + 1))
; (add1 3)
; => 4
; Many other languages implicitly "curry" whenever you call
; a function with not enough arguments.
(define curry
(lambda (f . c) (lambda x (apply f (append c x)))))
; take a list of tuples and an element, return another list
; with that element stitched on to each of the tuples:
; e.g.
; > (stitch '(1 2 3) 4)
; ((4 . 1) (4 . 2) (4 . 3))
(define stitch
(lambda (tuples element)
(map (curry cons element) tuples)))
; Flatten takes a list of lists and produces a single list
; e.g.
; > (flatten '((1 2) (3 4)))
; (1 2 3 4)
(define flatten
(curry apply append))
; cartesian takes two lists and returns their cartesian product
; e.g.
; > (cartesian '(1 2 3) '(4 5))
; ((1 . 4) (1 . 5) (2 . 4) (2 . 5) (3 . 4) (3 . 5))
(define cartesian
(lambda (l1 l2)
(flatten (map (curry stitch l2) l1))))
; cartesian-lists takes a list of lists
; and returns a single list containing the cartesian product of all of the lists.
; We start with a list containing a single 'nil', so that we create a
; "list of lists" rather than a list of "tuples".
; The other interesting function we use here is "fold-right" (sometimes called
; "foldr" or "reduce" in other implementations). It can be used
; to collapse a list from right to left using some binary operation and an
; initial value.
; e.g.
; (fold-right cons '() '(1 2 3))
; is equivalent to
; ((cons 1 (cons 2 (cons 3 '())))
; In our case, we have a list of lists, and our binary operation is to get the
; "cartesian product" between each list.
(define cartesian-lists
(lambda (lists)
(fold-right cartesian '(()) lists)))
; cartesian-map takes a n-argument function and n lists
; and returns a single list containing the result of calling that
; n-argument function for each combination of elements in the list:
; > (cartesian-map list '(a b) '(c d e) '(f g))
; ((a c f) (a c g) (a d f) (a d g) (a e f) (a e g) (b c f)
; (b c g) (b d f) (b d g) (b e f) (b e g))
(define cartesian-map
(lambda (f . lists)
(map (curry apply f) (cartesian-lists lists))))
без всех комментариев и более компактного синтаксиса определения функций у нас есть:
(define (curry f . c) (lambda x (apply f (append c x))))
(define (stitch tuples element)
(map (curry cons element) tuples))
(define flatten (curry apply append))
(define (cartesian l1 l2)
(flatten (map (curry stitch l2) l1)))
(define cartesian-lists (curry fold-right cartesian '(()))))
(define (cartesian-map f . lists)
(map (curry apply f) (cartesian-lists lists)))
Я думал, что выше было достаточно "элегантно"... пока кто-то не показал мне эквивалентное определение Хаскелла:
cartes f (a:b:[]) = [ f x y | x <- a , y <- b ]
cartes f (a:b:bs) = cartes f ([ f x y | x <- a , y <- b ]:bs)
2 линии!!!
Я не так уверен в эффективности моей реализации-в частности, шаг" сгладить "был быстро написать, но может в конечном итоге вызвать" добавить" с очень большое количество списков, которые могут быть или не быть очень эффективными на некоторых схемах реализации.
для максимальной практичности / полезности вам понадобится версия, которая может принимать "лениво оцененные" списки/потоки/итератор, а не полностью указанные списки.... функция "cartesian-map-stream", если хотите, затем вернет" поток " результатов... но это зависит от контекста (я думаю о концепции "потока", введенной в SICP)... и придет бесплатно Версия Haskell благодаря ленивой оценке.
В общем, в схеме, если вы хотите "вырваться" из цикла в какой-то момент, Вы также можете использовать продолжение (например, выбрасывание исключения, но это принято практиковать в схеме для потока управления).
мне было весело писать это!
Я не уверен, что понимаю, в чем проблема. Я считаю, что главное, что вы должны понимать в функциональном программировании : строить сложные функции, составляя несколько более простых функций.
например, в данном случае:
;compute the list of the (x,y) for y in l
(define (pairs x l)
(define (aux accu x l)
(if (null? l)
accu
(let ((y (car l))
(tail (cdr l)))
(aux (cons (cons x y) accu) x tail))))
(aux '() x l))
(define (cartesian-product l m)
(define (aux accu l)
(if (null? l)
accu
(let ((x (car l))
(tail (cdr l)))
(aux (append (pairs x m) accu) tail))))
(aux '() l))
вы определяете различные шаги: чтобы получить декартово произведение, если вы "петляете" над первым списком, вам нужно будет вычислить список (x,y)
, for y
во втором списке.
здесь уже есть несколько хороших ответов, но для простых вложенных функций (таких как ваш хвост-рекурсивный факториал) я предпочитаю именованный let:
(define factorial
(lambda (n)
(let factorial-impl ([n n] [t 1])
(if (eq? n 0)
t
(factorial-impl (- n 1) (* t n))))))