diff --git a/pkg/dag/dag.go b/pkg/dag/dag.go new file mode 100644 index 000000000..81bedd52f --- /dev/null +++ b/pkg/dag/dag.go @@ -0,0 +1,222 @@ +/* + * Copyright 2022 The Dragonfly Authors + * + * 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 dag + +import ( + "errors" + "sync" +) + +var ( + // ErrVertexNotFound represents vertex not found. + ErrVertexNotFound = errors.New("vertex not found") + + // ErrVertexAlreadyExists represents vertex already exists. + ErrVertexAlreadyExists = errors.New("vertex already exists") + + // ErrParnetAlreadyExists represents parent of vertex already exists. + ErrParnetAlreadyExists = errors.New("parent of vertex already exists") + + // ErrChildAlreadyExists represents child of vertex already exists. + ErrChildAlreadyExists = errors.New("child of vertex already exists") + + // ErrCycleBetweenVertices represents cycle between vertices. + ErrCycleBetweenVertices = errors.New("cycle between vertices") +) + +// DAG is the interface used for directed acyclic graph. +type DAG interface { + // AddVertex adds vertex to graph. + AddVertex(id string, value any) error + + // DeleteVertex deletes vertex graph. + DeleteVertex(id string) + + // GetVertex gets vertex from graph. + GetVertex(id string) (*Vertex, error) + + // AddEdge adds edge between two vertices. + AddEdge(fromVertexID, toVertexID string) error + + // DeleteEdge deletes edge between two vertices. + DeleteEdge(fromVertexID, toVertexID string) error +} + +// dag provides directed acyclic graph function. +type dag struct { + mu sync.RWMutex + vertices map[string]*Vertex +} + +// New returns a new DAG interface. +func NewDAG() DAG { + return &dag{ + vertices: make(map[string]*Vertex), + } +} + +// AddVertex adds vertex to graph. +func (d *dag) AddVertex(id string, value any) error { + d.mu.Lock() + defer d.mu.Unlock() + + if _, ok := d.vertices[id]; ok { + return ErrVertexAlreadyExists + } + + d.vertices[id] = NewVertex(id, value) + return nil +} + +// DeleteVertex deletes vertex graph. +func (d *dag) DeleteVertex(id string) { + d.mu.Lock() + defer d.mu.Unlock() + + vertex, ok := d.vertices[id] + if !ok { + return + } + + vertex.Parents.Range(func(item any) bool { + parent, ok := item.(*Vertex) + if !ok { + return true + } + + parent.Children.Delete(vertex) + return true + }) + + vertex.Children.Range(func(item any) bool { + child, ok := item.(*Vertex) + if !ok { + return true + } + + child.Parents.Delete(vertex) + return true + }) + + delete(d.vertices, id) +} + +// GetVertex gets vertex from graph. +func (d *dag) GetVertex(id string) (*Vertex, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + vertex, ok := d.vertices[id] + if !ok { + return nil, ErrVertexNotFound + } + + return vertex, nil +} + +// AddEdge adds edge between two vertices. +func (d *dag) AddEdge(fromVertexID, toVertexID string) error { + d.mu.Lock() + defer d.mu.Unlock() + + if fromVertexID == toVertexID { + return ErrCycleBetweenVertices + } + + fromVertex, ok := d.vertices[fromVertexID] + if !ok { + return ErrVertexNotFound + } + + toVertex, ok := d.vertices[toVertexID] + if !ok { + return ErrVertexNotFound + } + + for _, child := range fromVertex.Children.Values() { + vertex, ok := child.(*Vertex) + if !ok { + continue + } + + if vertex.ID == toVertexID { + return ErrCycleBetweenVertices + } + } + + if d.depthFirstSearch(toVertexID, fromVertexID) { + return ErrCycleBetweenVertices + } + + if ok := fromVertex.Children.Add(toVertex); !ok { + return ErrChildAlreadyExists + } + + if ok := toVertex.Parents.Add(fromVertex); !ok { + return ErrParnetAlreadyExists + } + + return nil +} + +// DeleteEdge deletes edge between two vertices. +func (d *dag) DeleteEdge(fromVertexID, toVertexID string) error { + d.mu.Lock() + defer d.mu.Unlock() + + fromVertex, ok := d.vertices[fromVertexID] + if !ok { + return ErrVertexNotFound + } + + toVertex, ok := d.vertices[toVertexID] + if !ok { + return ErrVertexNotFound + } + + fromVertex.Children.Delete(toVertex) + toVertex.Parents.Delete(fromVertex) + return nil +} + +// depthFirstSearch is a depth-first search of the directed acyclic graph. +func (d *dag) depthFirstSearch(fromVertexID, toVertexID string) bool { + successors := make(map[string]struct{}) + d.search(fromVertexID, successors) + _, ok := successors[toVertexID] + return ok +} + +// depthFirstSearch finds successors of vertex. +func (d *dag) search(vertexID string, successors map[string]struct{}) { + vertex, ok := d.vertices[vertexID] + if !ok { + return + } + + for _, child := range vertex.Children.Values() { + vertex, ok := child.(*Vertex) + if !ok { + continue + } + + if _, ok := successors[vertex.ID]; !ok { + successors[vertex.ID] = struct{}{} + d.search(vertex.ID, successors) + } + } +} diff --git a/pkg/dag/dag_test.go b/pkg/dag/dag_test.go new file mode 100644 index 000000000..de0bdf377 --- /dev/null +++ b/pkg/dag/dag_test.go @@ -0,0 +1,560 @@ +/* + * Copyright 2022 The Dragonfly Authors + * + * 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 dag + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewDAG(t *testing.T) { + d := NewDAG() + assert := assert.New(t) + assert.Equal(reflect.TypeOf(d).Elem().Name(), "dag") +} + +func TestDAGAddVertex(t *testing.T) { + tests := []struct { + name string + id string + value any + expect func(t *testing.T, d DAG, err error) + }{ + { + name: "add vertex", + id: mockVertexID, + value: mockVertexValue, + expect: func(t *testing.T, d DAG, err error) { + assert := assert.New(t) + assert.NoError(err) + }, + }, + { + name: "vertex already exists", + id: mockVertexID, + value: mockVertexValue, + expect: func(t *testing.T, d DAG, err error) { + assert := assert.New(t) + assert.NoError(err) + + assert.EqualError(d.AddVertex(mockVertexID, mockVertexValue), ErrVertexAlreadyExists.Error()) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDAG() + tc.expect(t, d, d.AddVertex(tc.id, tc.name)) + }) + } +} + +func TestDAGDeleteVertex(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DAG) + }{ + { + name: "delete vertex", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + d.DeleteVertex(mockVertexID) + + _, err := d.GetVertex(mockVertexID) + assert.EqualError(err, ErrVertexNotFound.Error()) + }, + }, + { + name: "delete vertex with edges", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + + var ( + mockToVertexID = "baz" + ) + if err := d.AddVertex(mockToVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexID, mockToVertexID); err != nil { + assert.NoError(err) + } + + d.DeleteVertex(mockVertexID) + + _, err := d.GetVertex(mockVertexID) + assert.EqualError(err, ErrVertexNotFound.Error()) + + vertex, err := d.GetVertex(mockToVertexID) + assert.NoError(err) + assert.Equal(vertex.Parents.Len(), uint(0)) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDAG() + tc.expect(t, d) + }) + } +} + +func TestDAGGetVertex(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DAG) + }{ + { + name: "get vertex", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil { + assert.NoError(err) + } + + vertex, err := d.GetVertex(mockVertexID) + assert.NoError(err) + assert.Equal(vertex.ID, mockVertexID) + assert.Equal(vertex.Value, mockVertexValue) + assert.Equal(vertex.Parents.Len(), uint(0)) + assert.Equal(vertex.Children.Len(), uint(0)) + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + _, err := d.GetVertex(mockVertexID) + assert.EqualError(err, ErrVertexNotFound.Error()) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDAG() + tc.expect(t, d) + }) + } +} + +func TestDAGAddEdge(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DAG) + }{ + { + name: "add edge", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + mockVertexGID = "bag" + mockVertexHID = "bah" + mockVertexIID = "bai" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexHID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexGID, mockVertexIID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexIID, mockVertexHID); err != nil { + assert.NoError(err) + } + }, + }, + { + name: "cycle between vertices", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + mockVertexGID = "bag" + mockVertexHID = "bah" + mockVertexIID = "bai" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexGID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexHID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexIID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + + if err := d.AddEdge(mockVertexFID, mockVertexGID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexGID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + + if err := d.AddEdge(mockVertexGID, mockVertexHID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexHID, mockVertexIID); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexIID, mockVertexEID); err != nil { + assert.EqualError(err, ErrCycleBetweenVertices.Error()) + } + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + + if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDAG() + tc.expect(t, d) + }) + } +} + +func TestDAGDeleteEdge(t *testing.T) { + tests := []struct { + name string + expect func(t *testing.T, d DAG) + }{ + { + name: "delete edge", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddVertex(mockVertexFID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + if err := d.DeleteEdge(mockVertexFID, mockVertexEID); err != nil { + assert.NoError(err) + } + + vf, err := d.GetVertex(mockVertexFID) + assert.NoError(err) + assert.Equal(vf.Parents.Len(), uint(1)) + ve, err := d.GetVertex(mockVertexEID) + assert.NoError(err) + assert.Equal(ve.Children.Len(), uint(1)) + + if err := d.DeleteEdge(mockVertexEID, mockVertexFID); err != nil { + assert.NoError(err) + } + + vf, err = d.GetVertex(mockVertexFID) + assert.NoError(err) + assert.Equal(vf.Parents.Len(), uint(0)) + ve, err = d.GetVertex(mockVertexEID) + assert.NoError(err) + assert.Equal(ve.Children.Len(), uint(0)) + }, + }, + { + name: "vertex not found", + expect: func(t *testing.T, d DAG) { + assert := assert.New(t) + var ( + mockVertexEID = "bae" + mockVertexFID = "baf" + ) + + if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil { + assert.NoError(err) + } + + if err := d.AddEdge(mockVertexEID, mockVertexFID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + + if err := d.AddEdge(mockVertexFID, mockVertexEID); err != nil { + assert.EqualError(err, ErrVertexNotFound.Error()) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + d := NewDAG() + tc.expect(t, d) + }) + } +} + +func BenchmarkDAGAddVertex(b *testing.B) { + var ids []string + d := NewDAG() + for n := 0; n < b.N; n++ { + ids = append(ids, fmt.Sprint(n)) + } + + b.ResetTimer() + for _, id := range ids { + if err := d.AddVertex(id, nil); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDAGDeleteVertex(b *testing.B) { + var ids []string + d := NewDAG() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, nil); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + b.ResetTimer() + for _, id := range ids { + d.DeleteVertex(id) + } +} + +func BenchmarkDAGDeleteVertexWithMultiEdges(b *testing.B) { + var ids []string + d := NewDAG() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, nil); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + edgeCount := 5 + for index, id := range ids { + if index+edgeCount > len(ids)-1 { + break + } + + for n := 1; n < edgeCount; n++ { + if err := d.AddEdge(id, ids[index+n]); err != nil { + b.Fatal(err) + } + } + } + + b.ResetTimer() + for _, id := range ids { + d.DeleteVertex(id) + } +} + +func BenchmarkDAGAddEdge(b *testing.B) { + var ids []string + d := NewDAG() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, nil); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + b.ResetTimer() + for index, id := range ids { + if index < 1 { + continue + } + + if err := d.AddEdge(id, ids[index-1]); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDAGAddEdgeWithMultiEdges(b *testing.B) { + var ids []string + d := NewDAG() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, nil); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + edgeCount := 5 + for index, id := range ids { + if index+edgeCount > len(ids)-1 { + break + } + + for n := 1; n < edgeCount; n++ { + if err := d.AddEdge(id, ids[index+n]); err != nil { + b.Fatal(err) + } + } + } + + b.ResetTimer() + for index, id := range ids { + if index+edgeCount+1 > len(ids)-1 { + break + } + + if err := d.AddEdge(id, ids[index+edgeCount+1]); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDAGDeleteEdge(b *testing.B) { + var ids []string + d := NewDAG() + for n := 0; n < b.N; n++ { + id := fmt.Sprint(n) + if err := d.AddVertex(id, nil); err != nil { + b.Fatal(err) + } + + ids = append(ids, id) + } + + for index, id := range ids { + if index < 1 { + continue + } + + if err := d.AddEdge(id, ids[index-1]); err != nil { + b.Fatal(err) + } + } + + b.ResetTimer() + for index, id := range ids { + if index < 1 { + continue + } + + if err := d.DeleteEdge(id, ids[index-1]); err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/dag/vertex.go b/pkg/dag/vertex.go new file mode 100644 index 000000000..99ff14424 --- /dev/null +++ b/pkg/dag/vertex.go @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Dragonfly Authors + * + * 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 dag + +import "d7y.io/dragonfly/v2/pkg/container/set" + +// Vertex is a vertex of the directed acyclic graph. +type Vertex struct { + ID string + Value any + Parents set.SafeSet + Children set.SafeSet +} + +// New returns a new Vertex instance. +func NewVertex(id string, value any) *Vertex { + return &Vertex{ + ID: id, + Value: value, + Parents: set.NewSafeSet(), + Children: set.NewSafeSet(), + } +} + +// Degree returns the degree of vertex. +func (v *Vertex) Degree() int { + return int(v.Parents.Len() + v.Children.Len()) +} + +// InDegree returns the indegree of vertex. +func (v *Vertex) InDegree() int { + return int(v.Parents.Len()) +} + +// OutDegree returns the outdegree of vertex. +func (v *Vertex) OutDegree() int { + return int(v.Children.Len()) +} diff --git a/pkg/dag/vertex_test.go b/pkg/dag/vertex_test.go new file mode 100644 index 000000000..21b1a10c5 --- /dev/null +++ b/pkg/dag/vertex_test.go @@ -0,0 +1,97 @@ +/* + * Copyright 2022 The Dragonfly Authors + * + * 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 dag + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + mockVertexID = "foo" + mockVertexValue = "var" +) + +func TestNewVertex(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.Parents.Len(), uint(0)) + assert.Equal(v.Children.Len(), uint(0)) +} + +func TestVertexDegree(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.Degree(), 0) + + v.Parents.Add(mockVertexID) + assert.Equal(v.Degree(), 1) + + v.Children.Add(mockVertexID) + assert.Equal(v.Degree(), 2) + + v.Parents.Delete(mockVertexID) + assert.Equal(v.Degree(), 1) + + v.Children.Delete(mockVertexID) + assert.Equal(v.Degree(), 0) +} + +func TestVertexInDegree(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.InDegree(), 0) + + v.Parents.Add(mockVertexID) + assert.Equal(v.InDegree(), 1) + + v.Children.Add(mockVertexID) + assert.Equal(v.InDegree(), 1) + + v.Parents.Delete(mockVertexID) + assert.Equal(v.InDegree(), 0) + + v.Children.Delete(mockVertexID) + assert.Equal(v.InDegree(), 0) +} + +func TestVertexOutDegree(t *testing.T) { + v := NewVertex(mockVertexID, mockVertexValue) + assert := assert.New(t) + assert.Equal(v.ID, mockVertexID) + assert.Equal(v.Value, mockVertexValue) + assert.Equal(v.OutDegree(), 0) + + v.Parents.Add(mockVertexID) + assert.Equal(v.OutDegree(), 0) + + v.Children.Add(mockVertexID) + assert.Equal(v.OutDegree(), 1) + + v.Parents.Delete(mockVertexID) + assert.Equal(v.OutDegree(), 1) + + v.Children.Delete(mockVertexID) + assert.Equal(v.OutDegree(), 0) +}