【问题标题】:Critique my Lisp, please [closed]请批评我的 Lisp [关闭]
【发布时间】:2010-10-09 06:56:29
【问题描述】:

我创建了一个不错的小程序:

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

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

【问题讨论】:

  • 取笑某人的语言障碍是不礼貌的。
  • @Paul Nathan 结果应该是((1 3 5) (2 4 6)),对吧?
  • @ring0:在任何编程语言中,都有一个成语和一个正确的风格,并且有兴趣学习和讨论。甚至还有一个新词——“pythonic”。
  • 括号太多。
  • @Frank:我认为戴夫是开玩笑的。

标签: lisp common-lisp


【解决方案1】:

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

在这种情况下,不必使用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 的随机访问对于列表来说是线性时间,这意味着您几乎不应该使用它。即使时间成本微不足道,它通常也表明您应该使用不同的数据结构。

【讨论】:

  • 漂亮。提及 nth 不好,你会得到 +1
【解决方案2】:

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

(defun解压缩(列表) (在 #'cddr 的列表中循环(x y) 将 x 收集到一个 将 y 收集到 b 中 最后(返回(列表 a b))))

【讨论】:

    【解决方案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))))
    

    【讨论】:

      【解决方案4】:

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

      不幸的是,我不知道如何在 Lisp 中执行此操作,但在 Scheme 中,您只需使用命名为 let

      【讨论】:

        【解决方案5】:

        我会使用 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。如果它们相同,则将其传回,否则交换结果列表中的两个列表。

        【讨论】:

        • 使用递归不是一个好主意,因为需要尾递归优化。这不是普通标准 CL 的一部分,并且在支持它的每个实现中都以不同的方式调用。
        【解决方案6】:

        方案版本,仅用于目标练习。 :-)(需要 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

        【讨论】:

          【解决方案7】:
          • 您应该使用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))))
          

          【讨论】:

          • 给定一个plist,它zip相反,不是吗?但在一般情况下,不会。
          【解决方案8】:

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

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

          首先,我们定义了一个辅助函数every-other,它从列表中获取所有其他元素。然后,我们在跳过第一项的原始列表和原始列表上运行此函数。

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

          【讨论】:

            【解决方案9】:

            这是一个对奇数长度输入不敏感的循环版本,它只是 @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))))
            

            【讨论】:

              【解决方案10】:

              递归解决方案:

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

              【讨论】:

              • 你应该拆分奇数或偶数索引,而不是奇数或偶数元素。
              猜你喜欢
              • 1970-01-01
              • 2010-10-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多