Skip to content

Commit d312e1d

Browse files
committed
removes duplication of un/directed implementations
Introduces the private graph[K, T] struct to partially implement the Graph[K, T] interface. Only select functions are in their respective directed or undirected implementation files. This reduces most of the duplication that was present in both files. Signed-off-by: davidovich <[email protected]>
1 parent a999520 commit d312e1d

File tree

4 files changed

+463
-614
lines changed

4 files changed

+463
-614
lines changed

directed.go

Lines changed: 19 additions & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -1,315 +1,45 @@
11
package graph
22

3-
import (
4-
"errors"
5-
"fmt"
6-
)
7-
83
type directed[K comparable, T any] struct {
9-
hash Hash[K, T]
10-
traits *Traits
11-
store Store[K, T]
4+
graph[K, T]
125
}
136

147
func newDirected[K comparable, T any](hash Hash[K, T], traits *Traits, store Store[K, T]) *directed[K, T] {
8+
traits.IsDirected = true
159
return &directed[K, T]{
16-
hash: hash,
17-
traits: traits,
18-
store: store,
19-
}
20-
}
21-
22-
func (d *directed[K, T]) Traits() *Traits {
23-
return d.traits
24-
}
25-
26-
func (d *directed[K, T]) AddVertex(value T, options ...func(*VertexProperties)) error {
27-
hash := d.hash(value)
28-
properties := VertexProperties{
29-
Weight: 0,
30-
Attributes: make(map[string]string),
31-
}
32-
33-
for _, option := range options {
34-
option(&properties)
10+
graph: graph[K, T]{
11+
hash: hash,
12+
traits: traits,
13+
store: store,
14+
},
3515
}
36-
37-
return d.store.AddVertex(hash, value, properties)
3816
}
3917

4018
func (d *directed[K, T]) AddVerticesFrom(g Graph[K, T]) error {
41-
adjacencyMap, err := g.AdjacencyMap()
42-
if err != nil {
43-
return fmt.Errorf("failed to get adjacency map: %w", err)
44-
}
45-
46-
for hash := range adjacencyMap {
47-
vertex, properties, err := g.VertexWithProperties(hash)
48-
if err != nil {
49-
return fmt.Errorf("failed to get vertex %v: %w", hash, err)
50-
}
51-
52-
if err = d.AddVertex(vertex, copyVertexProperties(properties)); err != nil {
53-
return fmt.Errorf("failed to add vertex %v: %w", hash, err)
54-
}
55-
}
56-
57-
return nil
58-
}
59-
60-
func (d *directed[K, T]) Vertex(hash K) (T, error) {
61-
vertex, _, err := d.store.Vertex(hash)
62-
return vertex, err
63-
}
64-
65-
func (d *directed[K, T]) VertexWithProperties(hash K) (T, VertexProperties, error) {
66-
vertex, properties, err := d.store.Vertex(hash)
67-
if err != nil {
68-
return vertex, VertexProperties{}, err
69-
}
70-
71-
return vertex, properties, nil
72-
}
73-
74-
func (d *directed[K, T]) RemoveVertex(hash K) error {
75-
return d.store.RemoveVertex(hash)
76-
}
77-
78-
func (d *directed[K, T]) AddEdge(sourceHash, targetHash K, options ...func(*EdgeProperties)) error {
79-
_, _, err := d.store.Vertex(sourceHash)
80-
if err != nil {
81-
return fmt.Errorf("source vertex %v: %w", sourceHash, err)
82-
}
83-
84-
_, _, err = d.store.Vertex(targetHash)
85-
if err != nil {
86-
return fmt.Errorf("target vertex %v: %w", targetHash, err)
87-
}
88-
89-
if _, err := d.Edge(sourceHash, targetHash); !errors.Is(err, ErrEdgeNotFound) {
90-
return ErrEdgeAlreadyExists
91-
}
19+
other := tograph(g)
20+
return d.addVerticesFrom(&other)
9221

93-
// If the user opted in to preventing cycles, run a cycle check.
94-
if d.traits.PreventCycles {
95-
createsCycle, err := d.createsCycle(sourceHash, targetHash)
96-
if err != nil {
97-
return fmt.Errorf("check for cycles: %w", err)
98-
}
99-
if createsCycle {
100-
return ErrEdgeCreatesCycle
101-
}
102-
}
103-
104-
edge := Edge[K]{
105-
Source: sourceHash,
106-
Target: targetHash,
107-
Properties: EdgeProperties{
108-
Attributes: make(map[string]string),
109-
},
110-
}
111-
112-
for _, option := range options {
113-
option(&edge.Properties)
114-
}
115-
116-
return d.addEdge(sourceHash, targetHash, edge)
11722
}
11823

