Macros 如何编写';破坏性';lisp中的dolist宏
我试图以最直观的方式在Common Lisp(也适用于Emacs Lisp)中编写一个简单的冒泡排序: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))
(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:新示例如何