【问题标题】:Appending to the result of a "loop-collect" in Lisp附加到 Lisp 中“循环收集”的结果
【发布时间】:2015-04-26 23:13:43
【问题描述】:

假设我运行以下命令

(loop for i to 4 collect i)

然后我得到一个列表(0 1 2 3 4)。现在,如果我想在结果中附加一些东西,我可以在它的last 元素上使用rplacd,但是由于Lisp 列表是链表,所以效率不是很高。这里的列表非常小,但这只是一个示例。

但是,由于循环工具以升序返回列表,它必须跟踪指向最后一个元素的指针,并使用rplacd 或等效的东西更新结果。 macroexpand-all 表明这是 CCL 所做的,可能还有其他 lisp。

问题:有没有办法在finally 子句中使用这个“指针”?它允许人们在结果中附加一些东西,这有时很有用。

当然,编写指针机制很容易,但它不是那么好。例如,以下内容会将列表 e 附加到列表 (0 1 ... n)

(defun foo (n e)
    (let* ((a (list nil)) (tail a))
        (loop for i to n
              do (rplacd tail (setf tail (list i)))
              finally (rplacd tail (setf tail e))
              (return (cdr a)))))

【问题讨论】:

    标签: loops lisp common-lisp


    【解决方案1】:

    每次迭代的额外比较和一个额外的迭代给你这个:

    CL-USER 2 > (defun foo (n e &aux (z (1+ n)))
                  (loop for i to z
                        unless (= i z)
                          collect i
                        else
                          nconc e))
    FOO
    
    CL-USER 3 > (foo 4 '(f o o))
    (0 1 2 3 4 F O O)
    

    【讨论】:

    • 我肯定还有很多东西要学,尤其是在 loop 上:-)
    • @Jean-ClaudeArbaut:由于您自己发现LOOP 实现会跟踪最后一个缺点并验证了它的宏扩展代码,所以看起来您的开端良好。 ;-)
    • 我比我更喜欢这个。这是一个更简洁的解决方案(只需添加一个额外的迭代并做一些特别的事情),并且你会得到列表的隐式返回。这是我会接受的答案。
    • 这和最后一个子句是append e而不是nconc e的循环有什么区别?
    • @ThrowawayAccount3Million:它将使用列表e 的副本,这不是必需的,因为我们不会更改它。
    【解决方案2】:

    问题:有没有办法在 finally 子句中使用这个“指针”?这将允许一个人在结果中附加一些东西,即 有时很有用。

    我认为答案是“不”。 finally 子句的语法如下:

    initial-final::= initially compound-form+ | finally compound-form+ 
    

    当然,您可以将 收集到 某个特定变量中,然后将其附加或 nconc:

    CL-USER> (loop for i from 1 to 5
                  collect i into ns
                  finally (return (nconc ns (list 'a 'b 'c))))
    ;=> (1 2 3 4 5 A B C)
    

    不过,这确实涉及对结果的额外遍历,这可能是不可取的。 (我认为这是您要避免的。)

    另一种选择是使用 nconc 而不是 collect 建立列表。如果您使用collect,那么您一次只获得一个值,然后收集它。你可以把那个值放到一个列表中,然后用nconc“收集”它。如果你将那个元素列表保存到循环中的一个变量中,你可以引用它,它几乎是一个尾指针。例如:

    CL-USER> (loop for i from 1 to 5
                for ilist = (list i)
                nconc ilist into ns
                finally (progn 
                          (nconc ilist '(a b c))  ; ***
                          (return ns)))
    ;=> (1 2 3 4 5 A B C)
    

    在标有***的行中,对nconc的调用只需遍历ilist的最终值,即(5)强>。这是一个非常快速的 nconc 调用。

    【讨论】:

    • 好主意,使用 nconc。这是一个更清洁的解决方案。谢谢。
    • 如果在 when 之后有条件地发生 nconcing,它似乎不会很好地工作。仍然可以将 nconc (setf ilist (list i)) 放入 ns,但如果 when 子句始终为 false,则 ns 为最终为零。然后它需要一个后续的 (if (null ns) ...)。我说的对吗?
    • @jean 我认为 Rainer 的回答更好。请考虑不接受这个并接受他的。
    猜你喜欢
    • 2017-05-12
    • 2014-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多