执行函数,直到它返回nil,将其值收集到列表中

时间:2011-06-28 17:33:09

标签: lisp elisp common-lisp

我从XKCD's Hofstadter comic得到了这个想法;什么是在(任何)Lisp方言中创建条件循环的最佳方法,该方法执行函数直到它返回NIL,此时它将返回的值收集到列表中。

对于那些没有看过这个笑话的人来说,道格拉斯·霍夫施塔特的“八字”自传只包含六个字:“我是如此元,甚至这个缩写”包含笑话的延续:(有些奇怪meta-paraprosdokian?)“是元” - 笑话是自传实际上是“我是如此元,甚至这个缩写是元”。但为什么不深入呢?

假设从字符串创建首字母缩略词并将其拆分为单词的首字母缩略词函数META,如果该字符串只包含一个单词,则返回NIL

(meta "I'm So Meta, Even This Acronym") ⇒ "Is Meta"
(meta (meta "I'm So Meta, Even This Acronym")) ⇒ "Im"
(meta (meta (meta "I'm So Meta, Even This Acronym"))) ⇒ NIL

(meta "GNU is Not UNIX") ⇒ "GNU"
(meta (meta "GNU is Not UNIX")) ⇒ NIL

现在我正在寻找如何实现一个功能:

(so-function #'meta "I'm So Meta, Even This Acronym") 
⇒ ("I'm So Meta, Even This Acronym" "Is Meta" "Im")
(so-function #'meta "GNU is Not Unix")
⇒ ("GNU is Not Unix" "GNU")

这样做的最佳方式是什么?

2 个答案:

答案 0 :(得分:3)

这很容易。我不想写一个解决方案,所以相反我会 - 但它将是糟糕的elisp版本,如果你能够坚持下去,可能会导致意想不到的启示:

(defun so-function (f str)
  (let (x '())
    (while str (setq x (cons str x)) (setq str (funcall f str)))
    (reverse x)))

要尝试这一点,你需要meta,但我不知道你如何决定把空格放在哪里,所以相反我会假装它:

(defun meta (x)
  (cadr (assoc x '(("I'm So Meta, Even This Acronym" "Is Meta")
                   ("Is Meta" "Im")
                   ("GNU is Not UNIX" "GNU")))))

这使您想要的代码工作。至于启蒙 - 尝试写它而不是你想要的,so-function将是一个更高阶的函数 - 一个将这样工作:

(funcall (so-function #'meta) "GNU is Not UNIX")

或者,在Scheme中:

((so-function meta) "GNU is Not UNIX")

这里的一个重要提示是你不能用普通的elisp(至少不是没有来自cl库的技巧)。要获得完全的功劳,请避免突变 - 这将导致您在Scheme中编写它的自然方式,甚至可能看起来比setq版本更具可读性。

答案 1 :(得分:1)

把它扔在一起似乎有效:

(defun collect-until-null (function initial-value)
  "Collects INITIAL-VALUE and the results of repeatedly applying FUNCTION to
   INITIAL-VALUE into a list.  When the result is NIL, iteration stops."
  (if initial-value
      (cons initial-value
            (collect-until-null function (funcall function initial-value)))))

使用meta Eli Barzilay稍微修改过的版本

(defun meta (x)
  (cadr (assoc x
               '(("I'm So Meta, Even This Acronym" "Is Meta")
                 ("Is Meta" "Im")
                 ("GNU is Not UNIX" "GNU"))
               :test #'equal))) ;strings with the same characters aren't EQL

我得到你想要的结果。

CL-USER> (collect-until-null #'meta "I'm So Meta, Even This Acronym")
("I'm So Meta, Even This Acronym" "Is Meta" "Im")

CL-USER> (collect-until-null #'meta "GNU is Not UNIX")
("GNU is Not UNIX" "GNU")

编辑:@Rainer Joswig指出,如果给定足够大的序列,collect-until-null将耗尽堆栈。下面是Rainer的迭代版本没有这个问题。

(defun collect-until-null-iter (function initial-value)
  "Collects INITIAL-VALUE and the results of repeatedly applying FUNCTION to
   INITIAL-VALUE into a list.  When the result is NIL, iteration stops."
  (loop for result = initial-value then (funcall function result)
        while result collect result))