【问题标题】:Using cons/car vs append to create lists in Racket使用 cons/car vs append 在 Racket 中创建列表
【发布时间】:2016-12-05 22:39:18
【问题描述】:

我想制作一个正方形列表。我可以用以下代码做到这一点:

(define (makelistsq n)
  (define outlist '())
  (for ((i n))
    (set! outlist (append outlist (list (* i i))))
    )
  outlist
  )

(makelistsq 5)

输出:

'(0 1 4 9 16)

但是,我发现经常使用 cons 和 car 关键字来创建和添加到列表中。与上述附加相比,该方法有什么优势吗?所以,是跟上面更好还是一样:

(define (makelistsq2 n)
  (define outlist '())
  (for ((i n))
    (set! outlist (cons (* i i) outlist))
    )
  (reverse outlist)
  )

感谢您的回答/cmets。

编辑:在此页面Cons element to list vs cons list to element in Scheme 上提到所有使用 append 都是错误的:

对于需要在最后添加一个元素的极少数情况 (相信我,这样做通常意味着您正在考虑 算法错误)你可以使用追加

【问题讨论】:

  • 我看到您正在努力掌握 Scheme 的窍门。请拿起一本书(SICP, How to Design Programs, The Little Schemer)阅读,阅读,直到你掌握了在 Scheme 中编写代码的方法。到目前为止,您正在尝试像使用命令式编程语言一样编写程序,而这绝对不是学习 Lisp 时要走的路。

标签: scheme racket


【解决方案1】:

我不会重复我自己的答案:P。是的,使用append 通常是构建输出列表的错误方法。但是您的两个实现似乎都不正确 - 大多数时候,您必须忘记set! 和循环。

递归是要走的路,最后使用cons 和(如有必要)reverse 是构建列表的首选方式。其实问题中的函数使用内置程序可以更简洁地表达,这是编写函数式代码时推荐的方式:

(define (makelistsq2 n)
  (map (lambda (x) (* x x))
       (range n)))

为了完整起见,让我们看看我们将如何从头开始编写过程,使用cons 构建输出 - 在极少数情况下没有内置过程可以满足您的需要。这会生成一个递归过程,注意这里我们从零到n-1

(define (makelistsq2 n)
  (define (helper i)
    (if (= i n)
        '()
        (cons (* i i)
              (helper (add1 i)))))
  (helper 0))

上述解决方案很好,非常适合小型列表。如果(且仅当)输出列表很大,您可能希望使用尾递归来构建它;这更有效,因为它不需要额外的内存进行迭代 - 我们将使用命名为 let,并注意这会生成一个迭代过程,从 n-1 变为零:

(define (makelistsq2 n)
  (let loop ((i (sub1 n)) (acc '()))
    (if (negative? i)
        acc
        (loop (sub1 i) (cons (* i i) acc)))))

无论您选择哪种实现,结果现在都符合预期:

(makelistsq2 5)
=> '(0 1 4 9 16)

【讨论】:

  • 如果只发送一个整数来表示需要的上层正方形(从 0 开始),您将如何编码。因此,我需要将该函数称为 (makelistsq3 5) 以获取 '(0 1 4 9 16)。
  • @rnso 你去,三种不同风格的解决方案
  • 太棒了。您也可以留下以前的代码,让每个人都受益。
  • @rnso 但我误解了这个问题:(。无论如何,SO中有很多这样的代码示例,其中一些是我写的:)
【解决方案2】:

cons 是一对的构造函数。列表只不过是由空列表() 终止的cons 链。

append 是一个在实现中使用cons 的函数。这是两个参数附加的实现:

(define (append lst1 lst2)
  (if (null? lst1)
      lst2
      (cons (car lst1) 
            (append (cdr lst1) lst2))))

简单地说,它为每对lst1 生成一个新对,然后附加 lst2。它的效率不是很高,因此在您的第一个示例中,要生成 5 个元素列表,除了您为每个步骤明确创建的一个元素列表之外,它还必须创建一个 4、3 和 2 个元素列表。

使用列表时,它会按从头到尾的顺序进行迭代,但列表的创建是从头到尾进行的。通常不可能做相反的事情,因此最终需要反转结果,但在你的情况下,倒数而不是倒数很容易。

(define (make-square-list end)
  (let loop ((n (sub1 end)) (acc '()))
    (if (< n 0)
        acc
        (loop (sub1 n) 
              (cons (* n n) acc)))))

使用高阶函数,您可以消除样板代码,但#!racket 有一个替代方案,可以生成更短的代码,称为for/list

(define (make-square-list end)
  (for/list ((n (range end)))
    (* n n)))

for/listmap 基本相同,但作为一种特殊形式。

【讨论】:

    【解决方案3】:

    我的版本:

    #lang racket
    
    (define (make-square-list n)
      (let loop ([count 1]
                 [result_list '()])
        (if (<= count n)
            (loop (add1 count) (cons (* count count) result_list))
            (reverse result_list))))
    
    (make-squqre-list 10)
    

    (1 4 9 16 25 36 49 64 81 100)

    【讨论】:

    • 对于内部函数,“定义”更好还是“让”更好? (参见上面答案中的“define(helper..”函数,这与您的 fn 非常相似)。
    • 我更喜欢用 let 来定义一个内部函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-10
    相关资源
    最近更新 更多