【问题标题】:Scheme/Lisp nested loops and recursionScheme/Lisp 嵌套循环和递归
【发布时间】:2010-12-12 02:36:16
【问题描述】:

我正在尝试解决 Scheme 中要求我使用嵌套循环或嵌套递归的问题。

例如我有两个列表,我必须检查他们的笛卡尔积的条件。

解决这类问题的最佳方法是什么?有关如何简化这些类型的函数的任何指示?


我会详细说明一下,因为我的意图可能不够清楚。

常规递归函数可能如下所示:

(define (factorial n)
  (factorial-impl n 1))

(define (factorial-impl n t)
  (if (eq? n 0)
      t
      (factorial-impl (- n 1) (* t n))))

尝试编写一个类似的函数,但使用嵌套递归会给代码带来新的复杂性,我想知道这些类型的函数的基本模式是什么,因为它会变得非常丑陋,非常快。

作为一个具体的例子,我正在寻找最简单的方法来访问两个列表的笛卡尔积中的所有项目。

【问题讨论】:

  • 您的问题不是很具体。您能否提供更多关于您正在尝试做的事情以及遇到的问题的信息?
  • 也许您可以尝试使用 SRFI-42 编写它并在此处发布以展示您想要完成的任务?

标签: functional-programming lisp scheme


【解决方案1】:

在方案中, “map”函数通常可以方便地根据另一个列表计算一个列表。

事实上,在 scheme 中,map 接受一个“n-argument”函数和“n”个列表并调用 每个列表的每个对应元素的函数:

> (map * '(3 4 5) '(1 2 3))
(3 8 15)

但是一个非常自然的补充是一个“笛卡尔地图”函数,它会调用你的“n-argument”函数,其中包含从每个列表中选择一个元素的所有不同方式。我花了一段时间才弄清楚到底该怎么做,但是你去吧:

; 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))))

没有所有的 cmets 和一些更紧凑的函数定义语法:

(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 行!!!

我对我的实现效率不是很有信心——尤其是“扁平化”步骤写得很快,但最终可能会调用“附加” 有非常多的列表,这在某些方案上可能非常有效,也可能不是非常有效 实现。

为了最终的实用性/实用性,您需要一个可以采用“惰性评估”列表/流/迭代器而不是完全指定的列表的版本......如果您愿意,可以使用“笛卡尔地图流”功能,然后返回结果的“流”......但这取决于上下文(我正在考虑在 SICP 中引入的“流”概念)......并且由于它的惰性评估,它可以从 Haskell 版本中免费获得。

一般来说,在 Scheme 中,如果您想在某个时候“中断”循环,您还可以使用延续(例如抛出异常,但在 Scheme 中这是控制流的公认做法)。

我写的很开心!

【讨论】:

  • 另请注意:我个人认为使用递归执行某种“迭代”并不优雅。有足够多的高阶函数(map、fold-right 等),您不必递归来执行任何简单的循环。
【解决方案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))       

您确定了不同的步骤:要获得笛卡尔积,如果您“循环”第一个列表,您将必须能够计算 (x,y) 的列表,因为 y 在第二个列表。

【讨论】:

    【解决方案3】:

    这里已经有一些很好的答案,但是对于简单的嵌套函数(比如你的尾递归阶乘),我更喜欢命名的 let:

    (define factorial  
      (lambda (n)
        (let factorial-impl ([n n] [t 1])
          (if (eq? n 0)
            t
            (factorial-impl (- n 1) (* t n))))))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-14
      • 1970-01-01
      • 1970-01-01
      • 2021-05-19
      • 1970-01-01
      • 1970-01-01
      • 2018-11-11
      • 1970-01-01
      相关资源
      最近更新 更多