Skip to content

Commit

Permalink
Merge pull request #11 from hmdsefi/dev
Browse files Browse the repository at this point in the history
restructure the code and implement undirected graph
  • Loading branch information
hmdsefi committed Feb 25, 2023
2 parents ec5dac1 + f062af0 commit e1e6764
Show file tree
Hide file tree
Showing 15 changed files with 435 additions and 294 deletions.
55 changes: 0 additions & 55 deletions graph/acyclic.go
Original file line number Diff line number Diff line change
@@ -1,60 +1,5 @@
package graph

// dag represents a directed graph that has no cycles. It is a graph
// where there is no path that starts and ends at the same vertex.
type dag[T comparable] struct {
*base[T]
}

func NewDAG[T comparable]() Graph[T] {
return &dag[T]{
base: newBaseGraph[T](),
}
}

// AddEdge adds a directed edges from the vertex with the 'from' label to
// the vertex with the 'to' label, after checking if the edges would create
// a cycle.
//
// AddEdge guarantees that the graph remain dag after adding new edges.
//
// If it finds a cycle between 'from' and 'to', returns error.
// If edge already exist, returns error.
func (g *dag[T]) AddEdge(from, to *Vertex[T]) (*Edge[T], error) {
if from == nil || to == nil {
return nil, ErrNilVertices
}

if g.findVertex(from.label) == nil {
g.AddVertex(from)
}

if g.findVertex(to.label) == nil {
g.AddVertex(to)
}

// prevent edge-multiplicity
if g.ContainsEdge(from, to) {
return nil, ErrEdgeAlreadyExists
}

// Add the new edges
from.neighbors = append(from.neighbors, to)
to.inDegree++

// If topological sort returns an error, new edges created a cycle
_, err := TopologySort[T](g)
if err != nil {
// Remove the new edges
from.neighbors = from.neighbors[:len(from.neighbors)-1]
to.inDegree--

return nil, ErrDAGCycle
}

return g.addToEdgeMap(from, to), nil
}

// TopologySort performs a topological sort of the graph using
// Kahn's algorithm. If the sorted list of vertices does not contain
// all vertices in the graph, it means there is a cycle in the graph.
Expand Down
36 changes: 2 additions & 34 deletions graph/acyclic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,9 @@ import (
"testing"
)

func TestDAG_AddEdge(t *testing.T) {
// Create a new dag
g := NewDAG[int]()

// Create three vertices with labels 1, 2, and 3
v1 := g.AddVertexByLabel(1)
v2 := g.AddVertexByLabel(2)
v3 := g.AddVertexByLabel(3)

// Add the vertices to the dag
g.AddVertex(v1)
g.AddVertex(v2)
g.AddVertex(v3)

// Add edges from 1 to 2 and from 2 to 3
_, err := g.AddEdge(v1, v2)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

_, err = g.AddEdge(v2, v3)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

// Try to add an edges from 3 to 1, which should result in an error
_, err = g.AddEdge(v3, v1)
if err == nil {
t.Error("Expected error, but got none")
}
}