11924
func (d *directed[K, T]) AddEdgesFrom(g Graph[K, T]) error {
120-
edges, err := g.Edges()
121-
if err != nil {
122-
return fmt.Errorf("failed to get edges: %w", err)
123-
}
124-
125-
for _, edge := range edges {
126-
if err := d.AddEdge(copyEdge(edge)); err != nil {
127-
return fmt.Errorf("failed to add (%v, %v): %w", edge.Source, edge.Target, err)
128-
}
129-
}
130-
131-
return nil
132-
}
133-
134-
func (d *directed[K, T]) Edge(sourceHash, targetHash K) (Edge[T], error) {
135-
edge, err := d.store.Edge(sourceHash, targetHash)
136-
if err != nil {
137-
return Edge[T]{}, err
138-
}
139-
140-
sourceVertex, _, err := d.store.Vertex(sourceHash)
141-
if err != nil {
142-
return Edge[T]{}, err
143-
}
144-
145-
targetVertex, _, err := d.store.Vertex(targetHash)
146-
if err != nil {
147-
return Edge[T]{}, err
148-
}
149-
150-
return Edge[T]{
151-
Source: sourceVertex,
152-
Target: targetVertex,
153-
Properties: EdgeProperties{
154-
Weight: edge.Properties.Weight,
155-
Attributes: edge.Properties.Attributes,
156-
Data: edge.Properties.Data,
157-
},
158-
}, nil
159-
}
160-
161-
func (d *directed[K, T]) Edges() ([]Edge[K], error) {
162-
return d.store.ListEdges()
25+
other := tograph(g)
26+
return d.addEdgesFrom(&other)
16327
}
16428

16529
func (d *directed[K, T]) UpdateEdge(source, target K, options ...func(properties *EdgeProperties)) error {
166-
existingEdge, err := d.store.Edge(source, target)
167-
if err != nil {
168-
return err
169-
}
170-
171-
for _, option := range options {
172-
option(&existingEdge.Properties)
173-
}
174-
175-
return d.store.UpdateEdge(source, target, existingEdge)
176-
}
177-
178-
func (d *directed[K, T]) RemoveEdge(source, target K) error {
179-
if _, err := d.Edge(source, target); err != nil {
180-
return err
181-
}
182-
183-
if err := d.store.RemoveEdge(source, target); err != nil {
184-
return fmt.Errorf("failed to remove edge from %v to %v: %w", source, target, err)
185-
}
186-
187-
return nil
188-
}
189-
190-
func (d *directed[K, T]) AdjacencyMap() (map[K]map[K]Edge[K], error) {
191-
vertices, err := d.store.ListVertices()
192-
if err != nil {
193-
return nil, fmt.Errorf("failed to list vertices: %w", err)
194-
}
30+
_, err := d.updateEdge(source, target, options...)
19531

196-
edges, err := d.store.ListEdges()
197-
if err != nil {
198-
return nil, fmt.Errorf("failed to list edges: %w", err)
199-
}
200-
201-
m := make(map[K]map[K]Edge[K], len(vertices))
202-
203-
for _, vertex := range vertices {
204-
m[vertex] = make(map[K]Edge[K])
205-
}
206-
207-
for _, edge := range edges {
208-
m[edge.Source][edge.Target] = edge
209-
}
210-
211-
return m, nil
212-
}
213-
214-
func (d *directed[K, T]) PredecessorMap() (map[K]map[K]Edge[K], error) {
215-
vertices, err := d.store.ListVertices()
216-
if err != nil {
217-
return nil, fmt.Errorf("failed to list vertices: %w", err)
218-
}
219-
220-
edges, err := d.store.ListEdges()
221-
if err != nil {
222-
return nil, fmt.Errorf("failed to list edges: %w", err)
223-
}
224-
225-
m := make(map[K]map[K]Edge[K], len(vertices))
226-
227-
for _, vertex := range vertices {
228-
m[vertex] = make(map[K]Edge[K])
229-
}
230-
231-
for _, edge := range edges {
232-
if _, ok := m[edge.Target]; !ok {
233-
m[edge.Target] = make(map[K]Edge[K])
234-
}
235-
m[edge.Target][edge.Source] = edge
236-
}
237-
238-
return m, nil
239-
}
240-
241-
func (d *directed[K, T]) addEdge(sourceHash, targetHash K, edge Edge[K]) error {
242-
return d.store.AddEdge(sourceHash, targetHash, edge)
32+
return err
24333
}
24434

