请批评我的Lisp

时间:2010-10-09 06:56:29

标签: lisp common-lisp

我创造了一个很好的小例程:

(defun unzip (seq)
  "Takes an even-length list and breaks it apart by evens/odd index"
  (let ((oddresult '())
    (evenresult '()))
    (loop for n from 0 to (- (length seq) 1) do
      (if (oddp n)
          (push (nth n seq) oddresult)
        (push (nth n seq) evenresult)))
    (list (reverse oddresult) (reverse evenresult))))

使用它:

CL-USER> (unzip '(1 2 3 4 5 6))
((2 4 6) (1 3 5))

但是,我敏锐地意识到我能够用任何语言编写> botch C ++,并希望对我的unzip进行一些分析,以获得良好的Common Lisp风格。

10 个答案:

答案 0 :(得分:14)

首先请注意,'()()是等效的,因为空列表是自我评估且等于NIL,并且在任何情况下您都不需要LET中的那些因为`{let(variable)...)语法隐含了NIL,这就是为什么在给出起始值时需要围绕每个绑定括号的原因。

对于这种情况,并非需要使用LET。更广泛地使用LOOP功能,此功能可写为:

(defun unzip (seq)
  "Takes an even-length list and breaks it apart by evens/odd index"
  (loop for n from 0
        for element in seq
        if (oddp n)
          collect element into oddresult
        else
          collect element into evenresult
        finally (return (list oddresult evenresult))))

我个人更喜欢iterate进行大多数迭代,使用它可以写成:

(defun unzip (seq)
  "Takes an even-length list and breaks it apart by evens/odd index"
  (iter (for element in seq)
        (for n from 0)
        (if (oddp n)
            (collect element into oddresult)
            (collect element into evenresult))
        (finally (return (list oddresult evenresult)))))

甚至:

(defun unzip (seq)
  "Takes an even-length list and breaks it apart by evens/odd index"
  (iter (generate element in seq)
        (collect (next element) into evenresult)
        (collect (next element) into oddresult)
        (finally (return (list oddresult evenresult)))))

编辑:附加说明:名称unzip通常表示略有不同的功能。参数名称应该是list,因为seq会建议该函数也采用向量。尽管可以使功能在通用序列上运行,但通常不推荐使用,因为列表和向量具有不同的性能特征。特别是,通过NTH的随机访问是列表的线性时间,这意味着您几乎不应该使用它。即使时间成本微不足道,它通常也表明您应该使用不同的数据结构。

答案 1 :(得分:8)

函数(nth n list)需要遍历列表以访问第n个元素,一个O(n)操作,并在实现中调用O(n)次,使整个过程为O(n ^ 2) 。您可以在O(n)中完成相同的操作:

(defun unzip (list)
  (loop for (x y) on list by #'cddr
        collect x into a
        collect y into b
        finally (return (list a b))))

答案 2 :(得分:3)

让我们来看看你的代码:

(defun unzip (seq)
  "Takes an even-length list and breaks it apart by evens/odd index"

 ; you name the variable seq (sequence), but the documentation mentions
 ; only lists. Sequence is in CL an abstraction over lists and vectors. 

 ; also nothing in the code really says that the list needs to be even-length  

  (let ((oddresult  '())
        (evenresult '()))
    (loop for n from 0 to (- (length seq) 1) do  ; you can iterate BELOW         
      (if (oddp n)
          (push (nth n seq) oddresult)    ; <- NTH is inefficient for lists
        (push (nth n seq) evenresult)))   ; <- NTH is inefficient for lists
    (list (reverse oddresult)             ; <- return multiple values
          (reverse evenresult))))

以下是列表的版本:

(defun unzip (list &aux oddresult evenresult (odd t))
  "Takes a list and breaks it apart by evens/odd index"
  (dolist (element list (values (reverse oddresult) (reverse evenresult)))
    (if (setf odd (not odd))
        (push element oddresult)
      (push element evenresult))))

示例(请注意,基于CL的索引为零,我更改了示例以减少其混淆):

CL-USER 10 > (unzip '(0 1 2 3 4 5))
(1 3 5)
(0 2 4)

这是一个需要偶数长度列表并使用LOOP的版本:

(defun unzip (list)
  "Takes an even-length list and breaks it apart by evens/odd index"
  (loop for (even odd) on list by #'cddr
        collect even into evenresult
        collect odd  into oddresult
        finally (return (values oddresult evenresult))))

答案 3 :(得分:2)

我能看到的唯一不好的是nth。您应该通过列表进行迭代,因为nth可能需要迭代列表。忘记数组和数组索引:)

不幸的是,我不知道在Lisp中如何做到这一点,但在Scheme中,你只需使用一个名为let的人。

答案 4 :(得分:1)

我会用LOOP或递归函数解决这个问题。

对于基于LOOP的解决方案,@ Ramarren几乎已经确定了它。

如果知道偶数索引的内容和奇怪的内容并不重要,我会使用类似的东西:

(defun unzip (list &optional acc1 acc2)
   (if seq
       (unzip (cdr list) acc2 (cons (car list) acc1)))
       (list (nreverse acc1) (nreverse acc2))))

如果有必要知道来自奇数或偶数索引的内容,我会将UNZIP包装在一个帮助函数中,该函数比较列表的CAR和返回值的CAAR。如果它们相同,则将其传回,否则交换结果列表中的两个列表。

答案 5 :(得分:0)

  • 您应该使用for-eachcarcdr来迭代列表中的元素,而不是循环遍历列表的元素。
  • 我不会称之为unzip,因为它不是zip的反面。

编辑:更具体地说,我希望zip获取一对列表并返回一对列表,因此unzip应该采用一对列表并返回一对列表。我希望它看起来像我发现的这个例子:

(defun unzip (list)
  (let ((a '())
        (b '()))
    (for-each (lambda (i) (push (first i) a) (push (second i) b)) list)
    (values (nreverse a) (nreverse b))))

答案 6 :(得分:0)

递归解决方案:

(defun unzip (seq &optional oddresult evenresult)
  (if (null seq)
      (list (reverse oddresult) (reverse evenresult))
    (if (oddp (car seq))
        (unzip (cdr seq) (cons (car seq) oddresult) evenresult)
      (unzip (cdr seq) oddresult (cons (car seq) evenresult)))))

答案 7 :(得分:0)

Scheme版本,仅适用于目标练习。 :-)(需要SRFI 1的fold功能。)

(define (unzip l)
  (define (iter e v)
    (list (not (car v)) (caddr v) (cons e (cadr v))))
  (define (swap-if p a b)
    (if p (list b a) (list a b)))
  (map reverse
       (apply swap-if (fold iter '(#t () ()) l))))

要更改返回列表的顺序(即首先是偶数索引元素),只需将#t更改为#f

答案 8 :(得分:0)

这是另一种可能的解决方案,使用递归:

(defun unzip (l)
  (labels ((every-other (l) (if l (cons (car l) (every-other (cddr l))))))
    (list (every-other (cdr l)) (every-other l))))

首先,我们每隔一个定义一个辅助函数,它从列表中获取每个其他元素。然后,我们在原始列表上运行此函数,跳过第一个项目,并在原始列表上。

缺点:由于递归,它可能会堆叠大型列表的溢出。如果它必须处理大型列表,请参阅@Vatine以获得尾递归解决方案。

答案 9 :(得分:0)

这是一个对奇数长度输入不敏感的循环版本,它只是@ huaiyuan的答案的一个小变化,添加了when子句:

(defun unzip (list)
  (loop for (x y) on list by #'cddr
     collect x into a
     when y
     collect y into b
     finally (return (list a b))))