Add tree reduce

This commit is contained in:
mcrakhman 2022-09-04 16:05:38 +02:00 committed by Mikhail Iudin
parent c099e04434
commit d52d539e9d
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
2 changed files with 180 additions and 0 deletions

View File

@ -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)

View 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
}