2015-02-22 4 views
3

Я пытаюсь написать функцию, которая принимает список определяемых пользователем объектов с именем nodes для создания связей между ними. Каждый объект node имеет слот для своего уникального номера ('num') и слот для списка чисел, которые выступают в качестве ребер между узлами («ребра»). +max-edges+ - это целое число, которое определяет, сколько раз будет предпринято парное сопряжение, а +max-rooms+ - количество узлов в списке узлов, передаваемых в функцию (и всегда < 50).Общая функция функции setstring объекта Lisp

Вот две версии функции, которая пытается решить эту проблему:

(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 «D дважды, что, я думаю, может быть проблемой производительности.

(connect-nodes%) пытается решить двойной поиск путем привязки местоположения в лексическом пространстве, но на самом деле не изменяет аргумент списка узлов на месте. Изменения не производятся, поскольку каждое место в привязке let (begin-node-lst и end-node-lst) является обязательным только лексически и выходит за рамки после setf s.

Поэтому я прошу разъяснить несколько пунктов:

  • ли мое понимание того, почему вторая функция не может изменить список аргументов правильно?
  • Является ли первая функция стилистически правильной? Есть ли лучший способ написать эту функцию, которая не ищет значение слота дважды для setf или это приемлемо для небольших списков длины?

Я запускаю slime + emacs + sbcl, если это влияет на ваш ответ.

EDIT: Вот что я в конечном итоге происходит с для списка-версии 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

Общий совет я получаю: 1) использовать макрос 'PUSH', который выполняет шаблон' SETF', определяющий значение элемента, связанного с списком в месте. 2) Кажется, расточительно хранить цифровые ключи поиска для моих объектов, когда я мог просто добавить ссылки на них непосредственно в слоте кромок. – qmoog

ответ

4

Ваше понимание о второй функции является правильным.

Возможно, вы захотите сохранить фактические узлы в слоте вместо номеров узлов. Затем вместо привязки локальных переменных к списку узлов внутри двух узлов, которые вы хотите подключить, вы можете привязать их к самим узлам, что также будет выглядеть лучше, чем повторные вызовы 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))) 
+0

Я даже не рассматривал возможность хранения ссылок на другие узлы, а не только числа смещения. Это, конечно, очистит какой-то другой код позже, когда я пройду узлы. Мне нужно прототип этого и посмотреть, понимаю ли я, как это будет работать, не создавая дополнительных копий существующих объектов узла. – qmoog

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 есть идея место.Многие макросы побочных эффектов работают с местами: 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) 
+0

Благодарим вас за подробное объяснение. В качестве последующего вопроса к вашим общим советам, является причина избежать «NTH» и списков, поскольку он требует повторения списка, пока вы не достигнете указанного назначения в сравнении с векторной коллекцией, которая может выполнять простой (и менее дорогостоящий) целочисленный индекс Погляди? – qmoog

Смежные вопросы