24535
func (d *directed[K, T]) Clone() (Graph[K, T], error) {
246-
traits := &Traits{
247-
IsDirected: d.traits.IsDirected,
248-
IsAcyclic: d.traits.IsAcyclic,
249-
IsWeighted: d.traits.IsWeighted,
250-
IsRooted: d.traits.IsRooted,
251-
PreventCycles: d.traits.PreventCycles,
252-
}
253-
254-
clone := &directed[K, T]{
255-
hash: d.hash,
256-
traits: traits,
257-
store: newMemoryStore[K, T](),
258-
}
259-
260-
if err := clone.AddVerticesFrom(d); err != nil {
261-
return nil, fmt.Errorf("failed to add vertices: %w", err)
262-
}
263-
264-
if err := clone.AddEdgesFrom(d); err != nil {
265-
return nil, fmt.Errorf("failed to add edges: %w", err)
266-
}
267-
268-
return clone, nil
269-
}
270-
271-
func (d *directed[K, T]) Order() (int, error) {
272-
return d.store.VertexCount()
273-
}
274-
275-
func (d *directed[K, T]) Size() (int, error) {
276-
return d.store.EdgeCount()
277-
}
278-
279-
func (d *directed[K, T]) edgesAreEqual(a, b Edge[T]) bool {
280-
aSourceHash := d.hash(a.Source)
281-
aTargetHash := d.hash(a.Target)
282-
bSourceHash := d.hash(b.Source)
283-
bTargetHash := d.hash(b.Target)
284-
285-
return aSourceHash == bSourceHash && aTargetHash == bTargetHash
286-
}
287-
288-
func (d *directed[K, T]) createsCycle(source, target K) (bool, error) {
289-
// If the underlying store implements CreatesCycle, use that fast path.
290-
if cc, ok := d.store.(interface {
291-
CreatesCycle(source, target K) (bool, error)
292-
}); ok {
293-
return cc.CreatesCycle(source, target)
36+
gclone, err := d.clone()
37+
if err != nil {
38+
return nil, err
29439
}
29540

296-
// Slow path.
297-
return CreatesCycle(Graph[K, T](d), source, target)
298-
}
299-
300-
// copyEdge returns an argument list suitable for the Graph.AddEdge method. This
301-
// argument list is derived from the given edge, hence the name copyEdge.
302-
//
303-
// The last argument is a custom functional option that sets the edge properties
304-
// to the properties of the original edge.
305-
func copyEdge[K comparable](edge Edge[K]) (K, K, func(properties *EdgeProperties)) {
306-
copyProperties := func(p *EdgeProperties) {
307-
for k, v := range edge.Properties.Attributes {
308-
p.Attributes[k] = v
309-
}
310-
p.Weight = edge.Properties.Weight
311-
p.Data = edge.Properties.Data
41+
dir := &directed[K, T]{
42+
graph: *gclone,
31243
}
313-
314-
return edge.Source, edge.Target, copyProperties
44+
return dir, nil
31545
}

directed_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ func TestDirected_Traits(t *testing.T) {
1010
traits *Traits
1111
expected *Traits
1212
}{
13-
"default traits": {
14-
traits: &Traits{},
15-
expected: &Traits{},
16-
},
1713
"directed": {
1814
traits: &Traits{IsDirected: true},
1915
expected: &Traits{IsDirected: true},

0 commit comments

Comments
 (0)