From da075bf9da092f280f471ccb1945362ce3c15c97 Mon Sep 17 00:00:00 2001 From: Zhaoqi Tong Date: Tue, 13 Jun 2023 14:07:38 +0800 Subject: [PATCH] feat(#82): List all path between vertices --- collection.go | 53 +++++++++++++++ collection_test.go | 165 +++++++++++++++++++++++++++++++++++++++++++++ paths.go | 101 +++++++++++++++++++++++++++ paths_test.go | 158 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 477 insertions(+) diff --git a/collection.go b/collection.go index d078f064..8340cb6a 100644 --- a/collection.go +++ b/collection.go @@ -106,3 +106,56 @@ func (m *minHeap[T]) Pop() interface{} { return item } + +type stack[T any] interface { + push(T) + pop() (T, error) + top() (T, error) + isEmpty() bool + // forEach iterate the stack from bottom to top + forEach(func(T)) +} + +func newStack[T any]() stack[T] { + return &stackImpl[T]{ + elements: make([]T, 0), + } +} + +type stackImpl[T any] struct { + elements []T +} + +func (s *stackImpl[T]) push(t T) { + s.elements = append(s.elements, t) +} + +func (s *stackImpl[T]) pop() (T, error) { + e, err := s.top() + if err != nil { + var defaultValue T + return defaultValue, err + } + + s.elements = s.elements[:len(s.elements)-1] + return e, nil +} + +func (s *stackImpl[T]) top() (T, error) { + if s.isEmpty() { + var defaultValue T + return defaultValue, errors.New("no element in stack") + } + + return s.elements[len(s.elements)-1], nil +} + +func (s *stackImpl[T]) isEmpty() bool { + return len(s.elements) == 0 +} + +func (s *stackImpl[T]) forEach(f func(T)) { + for _, e := range s.elements { + f(e) + } +} diff --git a/collection_test.go b/collection_test.go index f591dce6..e7e3d35b 100644 --- a/collection_test.go +++ b/collection_test.go @@ -1,6 +1,7 @@ package graph import ( + "reflect" "testing" ) @@ -222,3 +223,167 @@ func TestPriorityQueue_Len(t *testing.T) { } } } + +func Test_stackImpl_push(t *testing.T) { + type args[T any] struct { + t T + } + type testCase[T any] struct { + name string + s stackImpl[T] + args args[T] + } + tests := []testCase[int]{ + { + "push 1", + stackImpl[int]{ + elements: make([]int, 0), + }, + args[int]{ + t: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.push(tt.args.t) + }) + } +} + +func Test_stackImpl_pop(t *testing.T) { + type testCase[T any] struct { + name string + s stackImpl[T] + want T + wantErr bool + } + tests := []testCase[int]{ + { + "pop element", + stackImpl[int]{ + elements: []int{1}, + }, + 1, + false, + }, + { + "pop element from empty stack", + stackImpl[int]{ + elements: []int{}, + }, + 0, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.pop() + if (err != nil) != tt.wantErr { + t.Errorf("pop() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("pop() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_stackImpl_top(t *testing.T) { + type testCase[T any] struct { + name string + s stackImpl[T] + want T + wantErr bool + } + tests := []testCase[int]{ + { + "top element", + stackImpl[int]{ + elements: []int{1}, + }, + 1, + false, + }, + { + "top element of empty stack", + stackImpl[int]{ + elements: []int{}, + }, + 0, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.top() + if (err != nil) != tt.wantErr { + t.Errorf("top() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("top() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_stackImpl_isEmpty(t *testing.T) { + type testCase[T any] struct { + name string + s stackImpl[T] + want bool + } + tests := []testCase[int]{ + { + "empty", + stackImpl[int]{ + elements: []int{}, + }, + true, + }, + { + "not empty", + stackImpl[int]{ + elements: []int{1}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.isEmpty(); got != tt.want { + t.Errorf("isEmpty() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_stackImpl_forEach(t *testing.T) { + type args[T any] struct { + f func(T) + } + type testCase[T any] struct { + name string + s stackImpl[T] + args args[T] + } + tests := []testCase[int]{ + { + name: "forEach", + s: stackImpl[int]{ + elements: []int{1, 2, 3, 4, 5, 6}, + }, + args: args[int]{ + f: func(i int) { + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.forEach(tt.args.f) + }) + } +} diff --git a/paths.go b/paths.go index 7ff2fbac..6be0cb54 100644 --- a/paths.go +++ b/paths.go @@ -232,3 +232,104 @@ func findSCC[K comparable](vertexHash K, state *sccState[K]) { state.components = append(state.components, component) } } + +// AllPathsBetweenTwoVertices list all paths from start to end. +func AllPathsBetweenTwoVertices[K comparable, T any](g Graph[K, T], start, end K) ([][]K, error) { + adjacencyMap, err := g.AdjacencyMap() + if err != nil { + return nil, err + } + + mainStack, viceStack := newStack[K](), newStack[stack[K]]() + + checkEmpty := func() error { + if mainStack.isEmpty() || viceStack.isEmpty() { + return errors.New("empty stack") + } + return nil + } + + buildLayer := func(element K) { + mainStack.push(element) + + newElements := newStack[K]() + for e := range adjacencyMap[element] { + var contains bool + mainStack.forEach(func(k K) { + if e == k { + contains = true + } + }) + if contains { + continue + } + newElements.push(e) + } + viceStack.push(newElements) + } + + buildStack := func() error { + if err = checkEmpty(); err != nil { + return errors.New("empty stack") + } + + elements, _ := viceStack.top() + for !elements.isEmpty() { + element, _ := elements.pop() + buildLayer(element) + elements, _ = viceStack.top() + } + + return nil + } + + removeLayer := func() error { + if err = checkEmpty(); err != nil { + return errors.New("empty stack") + } + + e, _ := viceStack.top() + if !e.isEmpty() { + return errors.New("the top element of vice-stack is not empty") + } + + _, _ = mainStack.pop() + _, _ = viceStack.pop() + + return nil + } + + // init the first layer + + buildLayer(start) + + // loop + + allPaths := make([][]K, 0) + + for !mainStack.isEmpty() { + v, _ := mainStack.top() + adjs, _ := viceStack.top() + + if adjs.isEmpty() { + if v == end { + path := make([]K, 0) + mainStack.forEach(func(k K) { + path = append(path, k) + }) + allPaths = append(allPaths, path) + } + + err = removeLayer() + if err != nil { + return nil, err + } + } else { + if err = buildStack(); err != nil { + return nil, err + } + } + } + + return allPaths, nil +} diff --git a/paths_test.go b/paths_test.go index a7771c36..97e173bd 100644 --- a/paths_test.go +++ b/paths_test.go @@ -1,6 +1,8 @@ package graph import ( + "reflect" + "sort" "testing" ) @@ -496,3 +498,159 @@ func TestUndirectedStronglyConnectedComponents(t *testing.T) { } } } + +func TestAllPathBetweenTwoVertices(t *testing.T) { + type args[K comparable, T any] struct { + g Graph[K, T] + start K + end K + } + type testCase[K comparable, T any] struct { + name string + args args[K, T] + want [][]K + wantErr bool + } + tests := []testCase[int, int]{ + { + name: "directed", + args: args[int, int]{ + g: func() Graph[int, int] { + g := New(IntHash, Directed()) + for i := 0; i <= 8; i++ { + _ = g.AddVertex(i) + } + _ = g.AddEdge(0, 2) + _ = g.AddEdge(1, 0) + _ = g.AddEdge(1, 4) + _ = g.AddEdge(2, 6) + _ = g.AddEdge(3, 1) + _ = g.AddEdge(3, 7) + _ = g.AddEdge(4, 5) + _ = g.AddEdge(5, 2) + _ = g.AddEdge(5, 6) + _ = g.AddEdge(6, 8) + _ = g.AddEdge(7, 4) + return g + }(), + start: 3, + end: 6, + }, + want: [][]int{ + {3, 1, 0, 2, 6}, + {3, 1, 4, 5, 6}, + {3, 1, 4, 5, 2, 6}, + {3, 7, 4, 5, 2, 6}, + {3, 7, 4, 5, 6}, + }, + wantErr: false, + }, + { + name: "undirected", + args: args[int, int]{ + g: func() Graph[int, int] { + g := New(IntHash) + for i := 0; i <= 8; i++ { + _ = g.AddVertex(i) + } + _ = g.AddEdge(0, 1) + _ = g.AddEdge(0, 2) + _ = g.AddEdge(1, 3) + _ = g.AddEdge(1, 4) + _ = g.AddEdge(2, 5) + _ = g.AddEdge(2, 6) + _ = g.AddEdge(3, 7) + _ = g.AddEdge(4, 5) + _ = g.AddEdge(4, 7) + _ = g.AddEdge(5, 6) + _ = g.AddEdge(6, 8) + return g + }(), + start: 3, + end: 6, + }, + want: [][]int{ + {3, 1, 0, 2, 6}, + {3, 1, 0, 2, 5, 6}, + {3, 1, 4, 5, 6}, + {3, 1, 4, 5, 2, 6}, + {3, 7, 4, 5, 2, 6}, + {3, 7, 4, 5, 6}, + {3, 7, 4, 1, 0, 2, 6}, + {3, 7, 4, 1, 0, 2, 5, 6}, + }, + wantErr: false, + }, + { + name: "undirected (complex)", + args: args[int, int]{ + g: func() Graph[int, int] { + g := New(IntHash) + for i := 0; i <= 9; i++ { + _ = g.AddVertex(i) + } + _ = g.AddEdge(0, 1) + _ = g.AddEdge(0, 2) + _ = g.AddEdge(0, 3) + _ = g.AddEdge(2, 3) + _ = g.AddEdge(2, 6) + _ = g.AddEdge(3, 4) + _ = g.AddEdge(4, 8) + _ = g.AddEdge(5, 6) + _ = g.AddEdge(5, 8) + _ = g.AddEdge(5, 9) + _ = g.AddEdge(6, 7) + _ = g.AddEdge(7, 9) + _ = g.AddEdge(8, 9) + return g + }(), + start: 0, + end: 9, + }, + want: [][]int{ + {0, 2, 3, 4, 8, 5, 6, 7, 9}, + {0, 2, 3, 4, 8, 5, 9}, + {0, 2, 3, 4, 8, 9}, + {0, 2, 6, 5, 8, 9}, + {0, 2, 6, 5, 9}, + {0, 2, 6, 7, 9}, + {0, 3, 2, 6, 5, 8, 9}, + {0, 3, 2, 6, 5, 9}, + {0, 3, 2, 6, 7, 9}, + {0, 3, 4, 8, 5, 6, 7, 9}, + {0, 3, 4, 8, 5, 9}, + {0, 3, 4, 8, 9}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := AllPathsBetweenTwoVertices(tt.args.g, tt.args.start, tt.args.end) + if (err != nil) != tt.wantErr { + t.Errorf("AllPathsBetweenTwoVertices() error = %v, wantErr %v", err, tt.wantErr) + return + } + + toStr := func(s []int) string { + var num string + for _, n := range s { + num = num + string(rune(n)) + } + return num + } + + sort.Slice(got, func(i, j int) bool { + return toStr(got[i]) < toStr(got[j]) + }) + + sort.Slice(tt.want, func(i, j int) bool { + return toStr(tt.want[i]) < toStr(tt.want[j]) + }) + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AllPathsBetweenTwoVertices() got = %v, want %v", got, tt.want) + } + }) + } +}