From 0e3b970c0cd895ea48275726d2714332bb642edf Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 18 Jun 2020 21:09:58 -0700 Subject: [PATCH] added bounded tree to track exploration boundary --- weed/util/bounded_tree/bounded_tree.go | 129 ++++++++++++++++++++ weed/util/bounded_tree/bounded_tree_test.go | 113 +++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 weed/util/bounded_tree/bounded_tree.go create mode 100644 weed/util/bounded_tree/bounded_tree_test.go diff --git a/weed/util/bounded_tree/bounded_tree.go b/weed/util/bounded_tree/bounded_tree.go new file mode 100644 index 000000000..fa172a764 --- /dev/null +++ b/weed/util/bounded_tree/bounded_tree.go @@ -0,0 +1,129 @@ +package bounded_tree + +import ( + "fmt" + + "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/util" +) + +type Node struct { + Parent *Node + Name string + Children map[string]*Node +} + +type BoundedTree struct { + root *Node +} + +func NewBoundedTree() *BoundedTree { + return &BoundedTree{ + root: &Node{ + Name: "/", + }, + } +} + +type VisitNodeFunc func(path util.FullPath) (childDirectories []string, err error) + +// If the path is not visited, call the visitFn for each level of directory +// No action if the directory has been visited before or does not exist. +// A leaf node, which has no children, represents a directory not visited. +// A non-leaf node or a non-existing node represents a directory already visited, or does not need to visit. +func (t *BoundedTree) EnsureVisited(p util.FullPath, visitFn VisitNodeFunc) { + println() + println("EnsureVisited", p) + + if t.root == nil { + return + } + components := p.Split() + fmt.Printf("components %v %d\n", components, len(components)) + if canDelete := t.ensureVisited(t.root, util.FullPath("/"), components, 0, visitFn); canDelete { + t.root = nil + } +} + +func (t *BoundedTree) ensureVisited(n *Node, currentPath util.FullPath, components []string, i int, visitFn VisitNodeFunc) (canDeleteNode bool) { + + println("ensureVisited", currentPath, i) + + if n == nil { + fmt.Printf("%s null\n", currentPath) + return + } + + if n.isVisited() { + fmt.Printf("%s visited %v\n", currentPath, n.Name) + } else { + fmt.Printf("ensure %v\n", currentPath) + + children, err := visitFn(currentPath) + if err != nil { + glog.V(0).Infof("failed to visit %s: %v", currentPath, err) + return + } + + if len(children) == 0 { + fmt.Printf(" canDelete %v without children\n", currentPath) + return true + } + + n.Children = make(map[string]*Node) + for _, child := range children { + fmt.Printf(" add child %v %v\n", currentPath, child) + n.Children[child] = &Node{ + Name: child, + } + } + } + + if i >= len(components) { + return + } + + fmt.Printf(" check child %v %v\n", currentPath, components[i]) + + toVisitNode, found := n.Children[components[i]] + if !found { + fmt.Printf(" did not find child %v %v\n", currentPath, components[i]) + return + } + + fmt.Printf(" ensureVisited %v %v\n", currentPath, toVisitNode.Name) + + if canDelete := t.ensureVisited(toVisitNode, currentPath.Child(components[i]), components, i+1, visitFn); canDelete { + + fmt.Printf(" delete %v %v\n", currentPath, components[i]) + delete(n.Children, components[i]) + + if len(n.Children) == 0 { + fmt.Printf(" canDelete %v\n", currentPath) + return true + } + } + + return false + +} + +func (n *Node) isVisited() bool { + if n == nil { + return true + } + if len(n.Children) > 0 { + return true + } + return false +} + +func (n *Node) getChild(childName string) *Node { + if n == nil { + return nil + } + if len(n.Children) > 0 { + return n.Children[childName] + } + return nil +} diff --git a/weed/util/bounded_tree/bounded_tree_test.go b/weed/util/bounded_tree/bounded_tree_test.go new file mode 100644 index 000000000..b8dce664c --- /dev/null +++ b/weed/util/bounded_tree/bounded_tree_test.go @@ -0,0 +1,113 @@ +package bounded_tree + +import ( + "fmt" + "testing" + + "github.com/chrislusf/seaweedfs/weed/util" +) + + +var ( + + visitFn = func(path util.FullPath) (childDirectories []string, err error) { + fmt.Printf(" visit %v ...\n", path) + switch path { + case "/": + return []string{"a", "g", "h"}, nil + case "/a": + return []string{"b", "f"}, nil + case "/a/b": + return []string{"c", "e"}, nil + case "/a/b/c": + return []string{"d"}, nil + case "/a/b/c/d": + return []string{"i", "j"}, nil + case "/a/b/c/d/i": + return []string{}, nil + case "/a/b/c/d/j": + return []string{}, nil + case "/a/b/e": + return []string{}, nil + case "/a/f": + return []string{}, nil + } + return nil, nil + } + + + printMap = func(m map[string]*Node) { + for k := range m { + println(" >", k) + } + } + + +) + +func TestBoundedTree(t *testing.T) { + + // a/b/c/d/i + // a/b/c/d/j + // a/b/c/d + // a/b/e + // a/f + // g + // h + + tree := NewBoundedTree() + + tree.EnsureVisited(util.FullPath("/a/b/c"), visitFn) + + printMap(tree.root.Children) + + a := tree.root.getChild("a") + + b := a.getChild("b") + if !b.isVisited() { + t.Errorf("expect visited /a/b") + } + c := b.getChild("c") + if !c.isVisited() { + t.Errorf("expect visited /a/b/c") + } + + d := c.getChild("d") + if d.isVisited() { + t.Errorf("expect unvisited /a/b/c/d") + } + + tree.EnsureVisited(util.FullPath("/a/b/c/d"), visitFn) + tree.EnsureVisited(util.FullPath("/a/b/c/d/i"), visitFn) + tree.EnsureVisited(util.FullPath("/a/b/c/d/j"), visitFn) + tree.EnsureVisited(util.FullPath("/a/b/e"), visitFn) + tree.EnsureVisited(util.FullPath("/a/f"), visitFn) + + printMap(tree.root.Children) + +} + +func TestEmptyBoundedTree(t *testing.T) { + + // g + // h + + tree := NewBoundedTree() + + visitFn := func(path util.FullPath) (childDirectories []string, err error) { + fmt.Printf(" visit %v ...\n", path) + switch path { + case "/": + return []string{"g", "h"}, nil + } + t.Fatalf("expected visit %s", path) + return nil, nil + } + + tree.EnsureVisited(util.FullPath("/a/b"), visitFn) + + tree.EnsureVisited(util.FullPath("/a/b"), visitFn) + + printMap(tree.root.Children) + +}