我正在尝试编写一个函数,该函数接受一个名为nodes
的用户定义对象列表,以生成它们之间的连接。每个node
对象都有一个插槽用于其唯一编号(' num')和一个插槽,用于作为节点之间边缘的数字列表('边缘')。 +max-edges+
是一个整数,用于定义尝试边缘配对的次数,+max-rooms+
是节点列表中传递给函数的节点数(并且始终<50)。
以下是试图解决此问题的函数的两个版本:
(defun connect-nodes (node-list)
"Given a NODE-LIST, repeats for +MAX-EDGES+ amount of times
to alter NODE-LIST in-place to connect randomly generated edges to nodes."
(loop repeat +max-edges+
do (let ((begin-node (random +max-rooms+))
(end-node (random +max-rooms+)))
(when (not (= begin-node end-node))
(setf (slot-value (nth begin-node node-list) 'edges)
(cons end-node
(slot-value (nth begin-node node-list) 'edges)))
(setf (slot-value (nth end-node node-list) 'edges)
(cons begin-node
(slot-value (nth end-node node-list) 'edges))))))))
(defun connect-nodes% (node-list)
"Given a NODE-LIST, repeats for +MAX-EDGES+ amount of times
to alter NODE-LIST in-place to connect randomly generated edges to nodes."
(loop repeat +max-edges+
do (let ((begin-node (random +max-rooms+))
(end-node (random +max-rooms+)))
(when (not (= begin-node end-node))
(let ((begin-node-lst (slot-value (nth begin-node node-list) 'edges))
(end-node-lst (slot-value (nth end-node node-list) 'edges)))
(setf begin-node-lst (cons end-node begin-node-lst))
(setf end-node-lst (cons begin-node end-node-lst)))))))
(connect-nodes)
按预期工作,但最后两行似乎风格较长,并且查找对象的插槽值为setf
两次,我认为这可能是性能问题。
(connect-nodes%)
尝试通过在词法范围内绑定位置来解决双重查找,但实际上并未实际更改node-list参数。没有进行任何更改,因为let
绑定(begin-node-lst
和end-node-lst
)中的每个位置仅在词汇上绑定,并且在setf
s之后超出范围。
所以我要求澄清几点:
setf
查找两次槽值,或者这对于小长度列表是否可接受?如果这会影响你的答案,我正在运行史莱姆+ emacs + sbcl。
修改
根据我的问题答案的建议,这就是我最终选择connect-nodes
函数的列表版本的内容。我正在开发一个适用于矢量的版本,因此这个版本的connect-nodes
是泛型函数的一个方法:
(defmethod connect-nodes ((node-list list))
"Given a NODE-LIST, repeats for +MAX-EDGES+ amount of times
to alter NODE-LIST in-place to connect randomly generated edges to nodes."
(loop repeat +max-edges+
do (let ((begin-node (random +max-rooms+))
(end-node (random +max-rooms+)))
(when (not (= begin-node end-node))
(push end-node (edges (nth begin-node node-list)))
(push begin-node (edges (nth end-node node-list)))))))
答案 0 :(得分:4)
您对第二个功能的理解是正确的。
您可能希望将实际节点存储在edges
插槽中而不是节点编号中。然后,不是将局部变量绑定到要连接的两个节点内的节点列表,而是将它们绑定到节点本身,这看起来也比内部nth
的重复调用更好。 setf
表格。然后,当您访问edges
时,您也可以直接使用节点进行操作,而不必执行额外的查找。
为了改善第一个功能的风格,我建议两件事:
push
代替(setf ... (cons thing ...))
slot-value
是一个访问者,因此,它可以用作一个地方。 setf
是改变场所价值的一种方法,但Common Lisp定义了场所的其他操作。您在此处使用的模式在宏push
中实现。通过使用它,您可以显着简化表达式:
(push end-node (slot-value (nth begin-node node-list) 'edges))
slot-value
slot-value
应该很少使用,并且作为一种低级机制,因为它比使用命名访问器更冗长,更灵活。 slot-value
还将访问的重要部分(插槽的名称)放在表达式的末尾,这通常会使代码难以阅读。在您的情况下,我会在类定义中命名访问者edges
:
(edges :initform nil :accessor edges)
这会让你的第一个版本更具可读性:
(push end-node (edges (nth begin-node node-list)))
答案 1 :(得分:3)
而不是:
(setf (slot-value (nth begin-node node-list) 'edges)
(cons end-node (slot-value (nth begin-node node-list) 'edges)))
你可以写:
(push end-node (slot-value (nth begin-node node-list) 'edges))
为什么以下内容无法按预期工作?
(let ((begin-node-lst (slot-value (nth begin-node node-list) 'edges))
(end-node-lst (slot-value (nth end-node node-list) 'edges)))
(setf begin-node-lst (cons end-node begin-node-lst))
(setf end-node-lst (cons begin-node end-node-lst)))
您写道:尝试通过绑定位置来解决双重查找。
这不起作用。您可以绑定位置。您只能绑定值。 LET
将表单的值绑定到变量。
在Common Lisp中有一个 place 的想法。许多副作用宏适用于地点:SETF
和PUSH
是示例。地方只是访问代码的来源,而不是真正的第一类对象
地方的例子:
foo
作为变量(aref foo 10)
作为数组访问(slot-value object 'foo)
作为广告位访问(slot-value (find-object *somewhere* 'foo) 'bar)
作为广告位访问... 像SETF
这样的宏根据访问表单的来源,在宏展开时找到了生成设置表单的形式。它无法查看绑定等绑定形式的内容。
在这种情况下,通常会从数据结构中检索对象(通常是CLOS对象或结构),保留对对象的引用,然后使用SLOT-VALUE
或WITH-SLOTS
更改槽值。或者使用访问者。
(setf (slot-value person 'name) "Eva Lu Ator")
(setf (slot-value person 'group) :development)
将是
(with-slots (name group) person
(setf name "Eva Lu Ator"
group :development))
一般建议:
在你的函数中还要注意node
是什么的混淆。它是node
类型的对象还是数字?如果是数字,我会将变量命名为node-number
。
避免使用NTH
和列表。如果您需要随机访问,请使用向量。
直接使用节点对象(而不是那些数字)或使用符号:node-123
并将节点符号链接到某个注册表中的节点对象。您可能只想在某些情况下使用数字......
我会写这样的代码:
(defun connect-nodes (node-vector)
"Given a NODE-VECTOR, repeats for +MAX-EDGES+ amount of times to connect
nodes via randomly generated edges."
(loop repeat +max-edges+
for begin-node-number = (random +max-rooms+) and
end-node-number = (random +max-rooms+)
when (/= begin-node-number end-node-number) do
(let ((begin-node (aref node-vector begin-node-number))
(end-node (aref node-vector begin-node-number)))
(push end-node (slot-value begin-node 'edges))
(push begin-node (slot-value end-node 'edges))))
node-vector)