我正在尝试解决Scheme中的一个问题,它要求我使用嵌套循环或嵌套递归。
e.g。我有两个列表,我必须检查其笛卡尔积的条件。
解决这些类型问题的最佳方法是什么?关于如何简化这些类型的函数的任何指针?
<小时/> 我会详细说明一下,因为我的意图可能不够明确。
常规递归函数可能如下所示:
(define (factorial n)
(factorial-impl n 1))
(define (factorial-impl n t)
(if (eq? n 0)
t
(factorial-impl (- n 1) (* t n))))
尝试编写类似的函数但嵌套递归会给代码带来新的复杂程度,我想知道这些类型的函数的基本模式是什么,因为它可能非常难看,非常快。
作为一个具体的例子,我正在寻找最简单的方法来访问两个列表的笛卡尔积中的所有项目。
答案 0 :(得分:13)
在Scheme中, “map”功能通常可以方便地根据另一个列表计算一个列表。
实际上,在scheme中,map采用“n-argument”函数和“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)))
我认为上面的内容相当“优雅”......直到有人向我展示了相同的Haskell定义:
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行!!!
我对我的实施效率并不是很有信心 - 特别是“扁平”步骤很快写出来但最终可能会被称为“附加” 具有大量列表,在某些Scheme上可能会或可能不会非常有效 的实施方式。
为了获得最终的实用性/实用性,您需要一个可以采用“懒惰评估”列表/流/迭代器而非完全指定列表的版本....如果您愿意,可以使用“cartesian-map-stream”函数,那么返回结果的“流”...但这取决于上下文(我正在考虑SICP中引入的“流”概念)...并且由于它的懒惰评估而将从Haskell版本免费获得。
一般来说,在Scheme中,如果你想在某些时候“突破”循环,你也可以使用一个延续(比如抛出一个异常但是在控制流程的Scheme中是公认的做法)。
我很开心写这个!
答案 1 :(得分:2)
我不确定我是否知道问题所在。 我相信在函数式编程中你必须要理解的主要事情是:通过组合几个更简单的函数来构建复杂的函数。
例如,在这种情况下:
;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))
您确定了不同的步骤:获取笛卡尔积,如果您“循环”在第一个列表上,您将必须能够为{{1}计算(x,y)
的列表在第二个列表中。
答案 2 :(得分:2)
这里有一些很好的答案,但是对于简单的嵌套函数(比如你的尾递归因子),我更喜欢名为let:
(define factorial
(lambda (n)
(let factorial-impl ([n n] [t 1])
(if (eq? n 0)
t
(factorial-impl (- n 1) (* t n))))))