在Scheme中向列表中添加元素

8

以下是我的代码,它以一个列表元素(carVal)和一个参数初始化为空的列表作为参数。我想将该元素附加到列表中,但不起作用。

(define populateValues
   (lambda (carVal currVal)
      (append currVal(list carVal ))
       (display currVal)))

显示器一直显示空列表()。有人能帮我理解为什么吗?
5个回答

26

对于大多数问题,原语append! 可以解决。正如前面所提到的,Scheme 不赞成变异,虽然这是可能的,但通常要避免使用。因此,所有会导致变异的过程都在结尾加上一个(称为bang)。

set! 并不会改变数据,它只更改了环境,使变量指向另一个东西,原始数据保持不变。

在 Scheme 中突变数据相当麻烦,但是,我会给你展示我自己实现的append!,让你看看它是如何完成的:

(define (append! lst . lsts)
  (if (not (null? lsts))
      (if (null? (cdr lst))
          (begin
            (set-cdr! lst (car lsts))
            (apply append! (car lsts) (cdr lsts)))
          
          (apply append! (cdr lst) lsts))))

请注意使用 set-cdr!,它是一个真正的变异器,只能作用于对,它会在内存中改变数据,不像 `set!'。如果一个对被传递到一个函数中,并用 set-cdr! 或 set-car! 进行突变,则它在程序中的任何地方都被突变了。
这遵守 SRFI append! 规范,该规范指出它应该是可变参数的,而且应该返回未定义的值。
(define l1 (list 1 2 3 4))

(define l2 (list 2 3 4))

(define l3 (list 3 1))

(append! l1 l2 l3)

l1

l2

l3

显示:

(1 2 3 4 2 3 4 3 1)
(2 3 4 3 1)
(3 1)

如您所见,append! 可以使用无限数量的参数,并将它们全部进行变异,但最后一个不会被变异。
然而,Scheme 或许并不是您理想的编程语言。正如之前所说,使用 append! 是非标准的。相反,更推荐使用 append,它不会做出变异操作,并且会返回其值。我将其实现如下:
(define (append . lsts)
  (cond
    ((null? lsts) '())
    ((null? (car lsts)) (apply append (cdr lsts)))
    (else (cons (caar lsts) (apply append (cdar lsts) (cdr lsts))))))


> (append (list 1 2 3) (list 4 5 6) (list 'reasonable 'behavior))
(1 2 3 4 5 6 reasonable behavior)

在没有变异、大量使用递归和不使用排序的情况下,显示更熟悉的Scheme风格。

编辑:如果你只想向列表中添加一些元素,而不是连接两个列表:

(define (extend l . xs)
  (if (null? l) 
      xs
      (cons (car l) (apply extend (cdr l) xs))))

(define (extend! l . xs)
  (if (null? (cdr l))
      (set-cdr! l xs)
      (apply extend! (cdr l) xs)))

(extend '(0 1 2 3) 4 5 6)

(define list1 '(0 1 2 3))

(extend! list1 4 5 6)

list1

它做你所期望的事情


谢谢你的回答。顺便说一下,“granny”和“porn”这两个词,你可能需要更改它们,否则你可能会被踩得很惨 :) - name_masked
3
@darkie15 如果人们因为这些原因想要给答案点不赞成,那也不会让这个答案变得更不“有用”或者“清晰”。此外,你已经得到了你的答案。=)另外,如果其他人想要编辑它,他们也可以这么做。 - Zorf

5
  1. append 创建一个新的列表,它不会修改现有的列表。
  2. 这是因为通常情况下,Scheme(在这种情况下是 Racket)更喜欢函数式风格。
  3. 你可以使用 set! 使它更接近,但即使如此,它也只会修改局部绑定。
  4. 注意,在特别的 Racket 中,列表是不可变的,因此没有任何东西可以改变一个列表。
  5. 此外,即使你能够以这种方式修改列表,这也是一种非常低效的累加长列表的方法,因为你必须反复扫描整个列表。
  6. 最后,如果你在这个层面上遇到问题,我强烈建议阅读 HtDP

我需要实现这个功能。在这种情况下,你有什么建议? - name_masked
你可以使用box,它是指向(可变)值的指针。但是我怀疑你是否真的需要这个功能 - 新手经常认为必须拥有这个功能,因为他们习惯于只能通过改变值来实现操作。 - Eli Barzilay

2

(append foo bar) 返回 连接 foobar 的结果。它不会改变 foo 或者 bar


0

你需要使用set!更新currVal的值。你的例子应该有

(set! currVal (append currVal (list carVal))
(display currVal)

请注意,这将更改函数内部的“currVal”,但在外部不会产生任何可见效果。 - Eli Barzilay

0

你真的需要考虑清楚你所寻找的确切功能是什么。

如果你想要在原地改变一个引用列表,那么你必须做相当于append!的操作(如其他答案中所述)。但这是危险的,因为你可能有其他代码依赖于该列表是不可变的,如果你要这样做,你的过程需要在结尾加上一个!来标记这种危险。

在更函数式的风格中,你想要做的廉价近似是:

(define (populateValues carVal currVal)
 (let ((ll (append currVal (list carVal))))
   (display ll)
   ll))

请注意,它创建了一个新的列表,执行了添加操作,显示了结果,并将新列表作为值返回。如果您无法访问中间值,则这是一种有用的调试技术:绑定到变量,显示或记录它,然后返回它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接