Previous: Setcdr, Up: 修改列表


5.6.3 重组列表函数

下面是一些通过修改列表中cons cell的cdr从而“破坏性”地重组列表的函数。我们之所以称这些 函数为“破坏性”地,是因为它们对作为参数传递给它们的列表,重新连接列表中的cons cell,并返回修改 后的列表。

关于另外一个修改cons cell的函数,请参考集合与列表.一节中的delq

— Function: nconc &rest lists

此函数返回包含lists中所有元素的列表。不像append(参考see 构造列表.),lists被拷贝,而是将lists中的每个列表最后的cdr修改为指向下一个列表。lists中最后一个列 表不被替换。例如:

          (setq x '(1 2 3))
               ⇒ (1 2 3)
          (nconc x '(4 5))
               ⇒ (1 2 3 4 5)
          x
               ⇒ (1 2 3 4 5)

因为nconc最后一个参数并不被修改,因此使用一个常量列表是合理的,例如'(4 5),就像上面的例子一样。 基于同样的原因,最后一个参数可以不是列表:

          (setq x '(1 2 3))
               ⇒ (1 2 3)
          (nconc x 'z)
               ⇒ (1 2 3 . z)
          x
               ⇒ (1 2 3 . z)

然后,其他参数(除了最后一个参数的所有参数)必须是列表。

一个通常的陷阱是用一个被引用的常量列表做为非最后一个参数传递给nconc。如果你这样做,你的程序每次执行时都将变化! 下面演示了将发生什么:

          (defun add-foo (x)            ; 我们使用这个函数
            (nconc '(foo) x))           ; foo插入至其参数头部。
          
          (symbol-function 'add-foo)
               ⇒ (lambda (x) (nconc (quote (foo)) x))
          
          (setq xx (add-foo '(1 2)))    ; 看起来工作正常。
               ⇒ (foo 1 2)
          (setq xy (add-foo '(3 4)))    ; 发生了什么?
               ⇒ (foo 1 2 3 4)
          (eq xx xy)
               ⇒ t
          
          (symbol-function 'add-foo)
               ⇒ (lambda (x) (nconc (quote (foo 1 2 3 4) x)))
— Function: nreverse list

此函数将反转list中元素的顺序。不像revertnreverse通过反转构造列表的cons cell的cdr来 修改其参数。作为list的最后一个cons cell将变成新列表的第一个元素。

例如:

          (setq x '(a b c))
               ⇒ (a b c)
          x
               ⇒ (a b c)
          (nreverse x)
               ⇒ (c b a)
          ;; 第一个cons cell变成了最后一个。
          x
               ⇒ (a)

为避免混淆,我们通常将nreverse的结构存回保存原列表的变量里。

          (setq x (nreverse x))

下面是对我们最喜欢的例子(a b c)执行nreverse的图示:

          原始列表头:                       反转列表:
           -------------        -------------        ------------
          | car  | cdr  |      | car  | cdr  |      | car | cdr  |
          |   a  |  nil |<--   |   b  |   o  |<--   |   c |   o  |
          |      |      |   |  |      |   |  |   |  |     |   |  |
           -------------    |   --------- | -    |   -------- | -
                            |             |      |            |
                             -------------        ------------
— Function: sort list predicate

此函数稳定地对list进行排序,尽管是破坏性地,它返回排序后列表。它使用predicate进行排序。 稳定的列排序指的那些具有相同的比较键值的元素将在排序前后维持他们的相对顺序。当根据不同的标准对元素进行 连续的排序时,稳定性很重要。

参数predicate必须是一个接受两个参数的函数。以两个来自list中的元素做为参数被调用。 要进行升序排序,predicate必须在第一个参数“小于”第二个参数时返回非nil,否则返回 nil

函数predicate必须对任意的参数对都给出可靠的结果,至少在一次sort调用期间。它必须是反对称的, 也即如果a小于b,则b不能小于a。它必须是传递的,也即如果a小于b,并 且b小于c,那么a必须小于c。如果使用一个不满足这些要求的函数,那么排序结果将是不可预知的。

sort的破坏性特质是指它以修改cons cell的cdr来重排构成列表的cons cell。非破坏性的排序将按其顺序创建 新的cons cell来存储元素。如果你期望避免破坏原始列表而是创建一个排序后的拷贝的话,先用copy-sequence来拷贝 然后再进行排序。

排序并不改变list中的cons cell的car;在list中原来在car中持有a的cons cell将在 排序后仍在car中持有a,但因cdr的修改它将出现在列表的另外位置上。例如:

          (setq nums '(1 3 2 6 5 4 0))
               ⇒ (1 3 2 6 5 4 0)
          (sort nums '<)
               ⇒ (0 1 2 3 4 5 6)
          nums
               ⇒ (1 2 3 4 5 6)

警告: 注意在列表中将不在包含0;它仍在原来的cons cell中,但它不在是列表中的第一个了。不要假定原来持有参数 的变量将持有排序后的整个列表!你可以将sort的结构保存起来并使用它。大多数情况下我们将结果存回持有原来列表的 变量中。

          (setq nums (sort nums '<))

关于进行排序的更多函数,请参考See Sorting.一节。 对于sort的一个有用的例子,请参考Accessing Documentation一节中的documentation.。