From 4e7267762dba8c3a2e12a0b984ac930a92b5e608 Mon Sep 17 00:00:00 2001 From: tiburon Date: Thu, 10 Oct 2024 18:07:03 +0300 Subject: [PATCH] Add new UTs --- .run/test all.run.xml | 12 + weighting_n_cutting_the_tree/cutting.go | 58 +- weighting_n_cutting_the_tree/cutting_test.go | 77 ++- .../cutting_test_models.go | 632 ++++++++++++++++++ 4 files changed, 750 insertions(+), 29 deletions(-) create mode 100644 .run/test all.run.xml diff --git a/.run/test all.run.xml b/.run/test all.run.xml new file mode 100644 index 0000000..372e0ce --- /dev/null +++ b/.run/test all.run.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/weighting_n_cutting_the_tree/cutting.go b/weighting_n_cutting_the_tree/cutting.go index 797d3a4..0c24457 100644 --- a/weighting_n_cutting_the_tree/cutting.go +++ b/weighting_n_cutting_the_tree/cutting.go @@ -13,11 +13,14 @@ func DecomposeTree(tree map[string]*WeightedNode, threshold uint16) ([]SubTree, index := GetTreeIndex(tree) // Ищем ветку подходящую под лимит branchID, weight := GetBranch(tree, index, threshold) + if branchID == "" { + return nil, errors.New("[GetNodePath]: threshold too small") + } // Определяем путь к найденной ветке path, err := GetNodePath(tree, branchID) if err != nil { - return nil, errors.Wrap(err, "[GetNodePath] ") + return nil, errors.Wrap(err, "[GetNodePath]") } // Вычитаем ветку из веса предков RecalculateParents(tree, branchID, weight) @@ -65,6 +68,9 @@ func GetBranch(tree map[string]*WeightedNode, index []TreeIndex, threshold uint1 break } } + if branchID == "" { + return "", 0 + } return tree[branchID].node.ID, tree[branchID].weight } @@ -78,7 +84,7 @@ func GetNodePath(tree map[string]*WeightedNode, nodeID string) (string, error) { } nodeID = tree[nodeID].parent.ID path := tree[nodeID].node.Name - for { + for path != "/" { if tree[nodeID].parent.Name == "/" { path = "/" + path break @@ -104,6 +110,54 @@ func RecalculateParents(tree map[string]*WeightedNode, currentID string, nodeWei } } +func GetNode(tree map[string]*WeightedNode, index []TreeIndex, threshold uint16) (string, string, error) { + // Для работы необходим актуальный индекс мапы, отсортированный по убыванию + var branchID string + for _, v := range index { + if v.weight < threshold { + branchID = v.id + break + } + } + if branchID == "" { + return "", "", errors.New("threshold too small") + } + currentID := tree[branchID].node.ID + nodeWeight := tree[branchID].weight + + // GetNodePath возвращает полный путь ноды от корня + if _, ok := tree[currentID]; !ok { + return "", "", errors.New("node not exists") + } + if tree[currentID].parent == nil { + return tree[currentID].node.ID, "/", nil + } + currentID = tree[currentID].parent.ID + path := tree[currentID].node.Name + for { + if tree[currentID].parent.Name == "/" { + path = "/" + path + break + } + currentID = tree[currentID].parent.ID + path = tree[currentID].node.Name + "/" + path + } + // RecalculateParents вычитает вес вырезанной ветки из всех родительских веток + if tree[currentID].parent == nil { + return "", "", errors.New("it's the root node") + } + currentID = tree[currentID].parent.ID + for { + tree[currentID].weight -= nodeWeight + if tree[currentID].parent == nil { + break + } + currentID = tree[currentID].parent.ID + } + return tree[branchID].node.ID, path, nil + +} + // CutBranch удаляет из дерева ветку со всеми потомками, а так же удаляет ссылку на себя из родительской ноды func CutBranch(tree map[string]*WeightedNode, nodeID string) *Node { // Удаление ссылки на ноду из родительской ноды diff --git a/weighting_n_cutting_the_tree/cutting_test.go b/weighting_n_cutting_the_tree/cutting_test.go index f2e95a6..7e5b6b6 100644 --- a/weighting_n_cutting_the_tree/cutting_test.go +++ b/weighting_n_cutting_the_tree/cutting_test.go @@ -2,24 +2,47 @@ package main import ( "testing" - + "github.com/stretchr/testify/require" ) -func TestDecomposeTree(t *testing.T){ - testTree := getSmallTree() - for name, tt := range map[string]struct{ - inputTree map[string]*WeightedNode +func TestDecomposeTree(t *testing.T) { + smallTree := getSmallTree() + singleNodeTree := getSingleNodeTree() + deepTree := getDeepTree() + assimmetricTree := getAssimmetricTree() + for name, tt := range map[string]struct { + inputTree map[string]*WeightedNode inputThreshold uint16 - outputParts []SubTree - err string + outputParts []SubTree + err string }{ - "Normal":{ - inputTree: testTree.TreeWeighted, - inputThreshold: testTree.Threshold, - outputParts: testTree.Parts, + "Normal tree 3x3": { + inputTree: smallTree.TreeWeighted, + inputThreshold: smallTree.Threshold, + outputParts: smallTree.Parts, }, - }{ + "Single node short tree": { + inputTree: singleNodeTree.TreeWeighted, + inputThreshold: singleNodeTree.Threshold, + outputParts: singleNodeTree.Parts, + }, + "Single node short tree with small threshold": { + inputTree: singleNodeTree.TreeWeighted, + inputThreshold: 10, + err: "[GetNodePath]: threshold too small", + }, + "Single branch deep tree": { + inputTree: deepTree.TreeWeighted, + inputThreshold: deepTree.Threshold, + outputParts: deepTree.Parts, + }, + "Asymmetric tree": { + inputTree: assimmetricTree.TreeWeighted, + inputThreshold: assimmetricTree.Threshold, + outputParts: assimmetricTree.Parts, + }, + } { t.Run(name, func(t *testing.T) { result, err := DecomposeTree(tt.inputTree, tt.inputThreshold) if tt.err == "" { @@ -32,30 +55,30 @@ func TestDecomposeTree(t *testing.T){ } } -func TestGetNodePath(t *testing.T) { +func TestGetNodePath(t *testing.T) { testTree := getSmallTree() - for name, tt := range map[string]struct{ - inputTree map[string]*WeightedNode + for name, tt := range map[string]struct { + inputTree map[string]*WeightedNode inputCurrentNodeID string - outputPath string - outputErr string + outputPath string + outputErr string }{ - "Normal path":{ - inputTree: testTree.TreeWeighted, + "Normal path": { + inputTree: testTree.TreeWeighted, inputCurrentNodeID: "1.2.3.3", - outputPath: "/folder2/folder3", + outputPath: "/folder2/folder3", }, - "Short path":{ - inputTree: testTree.TreeWeighted, + "Short path": { + inputTree: testTree.TreeWeighted, inputCurrentNodeID: "1.0.0.0", - outputPath: "/", + outputPath: "/", }, - "Not existent node":{ - inputTree: testTree.TreeWeighted, + "Not existent node": { + inputTree: testTree.TreeWeighted, inputCurrentNodeID: "qwwrwteyu", - outputErr: "node not exists", + outputErr: "node not exists", }, - }{ + } { t.Run(name, func(t *testing.T) { path, err := GetNodePath(tt.inputTree, tt.inputCurrentNodeID) require.Equal(t, tt.outputPath, path) diff --git a/weighting_n_cutting_the_tree/cutting_test_models.go b/weighting_n_cutting_the_tree/cutting_test_models.go index 5c31b67..906e546 100644 --- a/weighting_n_cutting_the_tree/cutting_test_models.go +++ b/weighting_n_cutting_the_tree/cutting_test_models.go @@ -467,3 +467,635 @@ func getSmallTree() testDecomposingData { }, } } + +func getSingleNodeTree() testDecomposingData { + newTree := &Node{ + ID: "1.0.0.0", + Name: "/", + Children: []*Node{}, + } + return testDecomposingData{ + TreeWeighted: newTree.WeightingTreeWithStack(), + Threshold: 200, + Parts: []SubTree{ + { + path: "/", + tree: &Node{ + ID: "1.0.0.0", + Name: "/", + Children: []*Node{}, + }, + }, + }, + } +} + +func getDeepTree() testDecomposingData { + newTree := &Node{ + ID: "1", + Name: "/", + Children: []*Node{ + { + ID: "1.2", + Name: "folder2", + Children: []*Node{ + { + ID: "1.2.3", + Name: "folder3", + Children: []*Node{ + { + ID: "1.2.3.4", + Name: "folder4", + Children: []*Node{ + { + ID: "1.2.3.4.5", + Name: "folder5", + Children: []*Node{ + { + ID: "1.2.3.4.5.6", + Name: "folder6", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7", + Name: "folder7", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8", + Name: "folder8", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8.9", + Name: "folder9", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8.9.0", + Name: "folder0", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8.9.0.A", + Name: "folderA", + Children: []*Node{}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + return testDecomposingData{ + TreeWeighted: newTree.WeightingTreeWithStack(), + Threshold: 200, + Parts: []SubTree{ + { + path: "/folder1/folder2/folder3/folder4/folder5/folder6/folder7/folder8", + tree: &Node{ + ID: "1.2.3.4.5.6.7.8.9", + Name: "folder9", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8.9.0", + Name: "folder0", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8.9.0.A", + Name: "folderA", + Children: []*Node{}, + }, + }, + }, + }, + }, + }, + { + path: "/folder1/folder2/folder3/folder4/folder5", + tree: &Node{ + ID: "1.2.3.4.5.6", + Name: "folder6", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7", + Name: "folder7", + Children: []*Node{ + { + ID: "1.2.3.4.5.6.7.8", + Name: "folder8", + Children: []*Node{}, + }, + }, + }, + }, + }, + }, + { + path: "/", + tree: &Node{ + ID: "1.2", + Name: "folder2", + Children: []*Node{ + { + ID: "1.2.3", + Name: "folder3", + Children: []*Node{ + { + ID: "1.2.3.4", + Name: "folder4", + Children: []*Node{ + { + ID: "1.2.3.4.5", + Name: "folder5", + Children: []*Node{}, + }, + }, + }, + }, + }, + }, + }, + }, + { + path: "/", + tree: &Node{ + ID: "1", + Name: "/", + Children: []*Node{}, + }, + }, + }, + } +} + +func getAssimmetricTree() testDecomposingData { + newTree := &Node{ + ID: "1.0.0.0", + Name: "/", + Children: []*Node{ + { + ID: "1.1.0.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.1.1.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.1.1.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.1.1.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.1.1.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + { + ID: "1.1.2.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.1.2.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.1.2.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.1.2.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + { + ID: "1.1.3.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.1.3.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.1.3.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.1.3.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + }, + { + ID: "1.2.0.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.2.1.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.2.1.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.1.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.2.1.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + { + ID: "1.2.2.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.2.2.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.2.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.2.2.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + { + ID: "1.2.3.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.2.3.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.3.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.2.3.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + }, + { + ID: "1.3.0.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.3.1.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.3.1.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.3.1.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.1.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + { + ID: "1.3.2.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.3.2.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.3.2.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.2.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + { + ID: "1.3.3.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.3.3.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.3.3.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.3.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + }, + }, + } + return testDecomposingData{ + TreeWeighted: newTree.WeightingTreeWithStack(), + Threshold: 200, + Parts: []SubTree{ + { + path: "/folder1", + tree: &Node{ + ID: "1.1.1.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.1.1.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.1.1.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.1.1.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder1", + tree: &Node{ + ID: "1.1.2.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.1.2.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.1.2.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.1.2.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder1", + tree: &Node{ + ID: "1.1.3.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.1.3.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.1.3.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.1.3.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder2", + tree: &Node{ + ID: "1.2.1.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.2.1.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.1.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.2.1.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder2", + tree: &Node{ + ID: "1.2.2.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.2.2.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.2.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.2.2.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder2", + tree: &Node{ + ID: "1.2.3.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.2.3.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.3.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.2.3.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder3", + tree: &Node{ + ID: "1.3.1.0", + Name: "folder1", + Children: []*Node{ + { + ID: "1.3.1.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.3.1.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.1.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder3", + tree: &Node{ + ID: "1.3.2.0", + Name: "folder2", + Children: []*Node{ + { + ID: "1.3.2.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.3.2.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.2.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/folder3", + tree: &Node{ + ID: "1.3.3.0", + Name: "folder3", + Children: []*Node{ + { + ID: "1.3.3.1", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.3.3.2", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.3.3", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + { + path: "/", + tree: &Node{ + ID: "1.0.0.0", + Name: "/", + Children: []*Node{ + { + ID: "1.1.0.0", + Name: "folder1", + Children: []*Node{}, + }, + { + ID: "1.2.0.0", + Name: "folder2", + Children: []*Node{}, + }, + { + ID: "1.3.0.0", + Name: "folder3", + Children: []*Node{}, + }, + }, + }, + }, + }, + } +}