Skip to content

Commit cda09bd

Browse files
committed
feat: introduce graph InEdges & OutEdges methods
1 parent a999520 commit cda09bd

File tree

6 files changed

+420
-2
lines changed

6 files changed

+420
-2
lines changed

directed.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ func (d *directed[K, T]) Edges() ([]Edge[K], error) {
162162
return d.store.ListEdges()
163163
}
164164

165+
func (d *directed[K, T]) InEdges(targetHash K) ([]Edge[K], error) {
166+
return d.store.ListInEdges(targetHash)
167+
}
168+
169+
func (d *directed[K, T]) OutEdges(sourceHash K) ([]Edge[K], error) {
170+
return d.store.ListOutEdges(sourceHash)
171+
}
172+
165173
func (d *directed[K, T]) UpdateEdge(source, target K, options ...func(properties *EdgeProperties)) error {
166174
existingEdge, err := d.store.Edge(source, target)
167175
if err != nil {

directed_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,178 @@ func TestDirected_Edges(t *testing.T) {
783783
}
784784
}
785785

786+
func TestDirected_OutEdges(t *testing.T) {
787+
tests := map[string]struct {
788+
vertices []int
789+
edges []Edge[int]
790+
expectedEdges []Edge[int]
791+
}{
792+
"graph with 3 edges": {
793+
vertices: []int{1, 2, 3},
794+
edges: []Edge[int]{
795+
{
796+
Source: 1,
797+
Target: 2,
798+
Properties: EdgeProperties{
799+
Weight: 10,
800+
Attributes: map[string]string{
801+
"color": "red",
802+
},
803+
},
804+
},
805+
{
806+
Source: 2,
807+
Target: 3,
808+
Properties: EdgeProperties{
809+
Weight: 20,
810+
Attributes: map[string]string{
811+
"color": "green",
812+
},
813+
},
814+
},
815+
{
816+
Source: 3,
817+
Target: 1,
818+
Properties: EdgeProperties{
819+
Weight: 30,
820+
Attributes: map[string]string{
821+
"color": "blue",
822+
},
823+
},
824+
},
825+
},
826+
expectedEdges: []Edge[int]{
827+
{
828+
Source: 1,
829+
Target: 2,
830+
Properties: EdgeProperties{
831+
Weight: 10,
832+
Attributes: map[string]string{
833+
"color": "red",
834+
},
835+
},
836+
},
837+
},
838+
},
839+
}
840+
841+
for name, test := range tests {
842+
t.Run(name, func(t *testing.T) {
843+
g := New(IntHash, Directed())
844+
845+
for _, vertex := range test.vertices {
846+
_ = g.AddVertex(vertex)
847+
}
848+
849+
for _, edge := range test.edges {
850+
_ = g.AddEdge(copyEdge(edge))
851+
}
852+
853+
edges, err := g.OutEdges(test.vertices[0])
854+
if err != nil {
855+
t.Fatalf("unexpected error: %v", err.Error())
856+
}
857+
858+
for _, expectedEdge := range test.expectedEdges {
859+
for _, actualEdge := range edges {
860+
if actualEdge.Source != expectedEdge.Source || actualEdge.Target != expectedEdge.Target {
861+
continue
862+
}
863+
if !edgesAreEqual(expectedEdge, actualEdge, true) {
864+
t.Errorf("%s: expected edge %v, got %v", name, expectedEdge, actualEdge)
865+
}
866+
}
867+
}
868+
})
869+
}
870+
}
871+
872+
func TestDirected_InEdges(t *testing.T) {
873+
tests := map[string]struct {
874+
vertices []int
875+
edges []Edge[int]
876+
expectedEdges []Edge[int]
877+
}{
878+
"graph with 3 edges": {
879+
vertices: []int{1, 2, 3},
880+
edges: []Edge[int]{
881+
{
882+
Source: 1,
883+
Target: 2,
884+
Properties: EdgeProperties{
885+
Weight: 10,
886+
Attributes: map[string]string{
887+
"color": "red",
888+
},
889+
},
890+
},
891+
{
892+
Source: 2,
893+
Target: 3,
894+
Properties: EdgeProperties{
895+
Weight: 20,
896+
Attributes: map[string]string{
897+
"color": "green",
898+
},
899+
},
900+
},
901+
{
902+
Source: 3,
903+
Target: 1,
904+
Properties: EdgeProperties{
905+
Weight: 30,
906+
Attributes: map[string]string{
907+
"color": "blue",
908+
},
909+
},
910+
},
911+
},
912+
expectedEdges: []Edge[int]{
913+
{
914+
Source: 3,
915+
Target: 1,
916+
Properties: EdgeProperties{
917+
Weight: 30,
918+
Attributes: map[string]string{
919+
"color": "blue",
920+
},
921+
},
922+
},
923+
},
924+
},
925+
}
926+
927+
for name, test := range tests {
928+
t.Run(name, func(t *testing.T) {
929+
g := New(IntHash, Directed())
930+
931+
for _, vertex := range test.vertices {
932+
_ = g.AddVertex(vertex)
933+
}
934+
935+
for _, edge := range test.edges {
936+
_ = g.AddEdge(copyEdge(edge))
937+
}
938+
939+
edges, err := g.InEdges(test.vertices[0])
940+
if err != nil {
941+
t.Fatalf("unexpected error: %v", err.Error())
942+
}
943+
944+
for _, expectedEdge := range test.expectedEdges {
945+
for _, actualEdge := range edges {
946+
if actualEdge.Source != expectedEdge.Source || actualEdge.Target != expectedEdge.Target {
947+
continue
948+
}
949+
if !edgesAreEqual(expectedEdge, actualEdge, true) {
950+
t.Errorf("%s: expected edge %v, got %v", name, expectedEdge, actualEdge)
951+
}
952+
}
953+
}
954+
})
955+
}
956+
}
957+
786958
func TestDirected_UpdateEdge(t *testing.T) {
787959
tests := map[string]struct {
788960
vertices []int

graph.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ type Graph[K comparable, T any] interface {
130130
// Edge[K] and hence will contain the vertex hashes, not the vertex values.
131131
Edges() ([]Edge[K], error)
132132

133+
// InEdges returns a slice of all edges in the graph with a specific target vertex.
134+
// These edges are of type Edge[K] and hence will contain the vertex hashes, not the vertex values.
135+
InEdges(targetHash K) ([]Edge[K], error)
136+
137+
// OutEdges returns a slice of all edges in the graph with a specific source vertex.
138+
// These edges are of type Edge[K] and hence will contain the vertex hashes, not the vertex values.
139+
OutEdges(sourceHash K) ([]Edge[K], error)
140+
133141
// UpdateEdge updates the edge joining the two given vertices with the data
134142
// provided in the given functional options. Valid functional options are:
135143
// - EdgeWeight: Sets a new weight for the edge properties.

store.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ type Store[K comparable, T any] interface {
6363
// ListEdges should return all edges in the graph in a slice.
6464
ListEdges() ([]Edge[K], error)
6565

66+
// ListOutEdges should return all edges of a given source vertex in the graph in a slice.
67+
ListOutEdges(sourceHash K) ([]Edge[K], error)
68+
69+
// ListInEdges should return all edges of a given target vertex in the graph in a slice.
70+
ListInEdges(targetHash K) ([]Edge[K], error)
71+
6672
// EdgeCount should return the number of edges in the graph. This should be equal to the
6773
// length of the slice returned by ListEdges.
6874
EdgeCount() (int, error)
@@ -75,8 +81,8 @@ type memoryStore[K comparable, T any] struct {
7581

7682
// outEdges and inEdges store all outgoing and ingoing edges for all vertices. For O(1) access,
7783
// these edges themselves are stored in maps whose keys are the hashes of the target vertices.
78-
outEdges map[K]map[K]Edge[K] // source -> target
79-
inEdges map[K]map[K]Edge[K] // target -> source
84+
outEdges map[K]map[K]Edge[K] // source -> target
85+
inEdges map[K]map[K]Edge[K] // target -> source
8086
edgeCount int
8187
}
8288

@@ -254,6 +260,30 @@ func (s *memoryStore[K, T]) ListEdges() ([]Edge[K], error) {
254260
return res, nil
255261
}
256262

263+
func (s *memoryStore[K, T]) ListOutEdges(sourceHash K) ([]Edge[K], error) {
264+
s.lock.RLock()
265+
defer s.lock.RUnlock()
266+
267+
outEdges := s.outEdges[sourceHash]
268+
res := make([]Edge[K], 0, len(outEdges))
269+
for _, edge := range outEdges {
270+
res = append(res, edge)
271+
}
272+
return res, nil
273+
}
274+
275+
func (s *memoryStore[K, T]) ListInEdges(targetHash K) ([]Edge[K], error) {
276+
s.lock.RLock()
277+
defer s.lock.RUnlock()
278+
279+
inEdges := s.inEdges[targetHash]
280+
res := make([]Edge[K], 0, len(inEdges))
281+
for _, edge := range inEdges {
282+
res = append(res, edge)
283+
}
284+
return res, nil
285+
}
286+
257287
// CreatesCycle is a fastpath version of [CreatesCycle] that avoids calling
258288
// [PredecessorMap], which generates large amounts of garbage to collect.
259289
//

undirected.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ func (u *undirected[K, T]) Edges() ([]Edge[K], error) {
216216
return edges, nil
217217
}
218218

219+
func (d *undirected[K, T]) InEdges(targetHash K) ([]Edge[K], error) {
220+
return d.store.ListInEdges(targetHash)
221+
}
222+
223+
func (d *undirected[K, T]) OutEdges(sourceHash K) ([]Edge[K], error) {
224+
return d.store.ListOutEdges(sourceHash)
225+
}
226+
219227
func (u *undirected[K, T]) UpdateEdge(source, target K, options ...func(properties *EdgeProperties)) error {
220228
existingEdge, err := u.store.Edge(source, target)
221229
if err != nil {

0 commit comments

Comments
 (0)