diff --git a/README.md b/README.md index 75353bb..d86ca94 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,13 @@ structure which preserve and reuse previous versions. This uses a very functional, cons-style of list manipulation. Insert, get, remove, and size operations are O(n) as you would expect. +#### Simple Graph + +A mutable, non-persistent undirected graph where parallel edges and self-loops are +not permitted. Operations to add an edge as well as retrieve the total number of +vertices/edges are O(1) while the operation to retrieve the vertices adjacent to a +target is O(n). For more details see [wikipedia](https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Simple_graph) + ### Installation 1. Install Go 1.3 or higher. diff --git a/graph/simple.go b/graph/simple.go new file mode 100644 index 0000000..b01a1e3 --- /dev/null +++ b/graph/simple.go @@ -0,0 +1,136 @@ +/* +Copyright 2017 Julian Griggs + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package graph provides graph implementations. Currently, this includes an +undirected simple graph. +*/ +package graph + +import ( + "errors" + "sync" +) + +var ( + // ErrVertexNotFound is returned when an operation is requested on a + // non-existent vertex. + ErrVertexNotFound = errors.New("vertex not found") + + // ErrSelfLoop is returned when an operation tries to create a disallowed + // self loop. + ErrSelfLoop = errors.New("self loops not permitted") + + // ErrParallelEdge is returned when an operation tries to create a + // disallowed parallel edge. + ErrParallelEdge = errors.New("parallel edges are not permitted") +) + +// SimpleGraph is a mutable, non-persistent undirected graph. +// Parallel edges and self-loops are not permitted. +// Additional description: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Simple_graph +type SimpleGraph struct { + mutex sync.RWMutex + adjacencyList map[interface{}]map[interface{}]struct{} + v, e int +} + +// V returns the number of vertices in the SimpleGraph +func (g *SimpleGraph) V() int { + g.mutex.RLock() + defer g.mutex.RUnlock() + + return g.v +} + +// E returns the number of edges in the SimpleGraph +func (g *SimpleGraph) E() int { + g.mutex.RLock() + defer g.mutex.RUnlock() + + return g.e +} + +// AddEdge will create an edge between vertices v and w +func (g *SimpleGraph) AddEdge(v, w interface{}) error { + g.mutex.Lock() + defer g.mutex.Unlock() + + if v == w { + return ErrSelfLoop + } + + g.addVertex(v) + g.addVertex(w) + + if _, ok := g.adjacencyList[v][w]; ok { + return ErrParallelEdge + } + + g.adjacencyList[v][w] = struct{}{} + g.adjacencyList[w][v] = struct{}{} + g.e++ + return nil +} + +// Adj returns the list of all vertices connected to v +func (g *SimpleGraph) Adj(v interface{}) ([]interface{}, error) { + g.mutex.RLock() + defer g.mutex.RUnlock() + + deg, err := g.Degree(v) + if err != nil { + return nil, ErrVertexNotFound + } + + adj := make([]interface{}, deg) + i := 0 + for key := range g.adjacencyList[v] { + adj[i] = key + i++ + } + return adj, nil +} + +// Degree returns the number of vertices connected to v +func (g *SimpleGraph) Degree(v interface{}) (int, error) { + g.mutex.RLock() + defer g.mutex.RUnlock() + + val, ok := g.adjacencyList[v] + if !ok { + return 0, ErrVertexNotFound + } + return len(val), nil +} + +func (g *SimpleGraph) addVertex(v interface{}) { + mm, ok := g.adjacencyList[v] + if !ok { + mm = make(map[interface{}]struct{}) + g.adjacencyList[v] = mm + g.v++ + } +} + +// NewSimpleGraph creates and returns a SimpleGraph +func NewSimpleGraph() *SimpleGraph { + return &SimpleGraph{ + adjacencyList: make(map[interface{}]map[interface{}]struct{}), + v: 0, + e: 0, + } +} diff --git a/graph/simple_test.go b/graph/simple_test.go new file mode 100644 index 0000000..9a0edbc --- /dev/null +++ b/graph/simple_test.go @@ -0,0 +1,246 @@ +/* +Copyright 2017 Julian Griggs + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package graph + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestV(t *testing.T) { + assert := assert.New(t) + sgraph := NewSimpleGraph() + assert.Equal(0, sgraph.V()) + + sgraph.AddEdge("A", "B") + assert.Equal(2, sgraph.V()) + + sgraph.AddEdge("B", "C") + assert.Equal(3, sgraph.V()) + + sgraph.AddEdge("A", "C") + assert.Equal(3, sgraph.V()) + + // Parallel edges not allowed + sgraph.AddEdge("A", "C") + assert.Equal(3, sgraph.V()) + sgraph.AddEdge("C", "A") + assert.Equal(3, sgraph.V()) + + // Self loops not allowed + sgraph.AddEdge("C", "C") + assert.Equal(3, sgraph.V()) + sgraph.AddEdge("D", "D") + assert.Equal(3, sgraph.V()) +} + +func TestE(t *testing.T) { + assert := assert.New(t) + sgraph := NewSimpleGraph() + + assert.Equal(0, sgraph.E()) + + sgraph.AddEdge("A", "B") + assert.Equal(1, sgraph.E()) + + sgraph.AddEdge("B", "C") + assert.Equal(2, sgraph.E()) + + sgraph.AddEdge("A", "C") + assert.Equal(3, sgraph.E()) + + // Parallel edges not allowed + sgraph.AddEdge("A", "C") + assert.Equal(3, sgraph.E()) + sgraph.AddEdge("C", "A") + assert.Equal(3, sgraph.E()) + + // Self loops not allowed so no edges added + sgraph.AddEdge("C", "C") + assert.Equal(3, sgraph.E()) + sgraph.AddEdge("D", "D") + assert.Equal(3, sgraph.E()) +} + +func TestDegree(t *testing.T) { + assert := assert.New(t) + sgraph := NewSimpleGraph() + + // No edges added so degree is 0 + v, err := sgraph.Degree("A") + assert.Zero(v) + assert.Error(err) + + // One edge added + sgraph.AddEdge("A", "B") + v, err = sgraph.Degree("A") + assert.Equal(1, v) + assert.Nil(err) + + // Self loops are not allowed + sgraph.AddEdge("A", "A") + v, err = sgraph.Degree("A") + assert.Equal(1, v) + assert.Nil(err) + + // Parallel edges are not allowed + sgraph.AddEdge("A", "B") + v, err = sgraph.Degree("A") + assert.Equal(1, v) + assert.Nil(err) + sgraph.AddEdge("B", "A") + v, err = sgraph.Degree("A") + assert.Equal(1, v) + assert.Nil(err) + + v, err = sgraph.Degree("B") + assert.Equal(1, v) + assert.Nil(err) + + sgraph.AddEdge("C", "D") + sgraph.AddEdge("A", "C") + sgraph.AddEdge("E", "F") + sgraph.AddEdge("E", "G") + sgraph.AddEdge("H", "G") + + v, err = sgraph.Degree("A") + assert.Equal(2, v) + assert.Nil(err) + + v, err = sgraph.Degree("B") + assert.Equal(1, v) + assert.Nil(err) + + v, err = sgraph.Degree("C") + assert.Equal(2, v) + assert.Nil(err) + + v, err = sgraph.Degree("D") + assert.Equal(1, v) + assert.Nil(err) + + v, err = sgraph.Degree("E") + assert.Equal(2, v) + assert.Nil(err) + + v, err = sgraph.Degree("G") + assert.Equal(2, v) + assert.Nil(err) +} + +func TestAddEdge(t *testing.T) { + assert := assert.New(t) + sgraph := NewSimpleGraph() + + err := sgraph.AddEdge("A", "B") + assert.Nil(err) + + err = sgraph.AddEdge("A", "B") + assert.Error(err) + + err = sgraph.AddEdge("B", "A") + assert.Error(err) + + err = sgraph.AddEdge("A", "A") + assert.Error(err) + + err = sgraph.AddEdge("C", "C") + assert.Error(err) + + err = sgraph.AddEdge("B", "C") + assert.Nil(err) + +} + +func TestAdj(t *testing.T) { + assert := assert.New(t) + sgraph := NewSimpleGraph() + + v, err := sgraph.Adj("A") + assert.Zero(v) + assert.Error(err) + + // Self loops not allowed + sgraph.AddEdge("A", "A") + v, err = sgraph.Adj("A") + assert.Zero(v) + assert.Error(err) + + sgraph.AddEdge("A", "B") + v, err = sgraph.Adj("A") + assert.Equal(1, len(v)) + assert.Nil(err) + assert.Equal("B", v[0]) + + v, err = sgraph.Adj("B") + assert.Equal(1, len(v)) + assert.Nil(err) + assert.Equal("A", v[0]) + + // Parallel Edges not allowed + sgraph.AddEdge("A", "B") + sgraph.AddEdge("B", "A") + v, err = sgraph.Adj("B") + assert.Equal(1, len(v)) + assert.Nil(err) + assert.Equal("A", v[0]) + + sgraph.AddEdge("C", "D") + sgraph.AddEdge("A", "C") + sgraph.AddEdge("E", "F") + sgraph.AddEdge("E", "G") + sgraph.AddEdge("H", "G") + + v, err = sgraph.Adj("A") + assert.Equal(2, len(v)) + assert.Nil(err) + assert.Contains(v, "B") + assert.Contains(v, "C") + assert.NotContains(v, "A") + assert.NotContains(v, "D") + + v, err = sgraph.Adj("B") + assert.Equal(1, len(v)) + assert.Nil(err) + assert.Contains(v, "A") + assert.NotContains(v, "B") + assert.NotContains(v, "C") + assert.NotContains(v, "D") + + v, err = sgraph.Adj("C") + assert.Equal(2, len(v)) + assert.Nil(err) + assert.Contains(v, "A") + assert.Contains(v, "D") + assert.NotContains(v, "B") + assert.NotContains(v, "C") + + v, err = sgraph.Adj("E") + assert.Equal(2, len(v)) + assert.Nil(err) + assert.Contains(v, "F") + assert.Contains(v, "G") + assert.NotContains(v, "A") + + v, err = sgraph.Adj("G") + assert.Equal(2, len(v)) + assert.Nil(err) + assert.Contains(v, "E") + assert.Contains(v, "H") + assert.NotContains(v, "A") +}