Эффективное следование ссылкам в ориентированном графе (Common Lisp) - PullRequest
1 голос
/ 16 апреля 2020

Я хотел бы разработать эффективную стратегию, которая может быстро проверить, существует ли заранее заданный путь в большом полностью связанном ориентированном помеченном графе. Например, начиная с какого-то узла, скажем, узла 0, существует ли путь к другому узлу, скажем, узлу 9, который следует последовательности помеченных ссылок, скажем, узел 0 -> ссылка 3 -> ссылка 1 -> ссылка 4 + -> ссылка 1 -> узел 9, где ссылка + означает одно или несколько повторений метки этой ссылки. График является динамическим c, так что узлы и ссылки будут постоянно добавляться и удаляться. Уникальные метки узлов и ссылок будут строками, основанными на базовой информации semanti c.

Моя первая (самая простая) идея состоит в том, чтобы объединить все помеченные узлы графа и ссылки в отдельном пакете в виде символов. Затем установите таблицу ha sh в качестве значения символа для каждого узла. Таблица ha sh будет содержать ассоциации для этого узла из всех ссылок, исходящих от этого узла, к их соответствующим целевым узлам. Проверка, существует ли следующая ссылка в цепочке, - это простой поиск в таблице. Общее количество поисков зависит от длины цепочки ссылок. Все программные c ссылки на символы узлов и меток будут осуществляться через имя пакета.

Однако я не уверен в целесообразности использования символов и значений символов в качестве структур данных. Помогает ли в этом случае поместить их в собственный пакет потенциальные конфликты?

Ответы [ 2 ]

1 голос
/ 16 апреля 2020

Вместо того, чтобы зацикливаться на реализации, я бы разработал протокол , которому должна следовать система. Вот один из них (обратите внимание, что я сделал здесь некоторые предположения, некоторые из которых, вероятно, являются неявными, и ни одно из которых не может согласиться с тем, как вы хотите, чтобы все работало):

;;;; Protocol
;;;
;;; By assumption there is one link with each label, each link points
;;; at one other node.
;;;
;;; NODEs have identity and can be destructively modified but it is
;;; not specified whether node equality is object identity.
;;;

(defgeneric node-link-labelled (node label)
  (:documentation "Return the node linked to NODE via LABEL, or NIL".))

(defgeneric (setf node-link-labelled) (target node label)
  (:documentation "set the link with label LABEL of NODE to TARGET, replacing it if
it exists.  Return TARGET."))

(defgeneric nodes-equal (n1 n2)
  (:documentation "Are N1 and N2 the same node?"))

(defgeneric node-remove-link (node label)
  (:documentation "Remove the link with label LABEL from NODE.  Return NODE.

The link need not exist"))

(defgeneric mapc-node-links (fn node)
  (:documentation "call FN with arguments NODE, LABEL TARGET for each link of NODE.

FN is allowed to delete the link corresponding to LABEL but should not otherwise
modify NODE"))

Затем вы можете написать реализации для этот протокол. Вот простой, в котором узлы являются конусами (<something> . <links>). Это будет медленно для большого количества ссылок, но, вероятно, очень быстро для небольших номеров. У него есть приятная особенность, заключающаяся в том, что вы можете задавать имена узлов, которые не поддерживаются в вышеуказанном протоколе.

;;;; Consy nodes are simple
;;;

(defun make-consy-node (&optional (label 'node))
  (list label))

(defmethod node-link-labelled ((node cons) label)
  (cdr (assoc label (cdr node))))

(defmethod nodes-equal ((n1 cons) (n2 cons))
  (eql n1 n2))

(defmethod (setf node-link-labelled) (target (node cons) label)
  (let ((found (assoc label (cdr node))))
    (if found
        (setf (cdr found) target)
      (push (cons label target) (cdr node))))
  target)

(defmethod node-remove-link ((node cons) label)
  (setf (cdr node) (delete-if (lambda (link)
                                (eql (car link) label))
                              (cdr node)))
  node)

(defmethod mapc-node-links (fn (node cons))
  ;; This is at least safe
  (loop for (label . target) in (copy-list (cdr node))
        do (funcall fn node label target))
  node)

Или вы можете реализовать узлы в виде таблиц ha sh, которые будут быстрыми для графов со многими ссылки на узел:

;;;; Hashy nodes
;;;

(defun make-hashy-node ()
  (make-hash-table))

(defmethod nodes-equal ((n1 hash-table) (n2 hash-table))
  (eql n1 n2))

(defmethod node-link-labelled ((node hash-table) label)
  (values (gethash label node nil)))

(defmethod (setf node-link-labelled) (target (node hash-table) label)
  (setf (gethash label node) target)
  target)

(defmethod node-remove-link ((node hash-table) label)
  (remhash label node)
  node)

(defmethod mapc-node-links (fn (node hash-table))
  (maphash (lambda (label target)
             (funcall fn node label target))
           node)
  node)

Или вы можете делать любые другие вещи. И поскольку все они следуют протоколу, вы можете смешивать их:

(let ((n1 (make-hashy-node)))
  (setf (node-link-labelled n1 'foo) (make-hashy-node)
        (node-link-labelled n1 'bar) (make-consy-node 'n2))
  n1)

Вы можете определить конструкцию узла как часть протокола, если хотите:

(defgeneric make-node-of-sort (sort &key)
  (:documentation "make a node whose sort is SORT.  Methods on this GF should
use EQL specializers on SORT"))

...

(defmethod make-node-of-sort ((sort (eql 'consy)) &key (name 'node))
  (list name))

...
1 голос
/ 16 апреля 2020

Если вы хотите использовать символы, вам не нужны ha sh -tables; Вы можете хранить данные в слоте symbol-value символа, а любые дополнительные данные в его symbol-plist. Поиск уже выполняется во время чтения, или с find-symbol или intern во время выполнения. Вы можете использовать unintern, чтобы отделить символ от его домашнего пакета, но другие узлы могут ссылаться на него, поэтому вам нужно будет удалить любую другую ссылку на этот символ при удалении узла (поэтому иногда вы сохраняете как входящий, так и исходящий края узла).

Это можно сделать, и, насколько я знаю, раньше это был обычный способ исторической работы с символами. Один из возможных недостатков заключается в том, что при создании пакета его нужно называть по имени (поэтому нет анонимного пакета на лету). Вы потенциально можете выбрать строку, которая в настоящее время не используется в качестве имени пакета, и вы ограничиваете имя своих узлов конкретным c пакетом.

Другой способ реализовать это - иметь node класс, который содержит name, где именем может быть любой символ, выбранный пользователем (в любом пакете). Класс graph поддерживает все узлы и ребра и т. Д. c, и вы можете управлять этими объектами изолированно, не путаясь со списком пакетов среды, т. Е. c. Это может быть немного чище.

Это было недавно сделано доступным, поэтому я хотел бы также отметить, что эта книга существует: Алгоритмы программирования Всеволода Домкина, который использует Common Lisp реализовать алгоритмы.

...