-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.go
361 lines (294 loc) · 8.88 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// This was written as part of an experiment That required the use of the
// pagerank algorithm on Overmind data. This satisfies the interfaces inside the
// gonum package, which means that we can use any of the code in
// [gonum.org/v1/gonum/graph](https://pkg.go.dev/gonum.org/v1/gonum/graph@v0.15.0)
// to analyse our data.
package graph
import (
"github.com/overmindtech/cli/sdp-go"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/set/uid"
)
///////////
// Nodes //
///////////
var _ graph.Node = &Node{}
// A node is always an item
type Node struct {
Item *sdp.Item
Weight float64
Id int64
}
// A graph-unique integer ID
func (n *Node) ID() int64 {
return n.Id
}
var _ graph.Nodes = &Nodes{}
type Nodes struct {
// The nodes in the iterator
nodes []*Node
// The current position in the iterator
i int
}
// Adds a new node to the list
func (n *Nodes) Append(node *Node) {
n.nodes = append(n.nodes, node)
}
// Next advances the iterator and returns whether the next call to the item
// method will return a non-nil item.
//
// Next should be called prior to any call to the iterator's item retrieval
// method after the iterator has been obtained or reset.
//
// The order of iteration is implementation dependent.
func (n *Nodes) Next() bool {
n.i++
return n.i-1 < len(n.nodes)
}
// Len returns the number of items remaining in the iterator.
//
// If the number of items in the iterator is unknown, too large to materialize
// or too costly to calculate then Len may return a negative value. In this case
// the consuming function must be able to operate on the items of the iterator
// directly without materializing the items into a slice. The magnitude of a
// negative length has implementation-dependent semantics.
func (n *Nodes) Len() int {
return len(n.nodes) - n.i
}
// Reset returns the iterator to its start position.
func (n *Nodes) Reset() {
n.i = 0
}
// Node returns the current Node from the iterator.
func (n *Nodes) Node() graph.Node {
// The Next() function gets called *before* the first item is returned, so
// we need to return the item at position i (e.g. 1 is 1st position) rather
// than the actual index i. This allows us to start i at zero which makes a
// lot more sense
getIndex := n.i - 1
if getIndex >= len(n.nodes) || getIndex < 0 {
return nil
}
return n.nodes[getIndex]
}
///////////
// Edges //
///////////
var _ graph.WeightedEdge = &Edge{}
type Edge struct {
from *Node
to *Node
weight float64
}
// Creates a new edge. The weight of an edge is the sum of the weights of the
// two nodes
func NewEdge(from, to *Node) *Edge {
return &Edge{
from: from,
to: to,
weight: from.Weight + to.Weight,
}
}
// From returns the from node of the edge.
func (e *Edge) From() graph.Node {
return e.from
}
// To returns the to node of the edge.
func (e *Edge) To() graph.Node {
return e.to
}
// ReversedEdge returns the edge reversal of the receiver if a reversal is valid
// for the data type. When a reversal is valid an edge of the same type as the
// receiver with nodes of the receiver swapped should be returned, otherwise the
// receiver should be returned unaltered.
func (e *Edge) ReversedEdge() graph.Edge {
return nil
}
func (e *Edge) Weight() float64 {
return e.weight
}
///////////
// Graph //
///////////
// Assert that SDPGraph satisfies the graph.WeightedDirected interface
var _ graph.WeightedDirected = &SDPGraph{}
type SDPGraph struct {
uidSet *uid.Set
nodesByID map[int64]*Node
nodesByGUN map[string]*Node
// A map of items that have not been seen yet. The key is the GUN of the
// "To" end of the edge, and the value is a slice of nodes that are the
// "From" edges
unseenEdges map[string][]*Node
edges []*Edge
undirected bool
}
// NewSDPGraph creates a new SDPGraph. If undirected is true, the graph will be
// treated as undirected, meaning that all edges will be bidirectional
func NewSDPGraph(undirected bool) *SDPGraph {
return &SDPGraph{
uidSet: uid.NewSet(),
nodesByID: make(map[int64]*Node),
nodesByGUN: make(map[string]*Node),
unseenEdges: make(map[string][]*Node),
edges: make([]*Edge, 0),
undirected: undirected,
}
}
// AddItem adds an item to the graph including processing of its edges, returns
// the ID the node was assigned.
func (g *SDPGraph) AddItem(item *sdp.Item, weight float64) int64 {
id := g.uidSet.NewID()
g.uidSet.Use(id)
// Add the node to the storage
node := Node{
Item: item,
Weight: weight,
Id: id,
}
g.nodesByID[id] = &node
g.nodesByGUN[item.GloballyUniqueName()] = &node
// Find all edges and add them
for _, linkedItem := range item.GetLinkedItems() {
// Check if the linked item node exists
linkedItemNode, exists := g.nodesByGUN[linkedItem.GetItem().GloballyUniqueName()]
if exists {
// Add the edge
g.edges = append(g.edges, NewEdge(&node, linkedItemNode))
if g.undirected {
// Also add the reverse edge
g.edges = append(g.edges, NewEdge(linkedItemNode, &node))
}
} else {
// If the target for the edge doesn't exist, add this to the list to
// be created later
if _, exists := g.unseenEdges[linkedItem.GetItem().GloballyUniqueName()]; !exists {
g.unseenEdges[linkedItem.GetItem().GloballyUniqueName()] = []*Node{&node}
} else {
g.unseenEdges[linkedItem.GetItem().GloballyUniqueName()] = append(g.unseenEdges[linkedItem.GetItem().GloballyUniqueName()], &node)
}
}
}
// If there are any unseen edges that are now seen, add them
if unseenEdges, exists := g.unseenEdges[item.GloballyUniqueName()]; exists {
for _, unseenEdge := range unseenEdges {
// Add the edge
g.edges = append(g.edges, NewEdge(unseenEdge, &node))
if g.undirected {
// Also add the reverse edge
g.edges = append(g.edges, NewEdge(&node, unseenEdge))
}
}
}
return id
}
// HasEdgeFromTo returns whether an edge exists in the graph from u to v with
// the IDs uid and vid.
func (g *SDPGraph) HasEdgeFromTo(uid, vid int64) bool {
for _, edge := range g.edges {
if edge.from.Id == uid && edge.to.Id == vid {
return true
}
}
return false
}
// To returns all nodes that can reach directly to the node with the given ID.
//
// To must not return nil.
func (g *SDPGraph) To(id int64) graph.Nodes {
nodes := Nodes{}
for _, edge := range g.edges {
if edge.to.Id == id {
nodes.Append(edge.to)
}
}
return &nodes
}
// WeightedEdge returns the weighted edge from u to v with IDs uid and vid if
// such an edge exists and nil otherwise. The node v must be directly reachable
// from u as defined by the From method.
func (g *SDPGraph) WeightedEdge(uid, vid int64) graph.WeightedEdge {
for _, edge := range g.edges {
if edge.from.Id == uid && edge.to.Id == vid {
return edge
}
}
return nil
}
// Weight returns the weight for the edge between x and y with IDs xid and yid
// if Edge(xid, yid) returns a non-nil Edge. If x and y are the same node or
// there is no joining edge between the two nodes the weight value returned is
// implementation dependent. Weight returns true if an edge exists between x and
// y or if x and y have the same ID, false otherwise.
func (g *SDPGraph) Weight(xid, yid int64) (w float64, ok bool) {
edge := g.WeightedEdge(xid, yid)
if edge == nil {
return 0, false
}
return edge.Weight(), true
}
// Node returns the node with the given ID if it exists in the graph, and nil
// otherwise.
func (g *SDPGraph) Node(id int64) graph.Node {
node, exists := g.nodesByID[id]
if !exists {
return nil
}
return node
}
// Gets a node from the graph by it's globally unique name
func (g *SDPGraph) NodeByGloballyUniqueName(globallyUniqueName string) *Node {
node, exists := g.nodesByGUN[globallyUniqueName]
if !exists {
return nil
}
return node
}
// Nodes returns all the nodes in the graph.
//
// Nodes must not return nil.
func (g *SDPGraph) Nodes() graph.Nodes {
nodes := Nodes{}
for _, node := range g.nodesByID {
nodes.Append(node)
}
return &nodes
}
// From returns all nodes that can be reached directly from the node with the
// given ID.
//
// From must not return nil.
func (g *SDPGraph) From(id int64) graph.Nodes {
nodes := Nodes{}
for _, edge := range g.edges {
if edge.From().ID() == id {
nodes.Append(edge.to)
}
}
return &nodes
}
// HasEdgeBetween returns whether an edge exists between nodes with IDs xid and
// yid without considering direction.
func (g *SDPGraph) HasEdgeBetween(xid, yid int64) bool {
var fromID int64
var toID int64
for _, edge := range g.edges {
fromID = edge.From().ID()
toID = edge.To().ID()
if (fromID == xid && toID == yid) || (fromID == yid && toID == xid) {
return true
}
}
return false
}
// Edge returns the edge from u to v, with IDs uid and vid, if such an edge
// exists and nil otherwise. The node v must be directly reachable from u as
// defined by the From method.
func (g *SDPGraph) Edge(uid, vid int64) graph.Edge {
for _, edge := range g.edges {
if (edge.From().ID() == uid) && (edge.To().ID() == vid) {
return edge
}
}
return nil
}