func TestDAG_TopologySort(t *testing.T) {
// Create a dag with 6 vertices and 6 edges
g := NewDAG[int]()
g := New[int](Acyclic())
v0 := g.AddVertexByLabel(0)
v1 := g.AddVertexByLabel(1)
v2 := g.AddVertexByLabel(2)
Expand Down Expand Up @@ -78,7 +46,7 @@ func TestDAG_TopologySort(t *testing.T) {
}

// Perform a topological sort
sortedVertices, err := TopologySort(g)
sortedVertices, err := TopologySort[int](g)

// Check that there was no error
if err != nil {
Expand Down
105 changes: 80 additions & 25 deletions graph/base.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package graph

// base represents a basic graph. It stores a slice of
// baseGraph represents a basic graph. It stores a slice of
// pointers to all vertices.
type base[T comparable] struct {
type baseGraph[T comparable] struct {
// vertices is a map of vertices of the graph. the key of the map
// is the vertex label.
vertices map[T]*Vertex[T]
Expand All @@ -11,20 +11,23 @@ type base[T comparable] struct {
// first map is the label of source vertex and the key of the inner
// map is the label of destination vertex.
edges map[T]map[T]*Edge[T]

properties GraphProperties
}

func newBaseGraph[T comparable]() *base[T] {
return &base[T]{
vertices: make(map[T]*Vertex[T]),
edges: make(map[T]map[T]*Edge[T]),
func newBaseGraph[T comparable](properties GraphProperties) *baseGraph[T] {
return &baseGraph[T]{
vertices: make(map[T]*Vertex[T]),
edges: make(map[T]map[T]*Edge[T]),
properties: properties,
}
}

// addToEdgeMap creates a new edge struct and adds it to the edges map inside
// the base struct. Note that it doesn't add the neighbor to the source vertex.
// the baseGraph struct. Note that it doesn't add the neighbor to the source vertex.
//
// It returns the created edge.
func (g *base[T]) addToEdgeMap(from, to *Vertex[T]) *Edge[T] {
func (g *baseGraph[T]) addToEdgeMap(from, to *Vertex[T]) *Edge[T] {
edge := NewEdge(from, to)
if _, ok := g.edges[from.label]; !ok {
g.edges[from.label] = map[T]*Edge[T]{to.label: edge}
Expand All @@ -35,10 +38,62 @@ func (g *base[T]) addToEdgeMap(from, to *Vertex[T]) *Edge[T] {
return edge
}

// AddEdge adds a directed edges from the vertex with the 'from' label to
// the vertex with the 'to' label by appending the 'to' vertex to the
// 'neighbors' slice of the 'from' vertex.
//
// It creates the input vertices if they don't exist in the graph.
// If any of the specified vertices is nil, returns nil.
// If edge already exist, returns error.
func (g *baseGraph[T]) AddEdge(from, to *Vertex[T]) (*Edge[T], error) {
if from == nil || to == nil {
return nil, ErrNilVertices
}

if g.findVertex(from.label) == nil {
g.AddVertex(from)
}

if g.findVertex(to.label) == nil {
g.AddVertex(to)
}

// prevent edge-multiplicity
if g.ContainsEdge(from, to) {
return nil, ErrEdgeAlreadyExists
}

from.neighbors = append(from.neighbors, to)
to.inDegree++

// prevent cycle creation, if graph is acyclic
if g.properties.isAcyclic {
// If topological sort returns an error, new edges created a cycle
_, err := TopologySort[T](g)
if err != nil {
// Remove the new edges
from.neighbors = from.neighbors[:len(from.neighbors)-1]
to.inDegree--

return nil, ErrDAGCycle
}
}

// add "from" to the "to" vertex neighbor slice, if graph is undirected.
if !g.properties.isDirected {
to.neighbors = append(to.neighbors, from)
from.inDegree++

g.addToEdgeMap(to, from)
}

return g.addToEdgeMap(from, to), nil
}

// AddVertexByLabel adds a new vertex with the given label to the graph.
// If there is a vertex with the same label in the graph, returns nil.
// Otherwise, returns the created vertex.
func (g *base[T]) AddVertexByLabel(label T) *Vertex[T] {
func (g *baseGraph[T]) AddVertexByLabel(label T) *Vertex[T] {
v := g.addVertex(&Vertex[T]{label: label})

return v
Expand All @@ -47,15 +102,15 @@ func (g *base[T]) AddVertexByLabel(label T) *Vertex[T] {
// AddVertex adds the input vertex to the graph. It doesn't add
// vertex to the graph if the input vertex label is already exists
// in the graph.
func (g *base[T]) AddVertex(v *Vertex[T]) {
func (g *baseGraph[T]) AddVertex(v *Vertex[T]) {
if v == nil {
return
}

g.addVertex(v)
}

func (g *base[T]) addVertex(v *Vertex[T]) *Vertex[T] {
func (g *baseGraph[T]) addVertex(v *Vertex[T]) *Vertex[T] {
if _, ok := g.vertices[v.label]; ok {
return nil
}
Expand All @@ -64,7 +119,7 @@ func (g *base[T]) addVertex(v *Vertex[T]) *Vertex[T] {
return v
}

func (g *base[T]) findVertex(label T) *Vertex[T] {
func (g *baseGraph[T]) findVertex(label T) *Vertex[T] {
return g.vertices[label]
}

Expand All @@ -74,7 +129,7 @@ func (g *base[T]) findVertex(label T) *Vertex[T] {
// If any of the specified vertices is nil, returns nil.
// If any of the vertices does not exist, returns nil.
// If both vertices exist but no edges found, returns an empty set.
func (g *base[T]) GetAllEdges(from, to *Vertex[T]) []*Edge[T] {
func (g *baseGraph[T]) GetAllEdges(from, to *Vertex[T]) []*Edge[T] {
if from == nil || to == nil {
return nil
}
Expand Down Expand Up @@ -105,7 +160,7 @@ func (g *base[T]) GetAllEdges(from, to *Vertex[T]) []*Edge[T] {
//
// If any of the specified vertices is nil, returns nil.
// If edge does not exist, returns nil.
func (g *base[T]) GetEdge(from, to *Vertex[T]) *Edge[T] {
func (g *baseGraph[T]) GetEdge(from, to *Vertex[T]) *Edge[T] {
if from == nil || to == nil {
return nil
}
Expand All @@ -130,7 +185,7 @@ func (g *base[T]) GetEdge(from, to *Vertex[T]) *Edge[T] {
//
// If the input vertex is nil, returns nil.
// If the input vertex does not exist, returns nil.
func (g *base[T]) EdgesOf(v *Vertex[T]) []*Edge[T] {
func (g *baseGraph[T]) EdgesOf(v *Vertex[T]) []*Edge[T] {
if v == nil {
return nil
}
Expand Down Expand Up @@ -166,15 +221,15 @@ func (g *base[T]) EdgesOf(v *Vertex[T]) []*Edge[T] {
}

// RemoveEdges removes input edges from the graph if they exist.
func (g *base[T]) RemoveEdges(edges ...*Edge[T]) {
func (g *baseGraph[T]) RemoveEdges(edges ...*Edge[T]) {
for i := range edges {
g.removeEdge(edges[i])
}
}

// removeEdge removes the edge from edges destination map, if size of
// the internal map is zero, removes the source label from the edges.
func (g *base[T]) removeEdge(edge *Edge[T]) {
func (g *baseGraph[T]) removeEdge(edge *Edge[T]) {
if edge == nil {
return
}
Expand All @@ -201,7 +256,7 @@ func (g *base[T]) removeEdge(edge *Edge[T]) {
}
}

func (g *base[T]) removeNeighbor(sourceID, neighborLbl T) {
func (g *baseGraph[T]) removeNeighbor(sourceID, neighborLbl T) {
source := g.findVertex(sourceID)
for i := range source.neighbors {
if source.neighbors[i].label == neighborLbl {
Expand All @@ -223,14 +278,14 @@ func (g *base[T]) removeNeighbor(sourceID, neighborLbl T) {
// GetVertexByID returns the vertex with the input label.
//
// If vertex doesn't exist, returns nil.
func (g *base[T]) GetVertexByID(label T) *Vertex[T] {
func (g *baseGraph[T]) GetVertexByID(label T) *Vertex[T] {
return g.findVertex(label)
}

// GetAllVerticesByID returns a slice of vertices with the input label list.
//
// If vertex doesn't exist, doesn't add nil to the output list.
func (g *base[T]) GetAllVerticesByID(idList ...T) []*Vertex[T] {
func (g *baseGraph[T]) GetAllVerticesByID(idList ...T) []*Vertex[T] {
var vertices []*Vertex[T]
for _, label := range idList {
v := g.GetVertexByID(label)
Expand All @@ -243,7 +298,7 @@ func (g *base[T]) GetAllVerticesByID(idList ...T) []*Vertex[T] {
}

// GetAllVertices returns a slice of all existing vertices in the graph.
func (g *base[T]) GetAllVertices() []*Vertex[T] {
func (g *baseGraph[T]) GetAllVertices() []*Vertex[T] {
var vertices []*Vertex[T]
for _, vertex := range g.vertices {
vertices = append(vertices, vertex)
Expand All @@ -254,13 +309,13 @@ func (g *base[T]) GetAllVertices() []*Vertex[T] {

// RemoveVertices removes all the specified vertices from this graph including
// all its touching edges if present.
func (g *base[T]) RemoveVertices(vertices ...*Vertex[T]) {
func (g *baseGraph[T]) RemoveVertices(vertices ...*Vertex[T]) {
for i := range vertices {
g.removeVertex(vertices[i])
}
}

func (g *base[T]) removeVertex(in *Vertex[T]) {
func (g *baseGraph[T]) removeVertex(in *Vertex[T]) {
if in == nil {
return
}
Expand Down Expand Up @@ -290,7 +345,7 @@ func (g *base[T]) removeVertex(in *Vertex[T]) {
//
// If any of the specified vertices does not exist in the graph, or if is nil,
// returns 'false'.
func (g *base[T]) ContainsEdge(from, to *Vertex[T]) bool {
func (g *baseGraph[T]) ContainsEdge(from, to *Vertex[T]) bool {
if from == nil || to == nil {
return false
}
Expand All @@ -315,7 +370,7 @@ func (g *base[T]) ContainsEdge(from, to *Vertex[T]) bool {
// ContainsVertex returns 'true' if this graph contains the specified vertex.
//
// If the specified vertex is nil, returns 'false'.
func (g *base[T]) ContainsVertex(v *Vertex[T]) bool {
func (g *baseGraph[T]) ContainsVertex(v *Vertex[T]) bool {
if v == nil {
return false
}
Expand Down
Loading

0 comments on commit e1e6764

Please sign in to comment.