changelog shortlog graph tags branches changeset files file revisions raw help

Mercurial > core / annotate lisp/lib/obj/graph/pkg.lisp

changeset 656: b499d4bcfc39
parent: 5bde4fedc5c1
author: Richard Westhaver <ellis@rwest.io>
date: Tue, 17 Sep 2024 22:19:19 -0400
permissions: -rw-r--r--
description: removed x.lisp
104
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
1
 ;;; lib/obj/graph.lisp --- Graphs
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
2
 
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
3
 ;; Graph objects and algorithms
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
4
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
5
 ;;; Commentary:
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
6
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
7
 ;; Mostly modeled off of eschulte's GRAPH library - see also DAT/DOT
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
8
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
9
 ;; ref: https://eschulte.github.io/graph/
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
10
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
11
 ;; Our goals are slightly different than the original library - we prioritize
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
12
 ;; flexibility over speed or code size. To this end the base GRAPH class
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
13
 ;; accepts NODE vectors in addition to hash-tables. We also make minimal
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
14
 ;; assumptions about the underling node types - it is a blank class
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
15
 ;; definition. Edges are a bit more complicated - they subclass ID which can be
104
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
16
 
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
17
 ;;; Code:
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
18
 (in-package :obj/graph)
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
19
 
457
Richard Westhaver <ellis@rwest.io>
parents: 456
diff changeset
20
 (in-readtable :std)
Richard Westhaver <ellis@rwest.io>
parents: 456
diff changeset
21
 
234
d7aa08025537 cry, obj/meta, ffi/magick
Richard Westhaver <ellis@rwest.io>
parents: 122
diff changeset
22
 (defclass node () ())
d7aa08025537 cry, obj/meta, ffi/magick
Richard Westhaver <ellis@rwest.io>
parents: 122
diff changeset
23
 
104
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
24
 ;;; Vertex
