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) {
|
||||
t.Run("complex tree", func(t *testing.T) {
|
||||
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