Macros 如何编写';破坏性';lisp中的dolist宏

Macros 如何编写';破坏性';lisp中的dolist宏,macros,lisp,Macros,Lisp,我试图以最直观的方式在Common Lisp(也适用于Emacs Lisp)中编写一个简单的冒泡排序: (defun bubble-sort-long (list) (labels ((rec (list acc) (if (null list) (nreverse acc) (progn (dolist (x (cdr list))

我试图以最直观的方式在Common Lisp(也适用于Emacs Lisp)中编写一个简单的冒泡排序:

(defun bubble-sort-long (list)
  (labels ((rec (list acc)
                (if (null list) (nreverse acc)
                    (progn
                      (dolist (x (cdr list))
                        (if (< x (car list))
                            (rotatef x (car list))))
                      (rec (cdr list) (push (car list) acc))))))
           (rec list nil)))
我想最直接的解决方法是定义一个“破坏性”的dolist宏,它将一个x直接指定给列表中的迭代元素。类似于ndolist的东西,以便我可以进行以下工作:

(setf a '(1 2 3))
(ndolist (x a)
  (setf x (+ 1 x)))  
将产生:(2 3 4)

此外,如果您能提供有关使用lisp进行气泡排序的更直观的想法,请给出提示

在ruby中,类似的算法类似于:

class Array
  def bubble_sort
    self.each_with_index do |a,j|
      i=j+1
      while i<self.length
        if (self[j]>self[i])
          self[j],self[i]=self[i],self[j]
        end
        i=i+1
      end
    end
  end
类数组
def气泡_排序
self.each_与_索引do|a,j|
i=j+1
而我自己[我])
self[j],self[i]=self[i],self[j]
结束
i=i+1
结束
结束
结束

有趣的是,我仍然必须使用“并行赋值”来交换值。Ruby不支持(鼓励)在C/C++风格中通过引用使用临时变量进行交换

这方面有一些问题。这也不是那么容易

我会给你一些提示:

  • 变量指向一个值,而不是一个位置。修改一个变量永远不会改变另一个位置或另一个变量

  • 您可以像这样交换列表的第一项和第二项
    (rotatef(第一项列表)(第二项列表))

  • 永远不要更改代码中的文字列表-这是一个常量。如果以破坏性方式更改列表,请使用生成的列表(例如,通过复制列表或列表或…)创建的列表

  • 我们可以想到两种泡沫排序:一种是破坏性的,另一种不是

  • 我还将用循环替换尾部递归函数。在Scheme中,您将使用尾部递归函数,而在常见的Lisp中,通常不会


    • 这方面存在一些问题。这也不是那么容易

      我会给你一些提示:

      • 变量指向一个值,而不是一个位置。修改一个变量永远不会改变另一个位置或另一个变量

      • 您可以像这样交换列表的第一项和第二项
        (rotatef(第一项列表)(第二项列表))

      • 永远不要更改代码中的文字列表-这是一个常量。如果以破坏性方式更改列表,请使用生成的列表(例如,通过复制列表或列表或…)创建的列表

      • 我们可以想到两种泡沫排序:一种是破坏性的,另一种不是

      • 我还将用循环替换尾部递归函数。在Scheme中,您将使用尾部递归函数,而在常见的Lisp中,通常不会


      关于您最初的问题:因此您需要一个
      dolist
      变量,它不仅将迭代变量绑定到提供的名称上,而是将其作为一个位置。应该可以通过将该宏扩展为
      符号macrolet
      上的循环来实现,该循环将为每个迭代步骤将名称扩展为不同的位置形式

      关于冒泡排序实现:无论如何,冒泡排序效率非常低,所以麻烦使用尾部递归是一种能源浪费。如果您关心效率的话,您可以选择一个更好的算法,冒泡排序只是为了演示,所以您应该尽可能地使用伪代码实现。下面是一个lisp实现:

      (defun bubble-sort (seq)
        (loop for n from (length seq) downto 2 and swapped = nil
           do (dotimes (i (1- n))
                (when (> (elt seq i) (elt seq (1+ i)))
                  (rotatef (elt seq i) (elt seq (1+ i)))
                  (setf swapped t)))
           while swapped))
      
      这个实现的另一个好处是它使用了序列协议,所以它可以同时处理列表和向量(一维数组)

      编辑:正如在评论中所讨论的,在列表上使用序列协议的随机访问操作效率非常低,因此这里有一个版本可以避免这些操作(其缺点是它不再适用于向量):


      此版本返回排序后的列表,不会对原始列表进行变异。

      关于原始问题:因此您需要一个
      dolist
      变量,它不仅将迭代变量绑定到提供的名称,而是将其作为一个位置。应该可以通过将该宏扩展为
      符号macrolet
      上的循环来实现,该循环将为每个迭代步骤将名称扩展为不同的位置形式

      关于冒泡排序实现:无论如何,冒泡排序效率非常低,所以麻烦使用尾部递归是一种能源浪费。如果您关心效率的话,您可以选择一个更好的算法,冒泡排序只是为了演示,所以您应该尽可能地使用伪代码实现。下面是一个lisp实现:

      (defun bubble-sort (seq)
        (loop for n from (length seq) downto 2 and swapped = nil
           do (dotimes (i (1- n))
                (when (> (elt seq i) (elt seq (1+ i)))
                  (rotatef (elt seq i) (elt seq (1+ i)))
                  (setf swapped t)))
           while swapped))
      
      这个实现的另一个好处是它使用了序列协议,所以它可以同时处理列表和向量(一维数组)

      编辑:正如在评论中所讨论的,在列表上使用序列协议的随机访问操作效率非常低,因此这里有一个版本可以避免这些操作(其缺点是它不再适用于向量):

      此版本返回排序后的列表,不会对原始列表进行变异。

      最后我发现:

      (defmacro dolist* ((iterator list &optional return-value) &body body)
        "Like DOLIST but destructuring-binds the elements of LIST.
      
      If ITERATOR is a symbol then dolist* is just like dolist EXCEPT
      that it creates a fresh binding."
        (if (listp iterator)
            (let ((i (gensym "DOLIST*-I-")))
              `(dolist (,i ,list ,return-value)
                 (destructuring-bind ,iterator ,i
                   ,@body)))
            `(dolist (,iterator ,list ,return-value)
               (let ((,iterator ,iterator))
                 ,@body))))
      
      最后我发现:

      (defmacro dolist* ((iterator list &optional return-value) &body body)
        "Like DOLIST but destructuring-binds the elements of LIST.
      
      If ITERATOR is a symbol then dolist* is just like dolist EXCEPT
      that it creates a fresh binding."
        (if (listp iterator)
            (let ((i (gensym "DOLIST*-I-")))
              `(dolist (,i ,list ,return-value)
                 (destructuring-bind ,iterator ,i
                   ,@body)))
            `(dolist (,iterator ,list ,return-value)
               (let ((,iterator ,iterator))
                 ,@body))))
      

      谢谢信息量大。我正在搜索有关symbol macrolet的更多信息。我随机选择了bubble算法来练习lisp-fu,以供学习之用……虽然它可以处理列表,但处理列表的效率不是很高:你一直在用ELT浏览列表。例如,您在>中调用一个ELT到i,另一个ELT到i+1。如果你在列表的末尾,这将从一开始遍历列表两次。然后使用ROTATEF,你也在做同样的事情。Common Lisp中的列表是链接的单元格,随机访问不是它们的强项。@雷纳:是的,但如果我想要一个对列表有效的算法,我不会使用冒泡排序。@Rörd:您的示例没有教授以正确方式实现更好算法所需的技术。@Rainer:新示例如何