feat: add directed graph to pkg (#2014)

Signed-off-by: Gaius <gaius.qi@gmail.com>
This commit is contained in:
Gaius 2023-01-19 11:40:12 +08:00
parent 304448009f
commit eb65cd76f6
No known key found for this signature in database
GPG Key ID: 8B4E5D1290FA2FFB
11 changed files with 1634 additions and 3 deletions

View File

@ -308,7 +308,7 @@ func (d *dag[T]) depthFirstSearch(fromVertexID, toVertexID string) bool {
return ok return ok
} }
// depthFirstSearch finds successors of vertex. // search finds successors of vertex.
func (d *dag[T]) search(vertexID string, successors map[string]struct{}) { func (d *dag[T]) search(vertexID string, successors map[string]struct{}) {
vertex, ok := d.vertices.Get(vertexID) vertex, ok := d.vertices.Get(vertexID)
if !ok { if !ok {

View File

@ -7,7 +7,7 @@ package mocks
import ( import (
reflect "reflect" reflect "reflect"
dag "d7y.io/dragonfly/v2/pkg/dag" dag "d7y.io/dragonfly/v2/pkg/graph/dag"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )

308
pkg/graph/dg/dg.go Normal file
View File

@ -0,0 +1,308 @@
/*
* Copyright 2023 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.
*/
//go:generate mockgen -destination mocks/dg_mock.go -source dg.go -package mocks
package dg
import (
"errors"
"math/rand"
"sync"
"time"
cmap "github.com/orcaman/concurrent-map/v2"
)
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")
)
// DG is the interface used for directed graph.
type DG[T comparable] interface {
// AddVertex adds vertex to graph.
AddVertex(id string, value T) error
// DeleteVertex deletes vertex graph.
DeleteVertex(id string)
// GetVertex gets vertex from graph.
GetVertex(id string) (*Vertex[T], error)
// GetVertices returns map of vertices.
GetVertices() map[string]*Vertex[T]
// GetRandomVertices returns random map of vertices.
GetRandomVertices(n uint) []*Vertex[T]
// GetVertexKeys returns keys of vertices.
GetVertexKeys() []string
// GetSourceVertices returns source vertices.
GetSourceVertices() []*Vertex[T]
// GetSinkVertices returns sink vertices.
GetSinkVertices() []*Vertex[T]
// VertexCount returns count of vertices.
VertexCount() int
// AddEdge adds edge between two vertices.
AddEdge(fromVertexID, toVertexID string) error
// DeleteEdge deletes edge between two vertices.
DeleteEdge(fromVertexID, toVertexID string) error
// CanAddEdge finds whether there are circles through depth-first search.
CanAddEdge(fromVertexID, toVertexID string) bool
}
// dg provides directed graph function.
type dg[T comparable] struct {
mu sync.RWMutex
vertices cmap.ConcurrentMap[string, *Vertex[T]]
}
// New returns a new DG interface.
func NewDG[T comparable]() DG[T] {
return &dg[T]{
vertices: cmap.New[*Vertex[T]](),
}
}
// AddVertex adds vertex to graph.
func (d *dg[T]) AddVertex(id string, value T) error {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.vertices.Get(id); ok {
return ErrVertexAlreadyExists
}
d.vertices.Set(id, NewVertex(id, value))
return nil
}
// DeleteVertex deletes vertex graph.
func (d *dg[T]) DeleteVertex(id string) {
d.mu.Lock()
defer d.mu.Unlock()
vertex, ok := d.vertices.Get(id)
if !ok {
return
}
for _, parent := range vertex.Parents.Values() {
parent.Children.Delete(vertex)
}
for _, child := range vertex.Children.Values() {
child.Parents.Delete(vertex)
continue
}
d.vertices.Remove(id)
}
// GetVertex gets vertex from graph.
func (d *dg[T]) GetVertex(id string) (*Vertex[T], error) {
vertex, ok := d.vertices.Get(id)
if !ok {
return nil, ErrVertexNotFound
}
return vertex, nil
}
// GetVertices returns map of vertices.
func (d *dg[T]) GetVertices() map[string]*Vertex[T] {
return d.vertices.Items()
}
// GetRandomVertices returns random map of vertices.
func (d *dg[T]) GetRandomVertices(n uint) []*Vertex[T] {
d.mu.RLock()
defer d.mu.RUnlock()
keys := d.GetVertexKeys()
if int(n) >= len(keys) {
n = uint(len(keys))
}
rand.Seed(time.Now().Unix())
permutation := rand.Perm(len(keys))[:n]
randomVertices := make([]*Vertex[T], 0, n)
for _, v := range permutation {
key := keys[v]
if vertex, err := d.GetVertex(key); err == nil {
randomVertices = append(randomVertices, vertex)
}
}
return randomVertices
}
// GetVertexKeys returns keys of vertices.
func (d *dg[T]) GetVertexKeys() []string {
return d.vertices.Keys()
}
// VertexCount returns count of vertices.
func (d *dg[T]) VertexCount() int {
return d.vertices.Count()
}
// CanAddEdge finds whether there are circles through depth-first search.
func (d *dg[T]) CanAddEdge(fromVertexID, toVertexID string) bool {
d.mu.RLock()
defer d.mu.RUnlock()
if fromVertexID == toVertexID {
return false
}
fromVertex, ok := d.vertices.Get(fromVertexID)
if !ok {
return false
}
if _, ok := d.vertices.Get(toVertexID); !ok {
return false
}
for _, child := range fromVertex.Children.Values() {
if child.ID == toVertexID {
return false
}
}
return true
}
// AddEdge adds edge between two vertices.
func (d *dg[T]) AddEdge(fromVertexID, toVertexID string) error {
d.mu.Lock()
defer d.mu.Unlock()
if fromVertexID == toVertexID {
return ErrCycleBetweenVertices
}
fromVertex, ok := d.vertices.Get(fromVertexID)
if !ok {
return ErrVertexNotFound
}
toVertex, ok := d.vertices.Get(toVertexID)
if !ok {
return ErrVertexNotFound
}
for _, child := range fromVertex.Children.Values() {
if child.ID == toVertexID {
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 *dg[T]) DeleteEdge(fromVertexID, toVertexID string) error {
d.mu.Lock()
defer d.mu.Unlock()
fromVertex, ok := d.vertices.Get(fromVertexID)
if !ok {
return ErrVertexNotFound
}
toVertex, ok := d.vertices.Get(toVertexID)
if !ok {
return ErrVertexNotFound
}
fromVertex.Children.Delete(toVertex)
toVertex.Parents.Delete(fromVertex)
return nil
}
// GetSourceVertices returns source vertices.
func (d *dg[T]) GetSourceVertices() []*Vertex[T] {
d.mu.RLock()
defer d.mu.RUnlock()
var sourceVertices []*Vertex[T]
for _, vertex := range d.vertices.Items() {
if vertex.InDegree() == 0 {
sourceVertices = append(sourceVertices, vertex)
}
}
return sourceVertices
}
// GetSinkVertices returns sink vertices.
func (d *dg[T]) GetSinkVertices() []*Vertex[T] {
d.mu.RLock()
defer d.mu.RUnlock()
var sinkVertices []*Vertex[T]
for _, vertex := range d.vertices.Items() {
if vertex.OutDegree() == 0 {
sinkVertices = append(sinkVertices, vertex)
}
}
return sinkVertices
}
// search finds successors of vertex.
func (d *dg[T]) search(vertexID string, successors map[string]struct{}) {
vertex, ok := d.vertices.Get(vertexID)
if !ok {
return
}
for _, child := range vertex.Children.Values() {
if _, ok := successors[child.ID]; !ok {
successors[child.ID] = struct{}{}
d.search(child.ID, successors)
}
}
}

924
pkg/graph/dg/dg_test.go Normal file
View File

@ -0,0 +1,924 @@
/*
* Copyright 2023 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 dg
import (
"errors"
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewDG(t *testing.T) {
d := NewDG[string]()
assert := assert.New(t)
assert.Equal(reflect.TypeOf(d).Elem().Name(), "dg[string]")
}
func TestDGAddVertex(t *testing.T) {
tests := []struct {
name string
id string
value any
expect func(t *testing.T, d DG[string], err error)
}{
{
name: "add vertex",
id: mockVertexID,
value: mockVertexValue,
expect: func(t *testing.T, d DG[string], err error) {
assert := assert.New(t)
assert.NoError(err)
},
},
{
name: "vertex already exists",
id: mockVertexID,
value: mockVertexValue,
expect: func(t *testing.T, d DG[string], 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 := NewDG[string]()
tc.expect(t, d, d.AddVertex(tc.id, tc.name))
})
}
}
func TestDGDeleteVertex(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "delete vertex",
expect: func(t *testing.T, d DG[string]) {
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 DG[string]) {
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 := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGGetVertex(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get vertex",
expect: func(t *testing.T, d DG[string]) {
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 DG[string]) {
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 := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGVertexVertexCount(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get length of vertex",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil {
assert.NoError(err)
}
d.VertexCount()
assert.Equal(d.VertexCount(), 1)
d.DeleteVertex(mockVertexID)
assert.Equal(d.VertexCount(), 0)
},
},
{
name: "empty dg",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
assert.Equal(d.VertexCount(), 0)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGGetVertices(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get vertices",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil {
assert.NoError(err)
}
vertices := d.GetVertices()
assert.Equal(len(vertices), 1)
assert.Equal(vertices[mockVertexID].ID, mockVertexID)
assert.Equal(vertices[mockVertexID].Value, mockVertexValue)
d.DeleteVertex(mockVertexID)
vertices = d.GetVertices()
assert.Equal(len(vertices), 0)
},
},
{
name: "dg is empty",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
vertices := d.GetVertices()
assert.Equal(len(vertices), 0)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGGetRandomVertices(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get random vertices",
expect: func(t *testing.T, d DG[string]) {
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)
}
vertices := d.GetRandomVertices(0)
assert.Equal(len(vertices), 0)
vertices = d.GetRandomVertices(1)
assert.Equal(len(vertices), 1)
vertices = d.GetRandomVertices(2)
assert.Equal(len(vertices), 2)
vertices = d.GetRandomVertices(3)
assert.Equal(len(vertices), 2)
},
},
{
name: "dg is empty",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
vertices := d.GetRandomVertices(0)
assert.Equal(len(vertices), 0)
vertices = d.GetRandomVertices(1)
assert.Equal(len(vertices), 0)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGGetVertexKeys(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get keys of vertices",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
if err := d.AddVertex(mockVertexID, mockVertexValue); err != nil {
assert.NoError(err)
}
keys := d.GetVertexKeys()
assert.Equal(len(keys), 1)
assert.Equal(keys[0], mockVertexID)
d.DeleteVertex(mockVertexID)
keys = d.GetVertexKeys()
assert.Equal(len(keys), 0)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGAddEdge(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "add edge",
expect: func(t *testing.T, d DG[string]) {
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: "vertex not found",
expect: func(t *testing.T, d DG[string]) {
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 := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGCanAddEdge(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "can add edge",
expect: func(t *testing.T, d DG[string]) {
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)
}
ok := d.CanAddEdge(mockVertexIID, mockVertexHID)
assert.True(ok)
},
},
{
name: "cycle between vertices",
expect: func(t *testing.T, d DG[string]) {
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)
}
ok := d.CanAddEdge(mockVertexIID, mockVertexEID)
assert.True(ok)
},
},
{
name: "vertex not found",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
var (
mockVertexEID = "bae"
mockVertexFID = "baf"
)
if err := d.AddVertex(mockVertexEID, mockVertexValue); err != nil {
assert.NoError(err)
}
ok := d.CanAddEdge(mockVertexEID, mockVertexFID)
assert.False(ok)
ok = d.CanAddEdge(mockVertexFID, mockVertexEID)
assert.False(ok)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGDeleteEdge(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "delete edge",
expect: func(t *testing.T, d DG[string]) {
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 DG[string]) {
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 := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGSourceVertices(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get source vertices",
expect: func(t *testing.T, d DG[string]) {
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)
}
sourceVertices := d.GetSourceVertices()
assert.Equal(len(sourceVertices), 1)
assert.Equal(sourceVertices[0].Value, mockVertexValue)
},
},
{
name: "source vertices not found",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
sourceVertices := d.GetSourceVertices()
assert.Equal(len(sourceVertices), 0)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func TestDGSinkVertices(t *testing.T) {
tests := []struct {
name string
expect func(t *testing.T, d DG[string])
}{
{
name: "get sink vertices",
expect: func(t *testing.T, d DG[string]) {
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)
}
sinkVertices := d.GetSinkVertices()
assert.Equal(len(sinkVertices), 1)
assert.Equal(sinkVertices[0].Value, mockVertexValue)
},
},
{
name: "sink vertices not found",
expect: func(t *testing.T, d DG[string]) {
assert := assert.New(t)
sinkVertices := d.GetSinkVertices()
assert.Equal(len(sinkVertices), 0)
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
d := NewDG[string]()
tc.expect(t, d)
})
}
}
func BenchmarkDGAddVertex(b *testing.B) {
var ids []string
d := NewDG[string]()
for n := 0; n < b.N; n++ {
ids = append(ids, fmt.Sprint(n))
}
b.ResetTimer()
for _, id := range ids {
if err := d.AddVertex(id, string(id)); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDGDeleteVertex(b *testing.B) {
var ids []string
d := NewDG[string]()
for n := 0; n < b.N; n++ {
id := fmt.Sprint(n)
if err := d.AddVertex(id, string(id)); err != nil {
b.Fatal(err)
}
ids = append(ids, id)
}
b.ResetTimer()
for _, id := range ids {
d.DeleteVertex(id)
}
}
func BenchmarkDGGetRandomKeys(b *testing.B) {
d := NewDG[string]()
for n := 0; n < b.N; n++ {
id := fmt.Sprint(n)
if err := d.AddVertex(id, string(id)); err != nil {
b.Fatal(err)
}
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
vertices := d.GetRandomVertices(uint(n))
if len(vertices) != n {
b.Fatal(errors.New("get random vertices failed"))
}
}
}
func BenchmarkDGDeleteVertexWithMultiEdges(b *testing.B) {
var ids []string
d := NewDG[string]()
for n := 0; n < b.N; n++ {
id := fmt.Sprint(n)
if err := d.AddVertex(id, string(id)); 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 BenchmarkDGAddEdge(b *testing.B) {
var ids []string
d := NewDG[string]()
for n := 0; n < b.N; n++ {
id := fmt.Sprint(n)
if err := d.AddVertex(id, string(id)); 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 BenchmarkDGAddEdgeWithMultiEdges(b *testing.B) {
var ids []string
d := NewDG[string]()
for n := 0; n < b.N; n++ {
id := fmt.Sprint(n)
if err := d.AddVertex(id, string(id)); 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 BenchmarkDGDeleteEdge(b *testing.B) {
var ids []string
d := NewDG[string]()
for n := 0; n < b.N; n++ {
id := fmt.Sprint(n)
if err := d.AddVertex(id, string(id)); 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)
}
}
}

View File

@ -0,0 +1,202 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: dg.go
// Package mocks is a generated GoMock package.
package mocks
import (
reflect "reflect"
dg "d7y.io/dragonfly/v2/pkg/graph/dg"
gomock "github.com/golang/mock/gomock"
)
// MockDG is a mock of DG interface.
type MockDG[T comparable] struct {
ctrl *gomock.Controller
recorder *MockDGMockRecorder[T]
}
// MockDGMockRecorder is the mock recorder for MockDG.
type MockDGMockRecorder[T comparable] struct {
mock *MockDG[T]
}
// NewMockDG creates a new mock instance.
func NewMockDG[T comparable](ctrl *gomock.Controller) *MockDG[T] {
mock := &MockDG[T]{ctrl: ctrl}
mock.recorder = &MockDGMockRecorder[T]{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDG[T]) EXPECT() *MockDGMockRecorder[T] {
return m.recorder
}
// AddEdge mocks base method.
func (m *MockDG[T]) AddEdge(fromVertexID, toVertexID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddEdge", fromVertexID, toVertexID)
ret0, _ := ret[0].(error)
return ret0
}
// AddEdge indicates an expected call of AddEdge.
func (mr *MockDGMockRecorder[T]) AddEdge(fromVertexID, toVertexID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEdge", reflect.TypeOf((*MockDG[T])(nil).AddEdge), fromVertexID, toVertexID)
}
// AddVertex mocks base method.
func (m *MockDG[T]) AddVertex(id string, value T) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddVertex", id, value)
ret0, _ := ret[0].(error)
return ret0
}
// AddVertex indicates an expected call of AddVertex.
func (mr *MockDGMockRecorder[T]) AddVertex(id, value interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddVertex", reflect.TypeOf((*MockDG[T])(nil).AddVertex), id, value)
}
// CanAddEdge mocks base method.
func (m *MockDG[T]) CanAddEdge(fromVertexID, toVertexID string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CanAddEdge", fromVertexID, toVertexID)
ret0, _ := ret[0].(bool)
return ret0
}
// CanAddEdge indicates an expected call of CanAddEdge.
func (mr *MockDGMockRecorder[T]) CanAddEdge(fromVertexID, toVertexID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanAddEdge", reflect.TypeOf((*MockDG[T])(nil).CanAddEdge), fromVertexID, toVertexID)
}
// DeleteEdge mocks base method.
func (m *MockDG[T]) DeleteEdge(fromVertexID, toVertexID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteEdge", fromVertexID, toVertexID)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteEdge indicates an expected call of DeleteEdge.
func (mr *MockDGMockRecorder[T]) DeleteEdge(fromVertexID, toVertexID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteEdge", reflect.TypeOf((*MockDG[T])(nil).DeleteEdge), fromVertexID, toVertexID)
}
// DeleteVertex mocks base method.
func (m *MockDG[T]) DeleteVertex(id string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DeleteVertex", id)
}
// DeleteVertex indicates an expected call of DeleteVertex.
func (mr *MockDGMockRecorder[T]) DeleteVertex(id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVertex", reflect.TypeOf((*MockDG[T])(nil).DeleteVertex), id)
}
// GetRandomVertices mocks base method.
func (m *MockDG[T]) GetRandomVertices(n uint) []*dg.Vertex[T] {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetRandomVertices", n)
ret0, _ := ret[0].([]*dg.Vertex[T])
return ret0
}
// GetRandomVertices indicates an expected call of GetRandomVertices.
func (mr *MockDGMockRecorder[T]) GetRandomVertices(n interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRandomVertices", reflect.TypeOf((*MockDG[T])(nil).GetRandomVertices), n)
}
// GetSinkVertices mocks base method.
func (m *MockDG[T]) GetSinkVertices() []*dg.Vertex[T] {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSinkVertices")
ret0, _ := ret[0].([]*dg.Vertex[T])
return ret0
}
// GetSinkVertices indicates an expected call of GetSinkVertices.
func (mr *MockDGMockRecorder[T]) GetSinkVertices() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSinkVertices", reflect.TypeOf((*MockDG[T])(nil).GetSinkVertices))
}
// GetSourceVertices mocks base method.
func (m *MockDG[T]) GetSourceVertices() []*dg.Vertex[T] {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSourceVertices")
ret0, _ := ret[0].([]*dg.Vertex[T])
return ret0
}
// GetSourceVertices indicates an expected call of GetSourceVertices.
func (mr *MockDGMockRecorder[T]) GetSourceVertices() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSourceVertices", reflect.TypeOf((*MockDG[T])(nil).GetSourceVertices))
}
// GetVertex mocks base method.
func (m *MockDG[T]) GetVertex(id string) (*dg.Vertex[T], error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVertex", id)
ret0, _ := ret[0].(*dg.Vertex[T])
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetVertex indicates an expected call of GetVertex.
func (mr *MockDGMockRecorder[T]) GetVertex(id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVertex", reflect.TypeOf((*MockDG[T])(nil).GetVertex), id)
}
// GetVertexKeys mocks base method.
func (m *MockDG[T]) GetVertexKeys() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVertexKeys")
ret0, _ := ret[0].([]string)
return ret0
}
// GetVertexKeys indicates an expected call of GetVertexKeys.
func (mr *MockDGMockRecorder[T]) GetVertexKeys() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVertexKeys", reflect.TypeOf((*MockDG[T])(nil).GetVertexKeys))
}
// GetVertices mocks base method.
func (m *MockDG[T]) GetVertices() map[string]*dg.Vertex[T] {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVertices")
ret0, _ := ret[0].(map[string]*dg.Vertex[T])
return ret0
}
// GetVertices indicates an expected call of GetVertices.
func (mr *MockDGMockRecorder[T]) GetVertices() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVertices", reflect.TypeOf((*MockDG[T])(nil).GetVertices))
}
// VertexCount mocks base method.
func (m *MockDG[T]) VertexCount() int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VertexCount")
ret0, _ := ret[0].(int)
return ret0
}
// VertexCount indicates an expected call of VertexCount.
func (mr *MockDGMockRecorder[T]) VertexCount() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VertexCount", reflect.TypeOf((*MockDG[T])(nil).VertexCount))
}

70
pkg/graph/dg/vertex.go Normal file
View File

@ -0,0 +1,70 @@
/*
* Copyright 2023 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 dg
import "d7y.io/dragonfly/v2/pkg/container/set"
// Vertex is a vertex of the directed graph.
type Vertex[T comparable] struct {
ID string
Value T
Parents set.SafeSet[*Vertex[T]]
Children set.SafeSet[*Vertex[T]]
}
// New returns a new Vertex instance.
func NewVertex[T comparable](id string, value T) *Vertex[T] {
return &Vertex[T]{
ID: id,
Value: value,
Parents: set.NewSafeSet[*Vertex[T]](),
Children: set.NewSafeSet[*Vertex[T]](),
}
}
// Degree returns the degree of vertex.
func (v *Vertex[T]) Degree() int {
return int(v.Parents.Len() + v.Children.Len())
}
// InDegree returns the indegree of vertex.
func (v *Vertex[T]) InDegree() int {
return int(v.Parents.Len())
}
// OutDegree returns the outdegree of vertex.
func (v *Vertex[T]) OutDegree() int {
return int(v.Children.Len())
}
// DeleteInEdges deletes inedges of vertex.
func (v *Vertex[T]) DeleteInEdges() {
for _, parent := range v.Parents.Values() {
parent.Children.Delete(v)
}
v.Parents = set.NewSafeSet[*Vertex[T]]()
}
// DeleteOutEdges deletes outedges of vertex.
func (v *Vertex[T]) DeleteOutEdges() {
for _, child := range v.Children.Values() {
child.Parents.Delete(v)
}
v.Children = set.NewSafeSet[*Vertex[T]]()
}

127
pkg/graph/dg/vertex_test.go Normal file
View File

@ -0,0 +1,127 @@
/*
* Copyright 2023 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 dg
import (
"testing"
"github.com/stretchr/testify/assert"
)
const (
mockVertexID = "foo"
mockVertexValue = "bar"
)
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(v)
assert.Equal(v.Degree(), 1)
v.Children.Add(v)
assert.Equal(v.Degree(), 2)
v.Parents.Delete(v)
assert.Equal(v.Degree(), 1)
v.Children.Delete(v)
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(v)
assert.Equal(v.InDegree(), 1)
v.Children.Add(v)
assert.Equal(v.InDegree(), 1)
v.Parents.Delete(v)
assert.Equal(v.InDegree(), 0)
v.Children.Delete(v)
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(v)
assert.Equal(v.OutDegree(), 0)
v.Children.Add(v)
assert.Equal(v.OutDegree(), 1)
v.Parents.Delete(v)
assert.Equal(v.OutDegree(), 1)
v.Children.Delete(v)
assert.Equal(v.OutDegree(), 0)
}
func TestVertexDeleteInEdges(t *testing.T) {
va := NewVertex(mockVertexID, mockVertexValue)
vb := NewVertex(mockVertexID, mockVertexValue)
assert := assert.New(t)
va.Parents.Add(vb)
vb.Children.Add(va)
assert.Equal(va.Parents.Len(), uint(1))
assert.Equal(vb.Children.Len(), uint(1))
va.DeleteInEdges()
assert.Equal(va.Parents.Len(), uint(0))
assert.Equal(vb.Children.Len(), uint(0))
}
func TestVertexDeleteOutEdges(t *testing.T) {
va := NewVertex(mockVertexID, mockVertexValue)
vb := NewVertex(mockVertexID, mockVertexValue)
assert := assert.New(t)
va.Parents.Add(vb)
vb.Children.Add(va)
assert.Equal(va.Parents.Len(), uint(1))
assert.Equal(vb.Children.Len(), uint(1))
vb.DeleteOutEdges()
assert.Equal(va.Parents.Len(), uint(0))
assert.Equal(vb.Children.Len(), uint(0))
}

View File

@ -31,7 +31,7 @@ import (
logger "d7y.io/dragonfly/v2/internal/dflog" logger "d7y.io/dragonfly/v2/internal/dflog"
"d7y.io/dragonfly/v2/pkg/container/set" "d7y.io/dragonfly/v2/pkg/container/set"
"d7y.io/dragonfly/v2/pkg/dag" "d7y.io/dragonfly/v2/pkg/graph/dag"
"d7y.io/dragonfly/v2/pkg/types" "d7y.io/dragonfly/v2/pkg/types"
) )