feat: add directed acyclic graph package (#1468)
Signed-off-by: Gaius <gaius.qi@gmail.com>
This commit is contained in:
parent
c6654cb4ae
commit
c79bd943bb
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue