Skip to content

Commit

Permalink
Implement AllPathsBetween (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
tzq0301 committed Jul 4, 2023
1 parent 9144c0b commit 92fa587
Show file tree
Hide file tree
Showing 4 changed files with 477 additions and 0 deletions.
53 changes: 53 additions & 0 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
165 changes: 165 additions & 0 deletions collection_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graph

import (
"reflect"
"testing"
)

Expand Down Expand Up @@ -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)
})
}
}
101 changes: 101 additions & 0 deletions paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,104 @@ func findSCC[K comparable](vertexHash K, state *sccState[K]) {
state.components = append(state.components, component)
}
}

// AllPathsBetween list all paths from start to end.
func AllPathsBetween[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
}
Loading

0 comments on commit 92fa587

Please sign in to comment.