/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package graph import ( "math/rand" "time" ) func init() { rand.Seed(time.Now().UnixNano()) } // treePermutations represents possible permutations // of a tree type treePermutations struct { combinationUpperBound int // The upper bound of combinations of direct descendants. originalRoot *TreeVertex // The root vertex of all sub-trees permutations []*TreeVertex // The accumulated permutations descendantPermutations map[*TreeVertex][][]*TreeVertex // Defines the combinations of sub-trees based on the threshold of the current vertex } // newTreePermutation creates a new treePermutations object with a given root vertex func newTreePermutation(root *TreeVertex, combinationUpperBound int) *treePermutations { return &treePermutations{ combinationUpperBound: combinationUpperBound, descendantPermutations: make(map[*TreeVertex][][]*TreeVertex), originalRoot: root, permutations: []*TreeVertex{root}, } } // permute returns Trees that their vertices and edges all exist in the original tree who's vertex // is the 'originalRoot' field of the treePermutations func (tp *treePermutations) permute() []*Tree { tp.computeDescendantPermutations() it := newBFSIterator(tp.originalRoot) for { v := it.Next() if v == nil { break } if len(v.Descendants) == 0 { continue } // Iterate over all permutations where v exists // and separate them to 2 sets: a indiceSet where it exists and a indiceSet where it doesn't var permutationsWhereVexists []*TreeVertex var permutationsWhereVdoesntExist []*TreeVertex for _, perm := range tp.permutations { if perm.Exists(v.Id) { permutationsWhereVexists = append(permutationsWhereVexists, perm) } else { permutationsWhereVdoesntExist = append(permutationsWhereVdoesntExist, perm) } } // Remove the permutations where v exists from the permutations tp.permutations = permutationsWhereVdoesntExist // Next, we replace each occurrence of a permutation where v exists, // with multiple occurrences of its descendants permutations for _, perm := range permutationsWhereVexists { // For each permutation of v's descendants, clone the graph // and create a new graph with v replaced with the permutated graph // of v connected to the descendant permutation for _, permutation := range tp.descendantPermutations[v] { subGraph := &TreeVertex{ Id: v.Id, Data: v.Data, Descendants: permutation, } newTree := perm.Clone() newTree.replace(v.Id, subGraph) // Add the new option to the permutations tp.permutations = append(tp.permutations, newTree) } } } res := make([]*Tree, len(tp.permutations)) for i, perm := range tp.permutations { res[i] = perm.ToTree() } return res } // computeDescendantPermutations computes all possible combinations of sub-trees // for all vertices, based on the thresholds. func (tp *treePermutations) computeDescendantPermutations() { it := newBFSIterator(tp.originalRoot) for { v := it.Next() if v == nil { return } // Skip leaves if len(v.Descendants) == 0 { continue } // Ensure we don't have too much combinations of descendants for CombinationsExceed(len(v.Descendants), v.Threshold, tp.combinationUpperBound) { // Randomly pick a descendant, and remove it victim := rand.Intn(len(v.Descendants)) v.Descendants = append(v.Descendants[:victim], v.Descendants[victim+1:]...) } // Iterate over all options of selecting the threshold out of the descendants for _, el := range chooseKoutOfN(len(v.Descendants), v.Threshold) { // And for each such option, append it to the current TreeVertex tp.descendantPermutations[v] = append(tp.descendantPermutations[v], v.selectDescendants(el.indices)) } } } // selectDescendants returns a subset of descendants according to given indices func (v *TreeVertex) selectDescendants(indices []int) []*TreeVertex { r := make([]*TreeVertex, len(indices)) i := 0 for _, index := range indices { r[i] = v.Descendants[index] i++ } return r }