234
d7aa08025537 cry, obj/meta, ffi/magick
Richard Westhaver <ellis@rwest.io>
parents: 122
diff changeset
25
 (defclass vertex (id node)
104
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
26
   ()
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
27
   (:documentation "generic vertex mixin. The difference between this class and NODE is
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
28
 that a vertex always carries an ID slot."))
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
29
 
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
30
 ;;; Edge
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
31
 (defclass edge (node)
656
b499d4bcfc39 removed x.lisp
Richard Westhaver <ellis@rwest.io>
parents: 464
diff changeset
32
   ((in :initarg :in) (out :initarg :out))
104
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
33
   (:documentation "generic edge mixin. Compatible with the NODE and ID protocols."))
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
34
 
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
35
 (defclass edgex (edge id)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
36
   ()
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
37
   (:documentation "Edge compatible with the NODE and ID protocols."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
38
 
105
524dfb768c7a added PWN, NET, OBJ/URI
ellis <ellis@rwest.io>
parents: 104
diff changeset
39
 (defclass directed-edge (edge)
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
40
   ()
234
d7aa08025537 cry, obj/meta, ffi/magick
Richard Westhaver <ellis@rwest.io>
parents: 122
diff changeset
41
   (:documentation "An edge with an implicit direction from node A to B."))
104
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
42
 
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
43
 (defclass weighted-edge (edge)
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
44
   ((weight :initform 1d0 :initarg :weight :accessor weight-of)))
6e5caf0c68a1 obj tree, id, hash, seq, graph.. added cli/progress and init cli/repl
ellis <ellis@rwest.io>
parents:
diff changeset
45
 
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
46
 ;;; Hashing
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
47
 (defun copy-hash (hash &optional test comb)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
48
   "Return a copy of HASH.
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
49
 Optional argument TEST specifies a new equality test to use for the
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
50
 copy.  Second optional argument COMB specifies a function to use to
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
51
 combine the values of elements of HASH which collide in the copy due
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
52
 to a new equality test specified with TEST."
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
53
   (let ((comb (when comb (fdefinition comb)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
54
         (copy (make-hash-table :test (or test (hash-table-test hash)))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
55
     (maphash (lambda (k v) (setf (gethash k copy)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
56
                             (if (and (gethash k copy) comb)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
57
                                 (funcall comb (gethash k copy) v)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
58
                                 v)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
59
              hash)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
60
     copy))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
61
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
62
 (defun node-hash-equal (hash1 hash2)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
63
   "Test node hashes HASH1 and HASH2 for equality."
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
64
   (set-equal (hash-table-alist hash1)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
65
              (hash-table-alist hash2)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
66
              :test (lambda (a b)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
67
                      (and (equalp (car a) (car b))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
68
                           (set-equal (cdr a) (cdr b) :test 'tree-equal)))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
69
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
70
 (defun edge-hash-equal (hash1 hash2)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
71
   "Test edge hashes HASH1 and HASH2 for equality."
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
72
   (set-equal (hash-table-alist hash1)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
73
              (hash-table-alist hash2)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
74
              :test 'equalp))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
75
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
76
 (defun edge-equalp (edge1 edge2)
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
77
   (set-equal edge1 edge2 :test 'equal))
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
78
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
79
 (defun directed-edge-equalp (edge1 edge2)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
80
   (tree-equal edge1 edge2))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
81
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
82
 (defun sxhash-edge (edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
83
   (sxhash (sort (copy-tree edge)
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
84
                 (cond
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
85
                   ((and (numberp (car edge)) (numberp (cdr edge)))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
86
                    (lambda (a b)
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
87
                      (or (< (imagpart a) (imagpart b))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
88
                          (and (= (imagpart a) (imagpart b))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
89
                               (< (realpart a) (realpart b))))))
464
Richard Westhaver <ellis@rwest.io>
parents: 463
diff changeset
90
                   ((or (numberp (car edge)) (numberp (second edge)))
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
91
                    (lambda (a b) (declare (ignore a b)) t))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
92
                   (t #'string<)))))
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
93
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
94
 (sb-ext:define-hash-table-test edge-equalp sxhash-edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
95
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
96
 (sb-ext:define-hash-table-test directed-edge-equalp sxhash)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
97
 
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
98
 ;;; Proto
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
99
 (defgeneric nodes (graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
100
 (defgeneric (setf nodes) (graph nodes))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
101
 (defgeneric edges (graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
102
 (defgeneric (setf edges) (graph edges))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
103
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
104
 (defgeneric graph-equal (graph1 graph2))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
105
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
106
 (defgeneric subgraph (graph nodes)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
107
   (:documentation "Return the subgraph of GRAPH restricted to NODES."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
108
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
109
 (defgeneric delete-node (graph node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
110
   (:documentation "Delete NODE from GRAPH.
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
111
 Delete and return the old edges of NODE in GRAPH."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
112
 
462
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
113
 (defgeneric has-node-p (graph node)
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
114
   (:documentation "Return non-nil if GRAPH has node NODE."))
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
115
 (defgeneric has-edge-p (graph edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
116
   (:documentation "Return `true' if GRAPH has edge EDGE."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
117
 
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
118
 (defgeneric edge-weight (edge &key &allow-other-keys)
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
119
   (:method ((edge t) &key &allow-other-keys) (values 1.0)))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
120
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
121
 (defgeneric edge-value (graph edge)
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
122
   (:method ((graph t) (edge t)) (values nil)))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
123
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
124
 (defgeneric (setf edge-value) (new graph edge))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
125
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
126
 (defgeneric delete-edge (graph edge)
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
127
   (:documentation "Delete EDGE from GRAPH.
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
128
 Return the old value of EDGE."))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
129
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
130
 (defgeneric node-edges (graph node)
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
131
   (:documentation "Return the edges of NODE in GRAPH."))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
132
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
133
 (defgeneric (setf node-edges) (new graph node)
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
134
   (:documentation "Set the edges of NODE in GRAPH to NEW.
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
135
 Delete and return the old edges of NODE in GRAPH."))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
136
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
137
 (defgeneric add-node (graph node))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
138
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
139
 (defgeneric add-edge (graph edge &optional value))
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
140
 
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
141
 ;;; Graph
234
d7aa08025537 cry, obj/meta, ffi/magick
Richard Westhaver <ellis@rwest.io>
parents: 122
diff changeset
142
 (defclass graph (node)
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
143
   ((nodes :initform (make-hash-table :test 'equal)
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
144
           :type (or (vector node) hash-table)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
145
           :accessor nodes
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
146
           :initarg :nodes)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
147
    (edges :initform (make-hash-table :test 'edge-equalp)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
148
           :type hash-table
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
149
           :accessor edges
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
150
           :initarg :edges))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
151
   (:documentation "generic graph object."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
152
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
153
 (defmethod copy-graph ((graph graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
154
   (make-instance (type-of graph) :nodes (copy-hash (nodes graph)) :edges (copy-hash (edges graph))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
155
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
156
 (defmethod subgraph ((graph graph) nodes)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
157
   (make-instance (type-of graph) :nodes nodes :edges (copy-hash (edges graph))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
158
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
159
 (defmethod has-edge-p ((graph graph) edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
160
   (multiple-value-bind (value included) (gethash edge (edges graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
161
     (declare (ignorable value)) included))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
162
 
462
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
163
 (defmethod has-node-p ((graph graph) node)
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
164
   (multiple-value-bind (value included) (gethash node (nodes graph))
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
165
     (declare (ignorable value)) included))
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
166
 
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
167
 (defmethod delete-node ((graph graph) node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
168
   (prog1 (mapcar (lambda (edge) (cons edge (delete-edge graph edge)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
169
                  (node-edges graph node))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
170
     (remhash node (nodes graph))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
171
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
172
 (defmethod delete-edge ((graph graph) edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
173
   (prog1 (edge-value graph edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
174
     (mapc (lambda (node) (setf (gethash node (nodes graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
175
                           (remove edge (gethash node (nodes graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
176
                                   :test 'edge-equalp)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
177
           edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
178
     (remhash edge (edges graph))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
179
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
180
 (defmethod node-edges ((graph graph) node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
181
   (multiple-value-bind (edges included) (gethash node (nodes graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
182
     (assert included (node graph) "~S doesn't include ~S" graph node)
462
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
183
     edges))
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
184
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
185
 (defmethod (setf node-edges) (new (graph graph) node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
186
   (prog1 (mapc {delete-edge graph} (gethash node (nodes graph)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
187
     (mapc {add-edge graph} new)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
188
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
189
 (defmethod add-edge ((graph graph) edge &optional value)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
190
   (mapc (lambda (node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
191
           (add-node graph node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
192
           (pushnew (case (type-of graph)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
193
                      (graph (remove-duplicates edge))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
194
                      (directed-graph edge))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
195
                    (gethash node (nodes graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
196
                    :test 'edge-equalp))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
197
         edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
198
   (setf (gethash edge (edges graph)) value)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
199
   edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
200
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
201
 (defmethod edge-value ((graph graph) edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
202
   (multiple-value-bind (value included) (gethash edge (edges graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
203
     (assert included (edge graph) "~S doesn't include ~S" graph edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
204
     value))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
205
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
206
 (defmethod (setf edge-value) (new (graph graph) edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
207
   (setf (gethash edge (edges graph)) new))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
208
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
209
 (defgeneric merge-nodes (graph node1 node2 &key new)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
210
   (:documentation "Combine NODE1 and NODE2 in GRAPH into the node NEW.
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
211
 All edges of NODE1 and NODE2 in GRAPH will be combined into a new node
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
212
 of value NEW.  Edges between only NODE1 and NODE2 will be removed."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
213
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
214
 (defmethod merge-nodes ((graph graph) node1 node2 &key (new node1))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
215
   ;; replace all removed edges with NEW instead of NODE1 or NODE2
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
216
   (mapcar
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
217
    (lambda (l)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
218
      (destructuring-bind(edge . value) l
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
219
        (let ((e (mapcar (lambda (n) (if (member n (list node1 node2)) new n)) edge)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
220
          (if (has-edge-p graph e)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
221
              (when (and (edge-value graph e) value)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
222
                (setf (edge-value graph e) (+ (edge-value graph e) value)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
223
              (add-edge graph e value)))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
224
    ;; drop edges between only node1 and node2
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
225
    (remove-if-not [{set-difference _ (list node1 node2)} #'car]
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
226
                   ;; delete both nodes keeping their edges and values
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
227
                   (prog1 (append (delete-node graph node1)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
228
                                  (delete-node graph node2))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
229
                     ;; add the new node
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
230
                     (add-node graph new))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
231
   graph)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
232
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
233
 (defgeneric merge-edges (graph edge1 edge2 &key value)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
234
   (:documentation "Combine EDGE1 and EDGE2 in GRAPH into a new EDGE.
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
235
 Optionally provide a value for the new edge, the values of EDGE1 and
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
236
 EDGE2 will be combined."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
237
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
238
 (defmethod merge-edges ((graph graph) edge1 edge2 &key value)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
239
   (add-edge graph (remove-duplicates (append edge1 edge2))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
240
             (or value
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
241
                 (when (and (edge-value graph edge1) (edge-value graph edge2))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
242
                   (+ (edge-value graph edge1) (edge-value graph edge2)))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
243
   (append (delete-edge graph edge1)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
244
           (delete-edge graph edge2)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
245
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
246
 (defgeneric degree (graph node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
247
   (:documentation "Return the degree of NODE in GRAPH."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
248
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
249
 (defmethod degree ((graph graph) node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
250
   (length (node-edges graph node)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
251
 
462
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
252
 (defmethod add-node ((graph graph) node)
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
253
   ;; NOTE: This is where our implementation breaks character from Eschulte's
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
254
   ;; implementation. We currently accept strings in addition to numbers and symbols.
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
255
   (assert (or (numberp node) (symbolp node) (stringp node)) (node)
462
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
256
           "Nodes must be numbers, symbols or keywords, not ~S.~%Invalid node:~S"
463
f2bb57563930 obj/graph and dat/dot test and fixes
Richard Westhaver <ellis@rwest.io>
parents: 462
diff changeset
257
           (type-of node) node)
462
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
258
   (unless (has-node-p graph node)
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
259
     (setf (gethash node (nodes graph)) nil)
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
260
     node))
0a197b3b6995 random fixes, add sockopt-receive-timeout
Richard Westhaver <ellis@rwest.io>
parents: 457
diff changeset
261
 
456
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
262
 ;;; Directed Graph
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
263
 (defclass directed-graph (graph)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
264
   ((edges :initform (make-hash-table :test 'directed-edge-equalp)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
265
           :type (or (vector directed-edge) hash-table)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
266
           :accessor edges
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
267
           :initarg :edges))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
268
   (:documentation "graph with only directed edges."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
269
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
270
 (defgeneric indegree (digraph node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
271
   (:documentation "The number of edges directed to NODE in GRAPH."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
272
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
273
 (defmethod indegree ((digraph directed-graph) node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
274
   (length (remove-if-not [{member node} #'cdr] (node-edges digraph node))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
275
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
276
 (defgeneric outdegree (digraph node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
277
   (:documentation "The number of edges directed from NODE in DIGRAPH."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
278
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
279
 (defmethod outdegree ((digraph directed-graph) node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
280
   (length (remove-if-not [{equal node} #'car] (node-edges digraph node))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
281
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
282
 ;;; Shortest Path
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
283
 (defgeneric shortest-path (graph a b &optional heuristic)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
284
   (:documentation "Return the shortest path in GRAPH from A to B.
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
285
 Implemented using A* search.  Optional argument HEURISTIC may be a
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
286
 function which returns an estimated heuristic cost from an node to the
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
287
 target B.  The default value for HEURISTIC is the constant function of
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
288
 0, reducing this implementation to Dijkstra's algorithm.  The
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
289
 HEURISTIC function must satisfy HEURITIC(x)≤d(x,y)+HEURITIC(y) ∀ x,y
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
290
 in GRAPH allowing the more efficient monotonic or \"consistent\"
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
291
 implementation of A*.")
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
292
   (:method ((graph graph) a b
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
293
             &optional
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
294
               (heuristic (constantly 0))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
295
             &aux
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
296
               (from (make-hash-table))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
297
               (fringe (sb-concurrency:make-queue))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
298
               (open (make-hash-table))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
299
               (closed (make-hash-table))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
300
               (g (make-hash-table))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
301
               (f (make-hash-table)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
302
     (when (equal a b) (return-from shortest-path nil))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
303
     (labels ((reconstruct-path (current)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
304
                (destructuring-bind (node . edge) (gethash current from)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
305
                  (cons edge (unless (member a edge) (reconstruct-path node))))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
306
       (setf (gethash a g) 0
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
307
             (gethash a f) (funcall heuristic a)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
308
             (gethash a open) t)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
309
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
310
       (sb-concurrency:enqueue fringe (gethash a f))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
311
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
312
       (do ((current (sb-concurrency:dequeue fringe) (sb-concurrency:dequeue fringe)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
313
           ((zerop (hash-table-count open))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
314
            (multiple-value-bind (value present-p) (gethash b f)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
315
              (when present-p
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
316
                (values (nreverse (reconstruct-path b)) value))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
317
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
318
         (when (eql current b)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
319
           (return-from shortest-path
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
320
             (values (nreverse (reconstruct-path current))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
321
                     (gethash current f))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
322
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
323
         (remhash current open)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
324
         (setf (gethash current closed) t)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
325
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
326
         (mapc (lambda (edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
327
                 (let ((weight (or (edge-value graph edge) 1)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
328
                   (mapc (lambda (next)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
329
                           (unless (gethash next closed)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
330
                             (setf (gethash next open) t)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
331
                             (let ((tentative (+ (gethash current g) weight)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
332
                               (multiple-value-bind (value present-p)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
333
                                   (gethash next g)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
334
                                 (when (or (not present-p)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
335
                                           (< tentative value))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
336
                                   (setf (gethash next from) (cons current edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
337
                                         (gethash next g) tentative
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
338
                                         (gethash next f)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
339
                                         (+ tentative (funcall heuristic next)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
340
                                   (sb-concurrency:enqueue fringe (gethash next f)))))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
341
                         (etypecase graph
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
342
                           (directed-graph (cdr (member current edge)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
343
                           (graph (remove current edge))))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
344
               (node-edges graph current))))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
345
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
346
 ;;; Min Cut
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
347
 ;;
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
348
 ;; Stoer, M. and Wagner, Frank. 1997. A Simple Min-Cut Algorithm.
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
349
 ;; Journal of the ACM
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
350
 ;;
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
351
 ;; Theorem: Let s,t ∈ (nodes G), let G' be the result of merging s and
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
352
 ;;          t in G.  Then (min-cut G) is equal to the minimum of the
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
353
 ;;          min cut of s and t in G and (min-cut G').
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
354
 ;;
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
355
 (defun weigh-cut (graph cut)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
356
   (reduce #'+ (mapcar {edge-value graph}
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
357
                       (remove-if-not (lambda (edge)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
358
                                        (and (intersection edge (first cut))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
359
                                             (intersection edge (second cut))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
360
                                      (edges graph)))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
361
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
362
 (defgeneric min-cut (graph)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
363
   (:documentation
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
364
    "Return both the global min-cut of GRAPH and the weight of the cut."))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
365
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
366
 (defmethod min-cut ((graph graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
367
   (let ((g (copy-graph graph))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
368
         (merged-nodes (mapcar (lambda (n) (list n n)) (nodes graph)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
369
         cuts-of-phase)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
370
     (flet ((connection-weight (group node)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
371
              ;; return the weight of edges between GROUP and NODE
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
372
              (reduce #'+ (mapcar {edge-value g}
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
373
                                  (remove-if-not {intersection group}
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
374
                                                 (node-edges g node)))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
375
            (my-merge (a b)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
376
              ;; merge in the graph
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
377
              (merge-nodes g a b)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
378
              ;; update our merged nodes alist
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
379
              (setf (cdr (assoc a merged-nodes))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
380
                    (append (cdr (assoc a merged-nodes))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
381
                            (cdr (assoc b merged-nodes))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
382
              (setq merged-nodes
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
383
                    (remove-if (lambda (it) (eql (car it) b)) merged-nodes))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
384
       (loop :while (> (length (nodes g)) 1) :do
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
385
          (let* ((a (list (random (nodes g))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
386
                 (rest (remove (car a) (nodes g))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
387
            (loop :while rest :do
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
388
               ;; grow A by adding the node most tightly connected to A
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
389
               (let ((new (car (sort rest #'> :key {connection-weight a}))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
390
                 (setf rest (remove new rest))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
391
                 (push new a)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
392
            ;; store the cut-of-phase
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
393
            (push (cons (connection-weight (cdr a) (car a))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
394
                        (cdr (assoc (car a) merged-nodes)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
395
                  cuts-of-phase)
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
396
            ;; merge two last added nodes
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
397
            (my-merge (first a) (second a))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
398
       ;; return the minimum cut-of-phase
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
399
       (let* ((half (cdar (sort cuts-of-phase #'< :key #'car)))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
400
              (cut  (list half (set-difference (nodes graph) half))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
401
         (values (sort cut #'< :key #'length) (weigh-cut graph cut))))))
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
402
 
8d7aa0af2367 graphwork
Richard Westhaver <ellis@rwest.io>
parents: 234
diff changeset
403
 ;; https://en.wikipedia.org/wiki/Degeneracy_(graph_theory)