Skip to content

Commit

Permalink
Implement Graph.RemoveVertex (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikbraun committed Apr 13, 2023
1 parent 31d566b commit dadf507
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.18.0] - 2023-04-16

### Added
* Added the `Graph.RemoveVertex` method for removing a vertex.
* Added the `Store.RemoveVertex` method for removing a vertex.
* Added the `ErrVertexHasEdges` error instance.

## [0.17.0] - 2023-04-12

### Added
Expand Down
4 changes: 4 additions & 0 deletions directed.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ func (d *directed[K, T]) VertexWithProperties(hash K) (T, VertexProperties, erro
return vertex, properties, nil
}

func (d *directed[K, T]) RemoveVertex(hash K) error {
return d.store.RemoveVertex(hash)
}

func (d *directed[K, T]) AddEdge(sourceHash, targetHash K, options ...func(*EdgeProperties)) error {
_, _, err := d.store.Vertex(sourceHash)
if err != nil {
Expand Down
50 changes: 50 additions & 0 deletions directed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,56 @@ func TestDirected_Vertex(t *testing.T) {
}
}

func TestDirected_RemoveVertex(t *testing.T) {
tests := map[string]struct {
vertices []int
edges []Edge[int]
vertex int
expectedError error
}{
"existing disconnected vertex": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{
{Source: 2, Target: 3},
},
vertex: 1,
},
"existing connected vertex": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{
{Source: 1, Target: 2},
{Source: 2, Target: 3},
},
vertex: 1,
expectedError: ErrVertexHasEdges,
},
"non-existent vertex": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{},
vertex: 4,
expectedError: ErrVertexNotFound,
},
}

for name, test := range tests {
graph := New(IntHash, Directed())

for _, vertex := range test.vertices {
_ = graph.AddVertex(vertex)
}

for _, edge := range test.edges {
_ = graph.AddEdge(edge.Source, edge.Target)
}

err := graph.RemoveVertex(test.vertex)

if !errors.Is(err, test.expectedError) {
t.Errorf("%s: error expectancy doesn't match: expected %v, got %v", name, test.expectedError, err)
}
}
}

func TestDirected_AddEdge(t *testing.T) {
tests := map[string]struct {
vertices []int
Expand Down
8 changes: 8 additions & 0 deletions graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var (
ErrEdgeNotFound = errors.New("edge not found")
ErrEdgeAlreadyExists = errors.New("edge already exists")
ErrEdgeCreatesCycle = errors.New("edge would create a cycle")
ErrVertexHasEdges = errors.New("vertex has edges")
)

// Graph represents a generic graph data structure consisting of vertices of
Expand Down Expand Up @@ -84,6 +85,13 @@ type Graph[K comparable, T any] interface {
// its properties or ErrVertexNotFound if it doesn't exist.
VertexWithProperties(hash K) (T, VertexProperties, error)

// RemoveVertex removes the vertex with the given hash value from the graph.
//
// The vertex is not allowed to have edges and thus must be disconnected.
// Potential edges must be removed first. Otherwise, ErrVertexHasEdges will
// be returned. If the vertex doesn't exist, ErrVertexNotFound is returned.
RemoveVertex(hash K) error

// AddEdge creates an edge between the source and the target vertex.
//
// If either vertex cannot be found, ErrVertexNotFound will be returned. If
Expand Down
32 changes: 29 additions & 3 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ type Store[K comparable, T any] interface {
// vertex doesn't exist, ErrVertexNotFound should be returned.
Vertex(hash K) (T, VertexProperties, error)

// RemoveVertex should remove the vertex with the given hash value. If the vertex doesn't
// exist, ErrVertexNotFound should be returned. If the vertex has edges to other vertices,
// ErrVertexHasEdges should be returned.
RemoveVertex(hash K) error

// ListVertices should return all vertices in the graph in a slice.
ListVertices() ([]K, error)

Expand Down Expand Up @@ -109,17 +114,38 @@ func (s *memoryStore[K, T]) Vertex(k K) (T, VertexProperties, error) {
s.lock.RLock()
defer s.lock.RUnlock()

var v T
var ok bool
v, ok = s.vertices[k]
v, ok := s.vertices[k]
if !ok {
return v, VertexProperties{}, ErrVertexNotFound
}

p := s.vertexProperties[k]

return v, p, nil
}

func (s *memoryStore[K, T]) RemoveVertex(k K) error {
s.lock.RLock()
defer s.lock.RUnlock()

if _, ok := s.vertices[k]; !ok {
return ErrVertexNotFound
}

if _, ok := s.inEdges[k]; ok {
return ErrVertexHasEdges
}

if _, ok := s.outEdges[k]; ok {
return ErrVertexHasEdges
}

delete(s.vertices, k)
delete(s.vertexProperties, k)

return nil
}

func (s *memoryStore[K, T]) AddEdge(sourceHash, targetHash K, edge Edge[K]) error {
s.lock.Lock()
defer s.lock.Unlock()
Expand Down
4 changes: 4 additions & 0 deletions undirected.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ func (u *undirected[K, T]) VertexWithProperties(hash K) (T, VertexProperties, er
return vertex, prop, nil
}

func (u *undirected[K, T]) RemoveVertex(hash K) error {
return u.store.RemoveVertex(hash)
}

func (u *undirected[K, T]) AddEdge(sourceHash, targetHash K, options ...func(*EdgeProperties)) error {
if _, _, err := u.store.Vertex(sourceHash); err != nil {
return fmt.Errorf("could not find source vertex with hash %v: %w", sourceHash, err)
Expand Down
50 changes: 50 additions & 0 deletions undirected_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,56 @@ func TestUndirected_AddEdge(t *testing.T) {
}
}

func TestUndirected_RemoveVertex(t *testing.T) {
tests := map[string]struct {
vertices []int
edges []Edge[int]
vertex int
expectedError error
}{
"existing disconnected vertex": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{
{Source: 2, Target: 3},
},
vertex: 1,
},
"existing connected vertex": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{
{Source: 1, Target: 2},
{Source: 2, Target: 3},
},
vertex: 1,
expectedError: ErrVertexHasEdges,
},
"non-existent vertex": {
vertices: []int{1, 2, 3},
edges: []Edge[int]{},
vertex: 4,
expectedError: ErrVertexNotFound,
},
}

for name, test := range tests {
graph := New(IntHash)

for _, vertex := range test.vertices {
_ = graph.AddVertex(vertex)
}

for _, edge := range test.edges {
_ = graph.AddEdge(edge.Source, edge.Target)
}

err := graph.RemoveVertex(test.vertex)

if !errors.Is(err, test.expectedError) {
t.Errorf("%s: error expectancy doesn't match: expected %v, got %v", name, test.expectedError, err)
}
}
}

func TestUndirected_Edge(t *testing.T) {
tests := map[string]struct {
vertices []int
Expand Down

0 comments on commit dadf507

Please sign in to comment.