Add tree reduce
This commit is contained in:
parent
c099e04434
commit
d52d539e9d
@ -150,6 +150,97 @@ func TestTree_AddFuzzy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTree_CheckRootReduce(t *testing.T) {
|
||||||
|
t.Run("check root once", func(t *testing.T) {
|
||||||
|
tr := new(Tree)
|
||||||
|
tr.Add(
|
||||||
|
newSnapshot("0", ""),
|
||||||
|
newChange("1", "0", "0"),
|
||||||
|
newChange("1.1", "0", "1"),
|
||||||
|
newChange("1.2", "0", "1"),
|
||||||
|
newChange("1.4", "0", "1.2"),
|
||||||
|
newChange("1.3", "0", "1"),
|
||||||
|
newChange("1.3.1", "0", "1.3"),
|
||||||
|
newChange("1.2+3", "0", "1.4", "1.3.1"),
|
||||||
|
newChange("1.2+3.1", "0", "1.2+3"),
|
||||||
|
newSnapshot("10", "0", "1.2+3.1", "1.1"),
|
||||||
|
newChange("last", "10", "10"),
|
||||||
|
)
|
||||||
|
t.Run("check root", func(t *testing.T) {
|
||||||
|
total := tr.checkRoot(tr.attached["10"])
|
||||||
|
assert.Equal(t, 1, total)
|
||||||
|
})
|
||||||
|
t.Run("reduce", func(t *testing.T) {
|
||||||
|
tr.reduceTree()
|
||||||
|
assert.Equal(t, "10", tr.RootId())
|
||||||
|
var res []string
|
||||||
|
tr.Iterate(tr.RootId(), func(c *Change) (isContinue bool) {
|
||||||
|
res = append(res, c.Id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, []string{"10", "last"}, res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("check root many", func(t *testing.T) {
|
||||||
|
tr := new(Tree)
|
||||||
|
tr.Add(
|
||||||
|
newSnapshot("0", ""),
|
||||||
|
newSnapshot("1", "0", "0"),
|
||||||
|
newChange("1.1", "0", "1"),
|
||||||
|
newChange("1.2", "0", "1"),
|
||||||
|
newChange("1.4", "0", "1.2"),
|
||||||
|
newChange("1.3", "0", "1"),
|
||||||
|
newChange("1.3.1", "0", "1.3"),
|
||||||
|
newChange("1.2+3", "0", "1.4", "1.3.1"),
|
||||||
|
newChange("1.2+3.1", "0", "1.2+3"),
|
||||||
|
newSnapshot("10", "0", "1.2+3.1", "1.1"),
|
||||||
|
newChange("last", "10", "10"),
|
||||||
|
)
|
||||||
|
t.Run("check root", func(t *testing.T) {
|
||||||
|
total := tr.checkRoot(tr.attached["10"])
|
||||||
|
assert.Equal(t, 1, total)
|
||||||
|
|
||||||
|
total = tr.checkRoot(tr.attached["1"])
|
||||||
|
assert.Equal(t, 9, total)
|
||||||
|
})
|
||||||
|
t.Run("reduce", func(t *testing.T) {
|
||||||
|
tr.reduceTree()
|
||||||
|
assert.Equal(t, "10", tr.RootId())
|
||||||
|
var res []string
|
||||||
|
tr.Iterate(tr.RootId(), func(c *Change) (isContinue bool) {
|
||||||
|
res = append(res, c.Id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, []string{"10", "last"}, res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("check root incorrect", func(t *testing.T) {
|
||||||
|
tr := new(Tree)
|
||||||
|
tr.Add(
|
||||||
|
newSnapshot("0", ""),
|
||||||
|
newChange("1", "0", "0"),
|
||||||
|
newChange("1.1", "0", "1"),
|
||||||
|
newChange("1.2", "0", "1"),
|
||||||
|
newChange("1.4", "0", "1.2"),
|
||||||
|
newChange("1.3", "0", "1"),
|
||||||
|
newSnapshot("1.3.1", "0", "1.3"),
|
||||||
|
newChange("1.2+3", "0", "1.4", "1.3.1"),
|
||||||
|
newChange("1.2+3.1", "0", "1.2+3"),
|
||||||
|
newChange("10", "0", "1.2+3.1", "1.1"),
|
||||||
|
newChange("last", "10", "10"),
|
||||||
|
)
|
||||||
|
t.Run("check root", func(t *testing.T) {
|
||||||
|
total := tr.checkRoot(tr.attached["1.3.1"])
|
||||||
|
assert.Equal(t, -1, total)
|
||||||
|
})
|
||||||
|
t.Run("reduce", func(t *testing.T) {
|
||||||
|
tr.reduceTree()
|
||||||
|
assert.Equal(t, "0", tr.RootId())
|
||||||
|
assert.Equal(t, 0, len(tr.possibleRoots))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestTree_Iterate(t *testing.T) {
|
func TestTree_Iterate(t *testing.T) {
|
||||||
t.Run("complex tree", func(t *testing.T) {
|
t.Run("complex tree", func(t *testing.T) {
|
||||||
tr := new(Tree)
|
tr := new(Tree)
|
||||||
|
|||||||
89
pkg/acl/tree/treereduce.go
Normal file
89
pkg/acl/tree/treereduce.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
// clearPossibleRoots force removes any snapshots which can further be deemed as roots
|
||||||
|
func (t *Tree) clearPossibleRoots() {
|
||||||
|
t.possibleRoots = t.possibleRoots[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkRoot checks if a change can be a new root for the tree
|
||||||
|
// it returns total changes which were discovered during dfsPrev from heads
|
||||||
|
func (t *Tree) checkRoot(change *Change) (total int) {
|
||||||
|
t.stackBuf = t.stackBuf[:0]
|
||||||
|
stack := t.stackBuf
|
||||||
|
|
||||||
|
// starting with heads
|
||||||
|
for _, h := range t.headIds {
|
||||||
|
stack = append(stack, t.attached[h])
|
||||||
|
}
|
||||||
|
|
||||||
|
change.visited = true
|
||||||
|
t.dfsPrev(
|
||||||
|
stack,
|
||||||
|
func(ch *Change) {
|
||||||
|
total += 1
|
||||||
|
},
|
||||||
|
func(changes []*Change) {
|
||||||
|
if t.root.visited {
|
||||||
|
total = -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
change.visited = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeRootAndRemove removes all changes before start and makes start the root
|
||||||
|
func (t *Tree) makeRootAndRemove(start *Change) {
|
||||||
|
t.stackBuf = t.stackBuf[:0]
|
||||||
|
stack := t.stackBuf
|
||||||
|
for _, prev := range start.PreviousIds {
|
||||||
|
stack = append(stack, t.attached[prev])
|
||||||
|
}
|
||||||
|
|
||||||
|
t.dfsPrev(
|
||||||
|
stack,
|
||||||
|
func(ch *Change) {},
|
||||||
|
func(changes []*Change) {
|
||||||
|
for _, ch := range changes {
|
||||||
|
delete(t.unAttached, ch.Id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// removing unattached because they may refer to previous root
|
||||||
|
t.unAttached = make(map[string]*Change)
|
||||||
|
t.root = start
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduceTree tries to reduce the tree to one of possible tree roots
|
||||||
|
func (t *Tree) reduceTree() (res bool) {
|
||||||
|
if len(t.possibleRoots) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
minRoot *Change
|
||||||
|
minTotal = math.MaxInt
|
||||||
|
)
|
||||||
|
|
||||||
|
// checking if we can reduce tree to other root
|
||||||
|
for _, root := range t.possibleRoots {
|
||||||
|
totalChanges := t.checkRoot(root)
|
||||||
|
// we prefer new root with min amount of total changes
|
||||||
|
if totalChanges != -1 && totalChanges < minTotal {
|
||||||
|
minRoot = root
|
||||||
|
minTotal = totalChanges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.clearPossibleRoots()
|
||||||
|
if minRoot == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.makeRootAndRemove(minRoot)
|
||||||
|
res = true
|
||||||
|
return
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user