I am attempting to write a function that takes a list of user-defined objects called nodes to generate connections between them. Each node object has a slot for its unique number ('num') and a slot for a list of numbers that act as the edges between nodes ('edges'). +max-edges+ is an integer that defines how many times an edge pairing will be attempted, and +max-rooms+ is the number of nodes within the node-list getting passed into the function (and is always < 50).
Here are two versions of a function that attempts to solve this problem:
(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) works as expected, but the last two lines seem stylistically long and lookup the slot value for the object being setf'd twice, which I imagine could be a performance issue.
(connect-nodes%) attempts to solve the double lookup by binding the location in a lexically-scoped place, but does not actually alter the node-list argument in-place. No changes are made because each location in the let binding (begin-node-lst and end-node-lst) is binding only lexically and going out of scope after both setfs.
So I am asking for clarification on a few points:
- Is my understanding of why the second function fails to alter the argument list correct?
- Is the first function stylistically correct? Is there a better way to write this function that doesn't lookup the slot value twice for
setfor is this acceptable for small length lists?
I am running slime + emacs + sbcl if that factors into your answer.
EDIT:
Here's what I ended up going with for a list-version of a connect-nodes function thanks to the advice from the answers to my question. I am working on a version that works on vectors, hence this version of connect-nodes is a method on a generic function:
(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)))))))
PUSHmacro which performs a pattern ofSETFing the value of an item consed to a list at a place. 2) It appears to be wasteful to store numerical lookup keys for my objects when I could just add references to them directly in the edges slot. - qmoog