diff --git a/bool_tree/tree_v4.go b/bool_tree/tree_v4.go index a58f404..1e43254 100644 --- a/bool_tree/tree_v4.go +++ b/bool_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag bool, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []bool { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []bool, nodeIndex uint, filterFunc FilterFunc) []bool { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]bool, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]bool, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) bool { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag bool, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []bool, nodeIndex uint, matchTag bool, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag bool, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag bool, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag bool) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag bool) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag bool, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag bool, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag bool, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal bool) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal bool) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []bool, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal bool) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]bool, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []bool { ret := make([]bool, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []bool, address patricia.IPv4Address) []bool { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []bool { + ret := make([]bool, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]bool, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []bool, address patricia.IPv4Address, filterFunc FilterFunc) []bool { var matchCount uint root := &t.nodes[1] - ret := make([]bool, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]bool, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]bool, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, bool, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, bool) { root := &t.nodes[1] var found bool var ret bool @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, bool, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, bool, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, bool, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, bool, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []bool, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []bool) { + ret := make([]bool, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []bool, address patricia.IPv4Address) (bool, []bool) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []bool, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []bool, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []bool, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/bool_tree/tree_v4_manual.go b/bool_tree/tree_v4_manual.go index 6444071..833a9f9 100644 --- a/bool_tree/tree_v4_manual.go +++ b/bool_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]bool, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/bool_tree/tree_v6_generated.go b/bool_tree/tree_v6_generated.go index 3fca655..800485e 100644 --- a/bool_tree/tree_v6_generated.go +++ b/bool_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag bool, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []bool { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []bool, nodeIndex uint, filterFunc FilterFunc) []bool { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]bool, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]bool, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) bool { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag bool, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []bool, nodeIndex uint, matchTag bool, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag bool, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag bool, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag bool) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag bool) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag bool, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag bool, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag bool, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal bool) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal bool) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []bool, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal bool) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]bool, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []bool { ret := make([]bool, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []bool, address patricia.IPv6Address) []bool { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []bool { + ret := make([]bool, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]bool, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []bool, address patricia.IPv6Address, filterFunc FilterFunc) []bool { var matchCount uint root := &t.nodes[1] - ret := make([]bool, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]bool, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]bool, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, bool, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, bool) { root := &t.nodes[1] var found bool var ret bool @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, bool, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, bool, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, bool, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, bool, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []bool, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []bool) { + ret := make([]bool, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []bool, address patricia.IPv6Address) (bool, []bool) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []bool, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []bool, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []bool, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/bool_tree/tree_v6_manual.go b/bool_tree/tree_v6_manual.go index fe878a8..b209e5f 100644 --- a/bool_tree/tree_v6_manual.go +++ b/bool_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]bool, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/byte_tree/tree_v4.go b/byte_tree/tree_v4.go index dabafb1..7e7e712 100644 --- a/byte_tree/tree_v4.go +++ b/byte_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag byte, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []byte { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []byte, nodeIndex uint, filterFunc FilterFunc) []byte { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]byte, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]byte, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) byte { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag byte, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []byte, nodeIndex uint, matchTag byte, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag byte, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag byte, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag byte) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag byte) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag byte, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag byte, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag byte, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal byte) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal byte) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []byte, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal byte) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]byte, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []byte { ret := make([]byte, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []byte, address patricia.IPv4Address) []byte { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []byte { + ret := make([]byte, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]byte, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []byte, address patricia.IPv4Address, filterFunc FilterFunc) []byte { var matchCount uint root := &t.nodes[1] - ret := make([]byte, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]byte, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]byte, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, byte, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, byte) { root := &t.nodes[1] var found bool var ret byte @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, byte, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, byte, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, byte, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, byte, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []byte, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []byte) { + ret := make([]byte, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []byte, address patricia.IPv4Address) (bool, []byte) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []byte, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []byte, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []byte, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/byte_tree/tree_v4_manual.go b/byte_tree/tree_v4_manual.go index fd0c62e..e9edfa8 100644 --- a/byte_tree/tree_v4_manual.go +++ b/byte_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]byte, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/byte_tree/tree_v6_generated.go b/byte_tree/tree_v6_generated.go index 4dcd120..29d49af 100644 --- a/byte_tree/tree_v6_generated.go +++ b/byte_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag byte, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []byte { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []byte, nodeIndex uint, filterFunc FilterFunc) []byte { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]byte, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]byte, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) byte { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag byte, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []byte, nodeIndex uint, matchTag byte, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag byte, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag byte, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag byte) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag byte) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag byte, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag byte, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag byte, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal byte) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal byte) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []byte, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal byte) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]byte, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []byte { ret := make([]byte, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []byte, address patricia.IPv6Address) []byte { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []byte { + ret := make([]byte, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]byte, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []byte, address patricia.IPv6Address, filterFunc FilterFunc) []byte { var matchCount uint root := &t.nodes[1] - ret := make([]byte, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]byte, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]byte, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, byte, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, byte) { root := &t.nodes[1] var found bool var ret byte @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, byte, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, byte, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, byte, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, byte, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []byte, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []byte) { + ret := make([]byte, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []byte, address patricia.IPv6Address) (bool, []byte) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []byte, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []byte, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []byte, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/byte_tree/tree_v6_manual.go b/byte_tree/tree_v6_manual.go index db042be..5dff890 100644 --- a/byte_tree/tree_v6_manual.go +++ b/byte_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]byte, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/complex128_tree/tree_v4.go b/complex128_tree/tree_v4.go index faa38d3..10f1650 100644 --- a/complex128_tree/tree_v4.go +++ b/complex128_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag complex128, nodeIndex uint, matchFunc MatchesFunc, r return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []complex128 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []complex128, nodeIndex uint, filterFunc FilterFunc) []complex128 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]complex128, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]complex128, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) complex128 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag complex128, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []complex128, nodeIndex uint, matchTag complex128, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag complex128, matchFunc Matche // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag complex128, matchFunc Matche // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag complex128) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag complex128) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag complex128, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag complex128, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex128, matchFunc Mat } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal complex128) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal complex128) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []complex128, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal complex128) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]complex128, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []complex128 { ret := make([]complex128, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []complex128, address patricia.IPv4Address) []complex128 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []complex128 { + ret := make([]complex128, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]complex128, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []complex128, address patricia.IPv4Address, filterFunc FilterFunc) []complex128 { var matchCount uint root := &t.nodes[1] - ret := make([]complex128, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]complex128, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]complex128, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex128, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex128) { root := &t.nodes[1] var found bool var ret complex128 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex128, if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex128, // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex128, if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex128, } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex128, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex128) { + ret := make([]complex128, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []complex128, address patricia.IPv4Address) (bool, []complex128) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex1 if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex1 // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex1 if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/complex128_tree/tree_v4_manual.go b/complex128_tree/tree_v4_manual.go index 8f8a0d0..12730a5 100644 --- a/complex128_tree/tree_v4_manual.go +++ b/complex128_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]complex128, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/complex128_tree/tree_v6_generated.go b/complex128_tree/tree_v6_generated.go index a0eaa06..df1293f 100644 --- a/complex128_tree/tree_v6_generated.go +++ b/complex128_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag complex128, nodeIndex uint, matchFunc MatchesFunc, r return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []complex128 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []complex128, nodeIndex uint, filterFunc FilterFunc) []complex128 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]complex128, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]complex128, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) complex128 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag complex128, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []complex128, nodeIndex uint, matchTag complex128, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag complex128, matchFunc Matche // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag complex128, matchFunc Matche // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag complex128) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag complex128) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag complex128, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag complex128, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex128, matchFunc Mat } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal complex128) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal complex128) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []complex128, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal complex128) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]complex128, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []complex128 { ret := make([]complex128, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []complex128, address patricia.IPv6Address) []complex128 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []complex128 { + ret := make([]complex128, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]complex128, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []complex128, address patricia.IPv6Address, filterFunc FilterFunc) []complex128 { var matchCount uint root := &t.nodes[1] - ret := make([]complex128, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]complex128, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]complex128, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex128, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex128) { root := &t.nodes[1] var found bool var ret complex128 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex128, if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex128, // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex128, if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex128, } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex128, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex128) { + ret := make([]complex128, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []complex128, address patricia.IPv6Address) (bool, []complex128) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex1 if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex1 // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex1 if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/complex128_tree/tree_v6_manual.go b/complex128_tree/tree_v6_manual.go index 188129e..0c81ed1 100644 --- a/complex128_tree/tree_v6_manual.go +++ b/complex128_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]complex128, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/complex64_tree/tree_v4.go b/complex64_tree/tree_v4.go index 7b52e42..92a3a31 100644 --- a/complex64_tree/tree_v4.go +++ b/complex64_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag complex64, nodeIndex uint, matchFunc MatchesFunc, re return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []complex64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []complex64, nodeIndex uint, filterFunc FilterFunc) []complex64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]complex64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]complex64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) complex64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag complex64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []complex64, nodeIndex uint, matchTag complex64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag complex64, matchFunc Matches // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag complex64, matchFunc Matches // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag complex64) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag complex64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag complex64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag complex64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag complex64, matchFunc Matc } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal complex64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal complex64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []complex64, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal complex64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]complex64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []complex64 { ret := make([]complex64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []complex64, address patricia.IPv4Address) []complex64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []complex64 { + ret := make([]complex64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]complex64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []complex64, address patricia.IPv4Address, filterFunc FilterFunc) []complex64 { var matchCount uint root := &t.nodes[1] - ret := make([]complex64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]complex64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]complex64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex64, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex64) { root := &t.nodes[1] var found bool var ret complex64 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex64, if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex64, // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex64, if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, complex64, } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex64) { + ret := make([]complex64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []complex64, address patricia.IPv4Address) (bool, []complex64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex6 if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex6 // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []complex6 if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/complex64_tree/tree_v4_manual.go b/complex64_tree/tree_v4_manual.go index cc4a0ac..5351cd0 100644 --- a/complex64_tree/tree_v4_manual.go +++ b/complex64_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]complex64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/complex64_tree/tree_v6_generated.go b/complex64_tree/tree_v6_generated.go index 201c9af..bda546b 100644 --- a/complex64_tree/tree_v6_generated.go +++ b/complex64_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag complex64, nodeIndex uint, matchFunc MatchesFunc, re return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []complex64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []complex64, nodeIndex uint, filterFunc FilterFunc) []complex64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]complex64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]complex64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) complex64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag complex64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []complex64, nodeIndex uint, matchTag complex64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag complex64, matchFunc Matches // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag complex64, matchFunc Matches // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag complex64) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag complex64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag complex64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag complex64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag complex64, matchFunc Matc } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal complex64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal complex64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []complex64, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal complex64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]complex64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []complex64 { ret := make([]complex64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []complex64, address patricia.IPv6Address) []complex64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []complex64 { + ret := make([]complex64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]complex64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []complex64, address patricia.IPv6Address, filterFunc FilterFunc) []complex64 { var matchCount uint root := &t.nodes[1] - ret := make([]complex64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]complex64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]complex64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex64, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex64) { root := &t.nodes[1] var found bool var ret complex64 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex64, if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex64, // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex64, if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, complex64, } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex64) { + ret := make([]complex64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []complex64, address patricia.IPv6Address) (bool, []complex64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex6 if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex6 // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []complex6 if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/complex64_tree/tree_v6_manual.go b/complex64_tree/tree_v6_manual.go index d139c37..2a4604a 100644 --- a/complex64_tree/tree_v6_manual.go +++ b/complex64_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]complex64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/float32_tree/tree_v4.go b/float32_tree/tree_v4.go index 17fb3ac..629b8e6 100644 --- a/float32_tree/tree_v4.go +++ b/float32_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag float32, nodeIndex uint, matchFunc MatchesFunc, repl return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []float32 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []float32, nodeIndex uint, filterFunc FilterFunc) []float32 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]float32, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]float32, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) float32 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag float32, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []float32, nodeIndex uint, matchTag float32, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag float32, matchFunc MatchesFu // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag float32, matchFunc MatchesFu // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag float32) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag float32) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag float32, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag float32, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float32, matchFunc Matche } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal float32) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal float32) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []float32, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal float32) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]float32, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []float32 { ret := make([]float32, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []float32, address patricia.IPv4Address) []float32 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []float32 { + ret := make([]float32, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]float32, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []float32, address patricia.IPv4Address, filterFunc FilterFunc) []float32 { var matchCount uint root := &t.nodes[1] - ret := make([]float32, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]float32, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]float32, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float32, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float32) { root := &t.nodes[1] var found bool var ret float32 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float32, er if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float32, er // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float32, er if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float32, er } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float32, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float32) { + ret := make([]float32, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []float32, address patricia.IPv4Address) (bool, []float32) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float32, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float32, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float32, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/float32_tree/tree_v4_manual.go b/float32_tree/tree_v4_manual.go index b2cf555..687fbdb 100644 --- a/float32_tree/tree_v4_manual.go +++ b/float32_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]float32, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/float32_tree/tree_v6_generated.go b/float32_tree/tree_v6_generated.go index a002105..81232ee 100644 --- a/float32_tree/tree_v6_generated.go +++ b/float32_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag float32, nodeIndex uint, matchFunc MatchesFunc, repl return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []float32 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []float32, nodeIndex uint, filterFunc FilterFunc) []float32 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]float32, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]float32, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) float32 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag float32, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []float32, nodeIndex uint, matchTag float32, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag float32, matchFunc MatchesFu // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag float32, matchFunc MatchesFu // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag float32) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag float32) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag float32, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag float32, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float32, matchFunc Matche } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal float32) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal float32) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []float32, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal float32) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]float32, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []float32 { ret := make([]float32, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []float32, address patricia.IPv6Address) []float32 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []float32 { + ret := make([]float32, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]float32, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []float32, address patricia.IPv6Address, filterFunc FilterFunc) []float32 { var matchCount uint root := &t.nodes[1] - ret := make([]float32, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]float32, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]float32, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float32, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float32) { root := &t.nodes[1] var found bool var ret float32 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float32, er if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float32, er // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float32, er if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float32, er } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float32, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float32) { + ret := make([]float32, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []float32, address patricia.IPv6Address) (bool, []float32) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float32, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float32, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float32, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/float32_tree/tree_v6_manual.go b/float32_tree/tree_v6_manual.go index d9cf06b..f46002c 100644 --- a/float32_tree/tree_v6_manual.go +++ b/float32_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]float32, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/float64_tree/tree_v4.go b/float64_tree/tree_v4.go index 9a765c3..9516fab 100644 --- a/float64_tree/tree_v4.go +++ b/float64_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag float64, nodeIndex uint, matchFunc MatchesFunc, repl return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []float64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []float64, nodeIndex uint, filterFunc FilterFunc) []float64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]float64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]float64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) float64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag float64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []float64, nodeIndex uint, matchTag float64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag float64, matchFunc MatchesFu // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag float64, matchFunc MatchesFu // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag float64) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag float64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag float64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag float64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag float64, matchFunc Matche } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal float64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal float64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []float64, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal float64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]float64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []float64 { ret := make([]float64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []float64, address patricia.IPv4Address) []float64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []float64 { + ret := make([]float64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]float64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []float64, address patricia.IPv4Address, filterFunc FilterFunc) []float64 { var matchCount uint root := &t.nodes[1] - ret := make([]float64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]float64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]float64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float64, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float64) { root := &t.nodes[1] var found bool var ret float64 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float64, er if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float64, er // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float64, er if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, float64, er } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float64) { + ret := make([]float64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []float64, address patricia.IPv4Address) (bool, []float64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float64, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float64, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []float64, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/float64_tree/tree_v4_manual.go b/float64_tree/tree_v4_manual.go index 8470d04..adcdcf8 100644 --- a/float64_tree/tree_v4_manual.go +++ b/float64_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]float64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/float64_tree/tree_v6_generated.go b/float64_tree/tree_v6_generated.go index b95c952..8d05d61 100644 --- a/float64_tree/tree_v6_generated.go +++ b/float64_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag float64, nodeIndex uint, matchFunc MatchesFunc, repl return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []float64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []float64, nodeIndex uint, filterFunc FilterFunc) []float64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]float64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]float64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) float64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag float64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []float64, nodeIndex uint, matchTag float64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag float64, matchFunc MatchesFu // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag float64, matchFunc MatchesFu // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag float64) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag float64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag float64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag float64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag float64, matchFunc Matche } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal float64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal float64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []float64, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal float64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]float64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []float64 { ret := make([]float64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []float64, address patricia.IPv6Address) []float64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []float64 { + ret := make([]float64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]float64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []float64, address patricia.IPv6Address, filterFunc FilterFunc) []float64 { var matchCount uint root := &t.nodes[1] - ret := make([]float64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]float64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]float64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float64, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float64) { root := &t.nodes[1] var found bool var ret float64 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float64, er if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float64, er // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float64, er if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, float64, er } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float64) { + ret := make([]float64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []float64, address patricia.IPv6Address) (bool, []float64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float64, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float64, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []float64, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/float64_tree/tree_v6_manual.go b/float64_tree/tree_v6_manual.go index 8a46502..c5fc87e 100644 --- a/float64_tree/tree_v6_manual.go +++ b/float64_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]float64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int16_tree/tree_v4.go b/int16_tree/tree_v4.go index 445b1d1..38b5024 100644 --- a/int16_tree/tree_v4.go +++ b/int16_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag int16, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []int16 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []int16, nodeIndex uint, filterFunc FilterFunc) []int16 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int16, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int16, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) int16 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int16, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []int16, nodeIndex uint, matchTag int16, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int16, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int16, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag int16) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag int16) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag int16, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag int16, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int16, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int16) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int16) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []int16, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int16) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]int16, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []int16 { ret := make([]int16, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []int16, address patricia.IPv4Address) []int16 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []int16 { + ret := make([]int16, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int16, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []int16, address patricia.IPv4Address, filterFunc FilterFunc) []int16 { var matchCount uint root := &t.nodes[1] - ret := make([]int16, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int16, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int16, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int16, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int16) { root := &t.nodes[1] var found bool var ret int16 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int16, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int16, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int16, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int16, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int16, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int16) { + ret := make([]int16, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []int16, address patricia.IPv4Address) (bool, []int16) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int16, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int16, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int16, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int16_tree/tree_v4_manual.go b/int16_tree/tree_v4_manual.go index 9203aee..4dbc45c 100644 --- a/int16_tree/tree_v4_manual.go +++ b/int16_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]int16, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int16_tree/tree_v6_generated.go b/int16_tree/tree_v6_generated.go index c1ef07b..6ee4829 100644 --- a/int16_tree/tree_v6_generated.go +++ b/int16_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag int16, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []int16 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []int16, nodeIndex uint, filterFunc FilterFunc) []int16 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int16, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int16, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) int16 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int16, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []int16, nodeIndex uint, matchTag int16, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int16, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int16, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag int16) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag int16) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag int16, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag int16, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int16, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int16) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int16) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []int16, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int16) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]int16, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []int16 { ret := make([]int16, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []int16, address patricia.IPv6Address) []int16 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []int16 { + ret := make([]int16, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int16, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []int16, address patricia.IPv6Address, filterFunc FilterFunc) []int16 { var matchCount uint root := &t.nodes[1] - ret := make([]int16, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int16, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int16, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int16, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int16) { root := &t.nodes[1] var found bool var ret int16 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int16, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int16, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int16, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int16, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int16, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int16) { + ret := make([]int16, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []int16, address patricia.IPv6Address) (bool, []int16) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int16, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int16, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int16, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int16_tree/tree_v6_manual.go b/int16_tree/tree_v6_manual.go index 6330e12..d8424e4 100644 --- a/int16_tree/tree_v6_manual.go +++ b/int16_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]int16, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int32_tree/tree_v4.go b/int32_tree/tree_v4.go index f33f37d..72c957c 100644 --- a/int32_tree/tree_v4.go +++ b/int32_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag int32, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []int32 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []int32, nodeIndex uint, filterFunc FilterFunc) []int32 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int32, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int32, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) int32 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int32, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []int32, nodeIndex uint, matchTag int32, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int32, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int32, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag int32) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag int32) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag int32, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag int32, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int32, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int32) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int32) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []int32, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int32) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]int32, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []int32 { ret := make([]int32, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []int32, address patricia.IPv4Address) []int32 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []int32 { + ret := make([]int32, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int32, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []int32, address patricia.IPv4Address, filterFunc FilterFunc) []int32 { var matchCount uint root := &t.nodes[1] - ret := make([]int32, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int32, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int32, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int32, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int32) { root := &t.nodes[1] var found bool var ret int32 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int32, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int32, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int32, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int32, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int32, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int32) { + ret := make([]int32, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []int32, address patricia.IPv4Address) (bool, []int32) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int32, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int32, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int32, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int32_tree/tree_v4_manual.go b/int32_tree/tree_v4_manual.go index 10dad35..b7786d4 100644 --- a/int32_tree/tree_v4_manual.go +++ b/int32_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]int32, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int32_tree/tree_v6_generated.go b/int32_tree/tree_v6_generated.go index 0512284..c657206 100644 --- a/int32_tree/tree_v6_generated.go +++ b/int32_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag int32, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []int32 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []int32, nodeIndex uint, filterFunc FilterFunc) []int32 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int32, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int32, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) int32 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int32, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []int32, nodeIndex uint, matchTag int32, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int32, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int32, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag int32) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag int32) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag int32, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag int32, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int32, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int32) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int32) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []int32, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int32) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]int32, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []int32 { ret := make([]int32, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []int32, address patricia.IPv6Address) []int32 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []int32 { + ret := make([]int32, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int32, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []int32, address patricia.IPv6Address, filterFunc FilterFunc) []int32 { var matchCount uint root := &t.nodes[1] - ret := make([]int32, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int32, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int32, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int32, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int32) { root := &t.nodes[1] var found bool var ret int32 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int32, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int32, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int32, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int32, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int32, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int32) { + ret := make([]int32, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []int32, address patricia.IPv6Address) (bool, []int32) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int32, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int32, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int32, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int32_tree/tree_v6_manual.go b/int32_tree/tree_v6_manual.go index c3d1768..cb834cf 100644 --- a/int32_tree/tree_v6_manual.go +++ b/int32_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]int32, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int64_tree/tree_v4.go b/int64_tree/tree_v4.go index 18358e6..055b3b3 100644 --- a/int64_tree/tree_v4.go +++ b/int64_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag int64, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []int64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []int64, nodeIndex uint, filterFunc FilterFunc) []int64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) int64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []int64, nodeIndex uint, matchTag int64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int64, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int64, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag int64) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag int64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag int64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag int64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int64, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []int64, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]int64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []int64 { ret := make([]int64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []int64, address patricia.IPv4Address) []int64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []int64 { + ret := make([]int64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []int64, address patricia.IPv4Address, filterFunc FilterFunc) []int64 { var matchCount uint root := &t.nodes[1] - ret := make([]int64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int64, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int64) { root := &t.nodes[1] var found bool var ret int64 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int64, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int64, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int64, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int64, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int64) { + ret := make([]int64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []int64, address patricia.IPv4Address) (bool, []int64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int64, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int64, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int64, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int64_tree/tree_v4_manual.go b/int64_tree/tree_v4_manual.go index 2c3bb08..a45eb98 100644 --- a/int64_tree/tree_v4_manual.go +++ b/int64_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]int64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int64_tree/tree_v6_generated.go b/int64_tree/tree_v6_generated.go index 21ffa67..7c75a91 100644 --- a/int64_tree/tree_v6_generated.go +++ b/int64_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag int64, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []int64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []int64, nodeIndex uint, filterFunc FilterFunc) []int64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) int64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []int64, nodeIndex uint, matchTag int64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int64, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int64, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag int64) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag int64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag int64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag int64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int64, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []int64, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]int64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []int64 { ret := make([]int64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []int64, address patricia.IPv6Address) []int64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []int64 { + ret := make([]int64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []int64, address patricia.IPv6Address, filterFunc FilterFunc) []int64 { var matchCount uint root := &t.nodes[1] - ret := make([]int64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int64, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int64) { root := &t.nodes[1] var found bool var ret int64 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int64, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int64, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int64, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int64, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int64) { + ret := make([]int64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []int64, address patricia.IPv6Address) (bool, []int64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int64, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int64, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int64, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int64_tree/tree_v6_manual.go b/int64_tree/tree_v6_manual.go index 7e5b355..c6a673e 100644 --- a/int64_tree/tree_v6_manual.go +++ b/int64_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]int64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int8_tree/tree_v4.go b/int8_tree/tree_v4.go index 2f53e24..36eb552 100644 --- a/int8_tree/tree_v4.go +++ b/int8_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag int8, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []int8 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []int8, nodeIndex uint, filterFunc FilterFunc) []int8 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int8, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int8, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) int8 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int8, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []int8, nodeIndex uint, matchTag int8, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int8, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int8, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag int8) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag int8) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag int8, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag int8, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int8, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int8) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int8) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []int8, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int8) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]int8, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []int8 { ret := make([]int8, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []int8, address patricia.IPv4Address) []int8 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []int8 { + ret := make([]int8, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int8, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []int8, address patricia.IPv4Address, filterFunc FilterFunc) []int8 { var matchCount uint root := &t.nodes[1] - ret := make([]int8, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int8, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int8, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int8, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int8) { root := &t.nodes[1] var found bool var ret int8 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int8, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int8, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int8, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int8, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int8, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int8) { + ret := make([]int8, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []int8, address patricia.IPv4Address) (bool, []int8) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int8, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int8, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int8, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int8_tree/tree_v4_manual.go b/int8_tree/tree_v4_manual.go index a6798f7..4bf0bbd 100644 --- a/int8_tree/tree_v4_manual.go +++ b/int8_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]int8, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int8_tree/tree_v6_generated.go b/int8_tree/tree_v6_generated.go index 681aafd..dda7d67 100644 --- a/int8_tree/tree_v6_generated.go +++ b/int8_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag int8, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []int8 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []int8, nodeIndex uint, filterFunc FilterFunc) []int8 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int8, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int8, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) int8 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int8, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []int8, nodeIndex uint, matchTag int8, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int8, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int8, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag int8) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag int8) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag int8, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag int8, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int8, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int8) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int8) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []int8, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int8) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]int8, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []int8 { ret := make([]int8, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []int8, address patricia.IPv6Address) []int8 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []int8 { + ret := make([]int8, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int8, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []int8, address patricia.IPv6Address, filterFunc FilterFunc) []int8 { var matchCount uint root := &t.nodes[1] - ret := make([]int8, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int8, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int8, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int8, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int8) { root := &t.nodes[1] var found bool var ret int8 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int8, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int8, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int8, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int8, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int8, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int8) { + ret := make([]int8, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []int8, address patricia.IPv6Address) (bool, []int8) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int8, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int8, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int8, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int8_tree/tree_v6_manual.go b/int8_tree/tree_v6_manual.go index 44850c3..ce9b132 100644 --- a/int8_tree/tree_v6_manual.go +++ b/int8_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]int8, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int_tree/tree_v4.go b/int_tree/tree_v4.go index cc4d96f..4d07772 100644 --- a/int_tree/tree_v4.go +++ b/int_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag int, nodeIndex uint, matchFunc MatchesFunc, replaceF return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []int { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []int, nodeIndex uint, filterFunc FilterFunc) []int { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) int { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []int, nodeIndex uint, matchTag int, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag int, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag int) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag int) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag int, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag int, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag int, matchFunc MatchesFun } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []int, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal int) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]int, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []int { ret := make([]int, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []int, address patricia.IPv4Address) []int { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []int { + ret := make([]int, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []int, address patricia.IPv4Address, filterFunc FilterFunc) []int { var matchCount uint root := &t.nodes[1] - ret := make([]int, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]int, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int) { root := &t.nodes[1] var found bool var ret int @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int, error) if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int, error) // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int, error) if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, int, error) } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int) { + ret := make([]int, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []int, address patricia.IPv4Address) (bool, []int) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int, err if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int, err // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []int, err if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int_tree/tree_v4_manual.go b/int_tree/tree_v4_manual.go index eec9596..3fafaa5 100644 --- a/int_tree/tree_v4_manual.go +++ b/int_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]int, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/int_tree/tree_v6_generated.go b/int_tree/tree_v6_generated.go index 2ce9c46..2a74df6 100644 --- a/int_tree/tree_v6_generated.go +++ b/int_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag int, nodeIndex uint, matchFunc MatchesFunc, replaceF return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []int { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []int, nodeIndex uint, filterFunc FilterFunc) []int { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]int, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]int, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) int { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []int, nodeIndex uint, matchTag int, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag int, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag int) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag int) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag int, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag int, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag int, matchFunc MatchesFun } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []int, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal int) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]int, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []int { ret := make([]int, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []int, address patricia.IPv6Address) []int { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []int { + ret := make([]int, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []int, address patricia.IPv6Address, filterFunc FilterFunc) []int { var matchCount uint root := &t.nodes[1] - ret := make([]int, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]int, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int) { root := &t.nodes[1] var found bool var ret int @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int, error) if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int, error) // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int, error) if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, int, error) } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int) { + ret := make([]int, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []int, address patricia.IPv6Address) (bool, []int) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int, err if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int, err // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []int, err if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/int_tree/tree_v6_manual.go b/int_tree/tree_v6_manual.go index b237ebd..a8a4c35 100644 --- a/int_tree/tree_v6_manual.go +++ b/int_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]int, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/rune_tree/tree_v4.go b/rune_tree/tree_v4.go index 4ee7ebf..ca5e837 100644 --- a/rune_tree/tree_v4.go +++ b/rune_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag rune, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []rune { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []rune, nodeIndex uint, filterFunc FilterFunc) []rune { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]rune, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]rune, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) rune { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag rune, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []rune, nodeIndex uint, matchTag rune, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag rune, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag rune, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag rune) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag rune) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag rune, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag rune, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag rune, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal rune) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal rune) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []rune, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal rune) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]rune, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []rune { ret := make([]rune, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []rune, address patricia.IPv4Address) []rune { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []rune { + ret := make([]rune, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]rune, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []rune, address patricia.IPv4Address, filterFunc FilterFunc) []rune { var matchCount uint root := &t.nodes[1] - ret := make([]rune, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]rune, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]rune, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, rune, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, rune) { root := &t.nodes[1] var found bool var ret rune @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, rune, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, rune, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, rune, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, rune, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []rune, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []rune) { + ret := make([]rune, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []rune, address patricia.IPv4Address) (bool, []rune) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []rune, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []rune, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []rune, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/rune_tree/tree_v4_manual.go b/rune_tree/tree_v4_manual.go index 89dd3eb..e016d74 100644 --- a/rune_tree/tree_v4_manual.go +++ b/rune_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]rune, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/rune_tree/tree_v6_generated.go b/rune_tree/tree_v6_generated.go index 54d79c6..fd6b9da 100644 --- a/rune_tree/tree_v6_generated.go +++ b/rune_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag rune, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []rune { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []rune, nodeIndex uint, filterFunc FilterFunc) []rune { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]rune, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]rune, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) rune { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag rune, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []rune, nodeIndex uint, matchTag rune, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag rune, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag rune, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag rune) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag rune) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag rune, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag rune, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag rune, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal rune) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal rune) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []rune, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal rune) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]rune, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []rune { ret := make([]rune, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []rune, address patricia.IPv6Address) []rune { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []rune { + ret := make([]rune, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]rune, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []rune, address patricia.IPv6Address, filterFunc FilterFunc) []rune { var matchCount uint root := &t.nodes[1] - ret := make([]rune, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]rune, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]rune, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, rune, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, rune) { root := &t.nodes[1] var found bool var ret rune @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, rune, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, rune, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, rune, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, rune, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []rune, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []rune) { + ret := make([]rune, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []rune, address patricia.IPv6Address) (bool, []rune) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []rune, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []rune, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []rune, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/rune_tree/tree_v6_manual.go b/rune_tree/tree_v6_manual.go index f18debd..2fa1470 100644 --- a/rune_tree/tree_v6_manual.go +++ b/rune_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]rune, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/string_tree/tree_v4.go b/string_tree/tree_v4.go index 8ae1eaa..b47a65e 100644 --- a/string_tree/tree_v4.go +++ b/string_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag string, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []string { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []string, nodeIndex uint, filterFunc FilterFunc) []string { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]string, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]string, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) string { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag string, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []string, nodeIndex uint, matchTag string, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag string, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag string, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag string) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag string) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag string, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag string, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag string, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal string) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal string) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []string, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal string) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]string, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []string { ret := make([]string, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []string, address patricia.IPv4Address) []string { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []string { + ret := make([]string, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]string, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []string, address patricia.IPv4Address, filterFunc FilterFunc) []string { var matchCount uint root := &t.nodes[1] - ret := make([]string, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]string, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]string, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, string, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, string) { root := &t.nodes[1] var found bool var ret string @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, string, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, string, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, string, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, string, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []string, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []string) { + ret := make([]string, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []string, address patricia.IPv4Address) (bool, []string) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []string, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []string, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []string, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/string_tree/tree_v4_manual.go b/string_tree/tree_v4_manual.go index ed7fd55..2bc0bae 100644 --- a/string_tree/tree_v4_manual.go +++ b/string_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]string, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/string_tree/tree_v6_generated.go b/string_tree/tree_v6_generated.go index dd19a73..6434c67 100644 --- a/string_tree/tree_v6_generated.go +++ b/string_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag string, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []string { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []string, nodeIndex uint, filterFunc FilterFunc) []string { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]string, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]string, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) string { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag string, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []string, nodeIndex uint, matchTag string, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag string, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag string, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag string) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag string) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag string, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag string, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag string, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal string) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal string) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []string, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal string) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]string, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []string { ret := make([]string, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []string, address patricia.IPv6Address) []string { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []string { + ret := make([]string, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]string, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []string, address patricia.IPv6Address, filterFunc FilterFunc) []string { var matchCount uint root := &t.nodes[1] - ret := make([]string, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]string, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]string, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, string, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, string) { root := &t.nodes[1] var found bool var ret string @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, string, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, string, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, string, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, string, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []string, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []string) { + ret := make([]string, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []string, address patricia.IPv6Address) (bool, []string) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []string, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []string, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []string, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/string_tree/tree_v6_manual.go b/string_tree/tree_v6_manual.go index 2b21c78..cbc2987 100644 --- a/string_tree/tree_v6_manual.go +++ b/string_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]string, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/template/tree_v4.go b/template/tree_v4.go index 8a2a117..077ff8b 100644 --- a/template/tree_v4.go +++ b/template/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag GeneratedType, nodeIndex uint, matchFunc MatchesFunc return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []GeneratedType { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []GeneratedType, nodeIndex uint, filterFunc FilterFunc) []GeneratedType { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]GeneratedType, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]GeneratedType, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) GeneratedType { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag GeneratedType, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []GeneratedType, nodeIndex uint, matchTag GeneratedType, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag GeneratedType, matchFunc Mat // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag GeneratedType, matchFunc Mat // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag GeneratedType) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag GeneratedType) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag GeneratedType, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag GeneratedType, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag GeneratedType, matchFunc } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal GeneratedType) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal GeneratedType) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []GeneratedType, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal GeneratedType) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]GeneratedType, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []GeneratedType { ret := make([]GeneratedType, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []GeneratedType, address patricia.IPv4Address) []GeneratedType { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []GeneratedType { + ret := make([]GeneratedType, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]GeneratedType, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []GeneratedType, address patricia.IPv4Address, filterFunc FilterFunc) []GeneratedType { var matchCount uint root := &t.nodes[1] - ret := make([]GeneratedType, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]GeneratedType, error) } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]GeneratedType, error) // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, GeneratedType, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, GeneratedType) { root := &t.nodes[1] var found bool var ret GeneratedType @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, GeneratedTy if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, GeneratedTy // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, GeneratedTy if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, GeneratedTy } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []GeneratedType, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []GeneratedType) { + ret := make([]GeneratedType, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []GeneratedType, address patricia.IPv4Address) (bool, []GeneratedType) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []Generate if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []Generate // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []Generate if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/template/tree_v4_bulk_test.go b/template/tree_v4_bulk_test.go index 6d7f273..8b1d584 100644 --- a/template/tree_v4_bulk_test.go +++ b/template/tree_v4_bulk_test.go @@ -73,6 +73,7 @@ func TestBulkLoad(t *testing.T) { assert.Equal(t, recordsLoaded, tree.CountTags()) } + buf := make([]GeneratedType, 0) evaluate := func() { fmt.Printf("# of nodes: %d\n", len(tree.nodes)) // query all tags from each address, query specific tag from each address, delete the tag @@ -83,21 +84,18 @@ func TestBulkLoad(t *testing.T) { panic(fmt.Sprintf("search: Could not parse IP '%s': %s", address, err)) } if v4 != nil { - foundTags, err := tree.FindTags(*v4) - assert.NoError(t, err) + foundTags := tree.FindTagsAppend(buf, *v4) if assert.True(t, len(foundTags) > 0, "Couldn't find tags for "+address) { assert.True(t, tag == foundTags[len(foundTags)-1]) } - found, foundTag, err := tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, foundTag := tree.FindDeepestTag(*v4) assert.True(t, found, "Couldn't find deepest tag") assert.True(t, tag == foundTag) // delete the tags now //fmt.Printf("Deleting %s: %s\n", address, tag) - deleteCount, err := tree.Delete(*v4, func(a GeneratedType, b GeneratedType) bool { return a == b }, tag) - assert.NoError(t, err) + deleteCount := tree.DeleteWithBuffer(buf, *v4, func(a GeneratedType, b GeneratedType) bool { return a == b }, tag) assert.Equal(t, 1, deleteCount, "Tried deleting tag") //tree.print() } else if v6 == nil { diff --git a/template/tree_v4_manual.go b/template/tree_v4_manual.go index 104b8df..106cbd6 100644 --- a/template/tree_v4_manual.go +++ b/template/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]GeneratedType, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/template/tree_v4_test.go b/template/tree_v4_test.go index cd0d5bc..46e77a6 100644 --- a/template/tree_v4_test.go +++ b/template/tree_v4_test.go @@ -30,10 +30,36 @@ func BenchmarkFindTags(b *testing.B) { tree.Add(ipv4FromBytes([]byte{160, 0, 0, 0}, 2), tagB, nil) // 160 -> 128 tree.Add(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), tagC, nil) + address := patricia.NewIPv4Address(uint32(2156823809), 32) + + var buf []GeneratedType b.ResetTimer() for n := 0; n < b.N; n++ { - address := patricia.NewIPv4Address(uint32(2156823809), 32) - tree.FindTags(address) + buf = tree.FindTags(address) + } + _ = buf +} + +func BenchmarkFindTagsAppend(b *testing.B) { + tagA := "tagA" + tagB := "tagB" + tagC := "tagC" + tagZ := "tagD" + + tree := NewTreeV4() + + buf := make([]GeneratedType, 0) + tree.Add(patricia.IPv4Address{}, tagZ, nil) // default + tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), tagA, nil) + tree.Add(ipv4FromBytes([]byte{160, 0, 0, 0}, 2), tagB, nil) // 160 -> 128 + tree.Add(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), tagC, nil) + + address := patricia.NewIPv4Address(uint32(2156823809), 32) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + buf = buf[:0] + buf = tree.FindTagsAppend(buf, address) } } @@ -79,8 +105,10 @@ func BenchmarkBuildTreeAndFindDeepestTag(b *testing.B) { } func TestTree2(t *testing.T) { - tree := NewTreeV4() + tags := make([]GeneratedType, 0) + found := false + tree := NewTreeV4() // insert a bunch of tags v4, _, err := patricia.ParseIPFromString("1.2.3.0/24") assert.NoError(t, err) @@ -142,105 +170,94 @@ func TestTree2(t *testing.T) { // -------- // now assert they're all found v4, _, _ = patricia.ParseIPFromString("188.212.216.242") - found, tag, err := tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag := tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "a", tag) v4, _, _ = patricia.ParseIPFromString("171.233.143.228") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "b", tag) v4, _, _ = patricia.ParseIPFromString("186.244.183.12") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "c", tag) v4, _, _ = patricia.ParseIPFromString("171.233.143.222") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "d", tag) v4, _, _ = patricia.ParseIPFromString("190.207.189.24") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "e", tag) v4, _, _ = patricia.ParseIPFromString("188.212.216.240") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "f", tag) v4, _, _ = patricia.ParseIPFromString("185.76.10.148") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "g", tag) v4, _, _ = patricia.ParseIPFromString("14.208.248.50") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "h", tag) v4, _, _ = patricia.ParseIPFromString("59.60.75.52") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "i", tag) v4, _, _ = patricia.ParseIPFromString("185.76.10.146") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "j", tag) + tags = tags[:0] v4, _, _ = patricia.ParseIPFromString("185.76.10.146") - found, tags, err := tree.FindDeepestTags(*v4) - assert.NoError(t, err) + found, tags = tree.FindDeepestTagsAppend(tags, *v4) assert.True(t, found) assert.Equal(t, "j", tags[0]) assert.Equal(t, "k", tags[1]) // test searching for addresses with no leaf nodes + tags = tags[:0] v4, _, _ = patricia.ParseIPFromString("1.2.3.4") - found, tags, err = tree.FindDeepestTags(*v4) - assert.NoError(t, err) + found, tags = tree.FindDeepestTagsAppend(tags, *v4) assert.True(t, found) assert.Equal(t, "foo", tags[0]) assert.Equal(t, "bar", tags[1]) + tags = tags[:0] v4, _, _ = patricia.ParseIPFromString("1.2.3.5") - found, tags, err = tree.FindDeepestTags(*v4) - assert.NoError(t, err) + found, tags = tree.FindDeepestTagsAppend(tags, *v4) assert.True(t, found) assert.Equal(t, "foo", tags[0]) assert.Equal(t, "bar", tags[1]) v4, _, _ = patricia.ParseIPFromString("1.2.3.4") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "foo", tag) // test searching for an address that has nothing + tags = tags[:0] v4, _, _ = patricia.ParseIPFromString("9.9.9.9") - found, tags, err = tree.FindDeepestTags(*v4) - assert.NoError(t, err) + found, tags = tree.FindDeepestTagsAppend(tags, *v4) assert.False(t, found) assert.NotNil(t, tags) assert.Equal(t, 0, len(tags)) // test searching for an empty address + tags = tags[:0] v4, _, _ = patricia.ParseIPFromString("9.9.9.9/0") - found, tags, err = tree.FindDeepestTags(*v4) - assert.NoError(t, err) + found, tags = tree.FindDeepestTagsAppend(tags, *v4) assert.False(t, found) assert.NotNil(t, tags) assert.Equal(t, 0, len(tags)) @@ -252,22 +269,140 @@ func TestTree2(t *testing.T) { tree.Add(*v4, "root_node", nil) v4, _, _ = patricia.ParseIPFromString("9.9.9.9/0") - found, tags, err = tree.FindDeepestTags(*v4) - assert.NoError(t, err) + tags = tags[:0] + found, tags = tree.FindDeepestTagsAppend(tags, *v4) assert.True(t, found) assert.NotNil(t, tags) assert.Equal(t, 1, len(tags)) assert.Equal(t, "root_node", tags[0]) v4, _, _ = patricia.ParseIPFromString("9.9.9.9/0") - found, tag, err = tree.FindDeepestTag(*v4) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "root_node", tag) } +// TestFindDeepestTags tests self-allocating FindDeepestTags +func TestFindDeepestTags(t *testing.T) { + assert := assert.New(t) + + tags := make([]GeneratedType, 0) + found := false + + tree := NewTreeV4() + // insert a bunch of tags + v4, _, err := patricia.ParseIPFromString("1.2.3.0/24") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "foo", nil) + tree.Add(*v4, "bar", nil) + + v4, _, err = patricia.ParseIPFromString("188.212.216.242") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "a", nil) + + v4, _, err = patricia.ParseIPFromString("171.233.143.228") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "b", nil) + + v4, _, err = patricia.ParseIPFromString("186.244.183.12") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "c", nil) + + v4, _, err = patricia.ParseIPFromString("171.233.143.222") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "d", nil) + + v4, _, err = patricia.ParseIPFromString("190.207.189.24") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "e", nil) + + v4, _, err = patricia.ParseIPFromString("188.212.216.240") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "f", nil) + + v4, _, err = patricia.ParseIPFromString("185.76.10.148") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "g", nil) + + v4, _, err = patricia.ParseIPFromString("14.208.248.50") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "h", nil) + + v4, _, err = patricia.ParseIPFromString("59.60.75.52") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "i", nil) + + v4, _, err = patricia.ParseIPFromString("185.76.10.146") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "j", nil) + tree.Add(*v4, "k", nil) + + // now test + v4, _, _ = patricia.ParseIPFromString("185.76.10.146") + found, tags = tree.FindDeepestTags(*v4) + assert.True(found) + assert.Equal("j", tags[0]) + assert.Equal("k", tags[1]) + assert.Equal(2, len(tags)) + + // test searching for addresses with no leaf nodes + v4, _, _ = patricia.ParseIPFromString("1.2.3.4") + found, tags = tree.FindDeepestTags(*v4) + assert.True(found) + assert.Equal("foo", tags[0]) + assert.Equal("bar", tags[1]) + assert.Equal(2, len(tags)) + + v4, _, _ = patricia.ParseIPFromString("1.2.3.5") + found, tags = tree.FindDeepestTags(*v4) + assert.True(found) + assert.Equal("foo", tags[0]) + assert.Equal("bar", tags[1]) + assert.Equal(2, len(tags)) + + // test searching for an address that has nothing + v4, _, _ = patricia.ParseIPFromString("9.9.9.9") + found, tags = tree.FindDeepestTags(*v4) + assert.False(found) + assert.NotNil(tags) + assert.Equal(0, len(tags)) + + // test searching for an empty address + v4, _, _ = patricia.ParseIPFromString("9.9.9.9/0") + found, tags = tree.FindDeepestTags(*v4) + assert.False(found) + assert.NotNil(tags) + assert.Equal(0, len(tags)) + + // add a root node tag and try again + v4, _, err = patricia.ParseIPFromString("1.1.1.1/0") + assert.NoError(err) + assert.NotNil(v4) + tree.Add(*v4, "root_node", nil) + + v4, _, _ = patricia.ParseIPFromString("9.9.9.9/0") + found, tags = tree.FindDeepestTags(*v4) + assert.True(found) + assert.NotNil(tags) + assert.Equal(1, len(tags)) + assert.Equal("root_node", tags[0]) +} + // test that the find functions don't destroy an address - too brittle and confusing for caller for what gains? func TestAddressReusable(t *testing.T) { + tags := make([]GeneratedType, 0) + tree := NewTreeV4() pv4, pv6, err := patricia.ParseIPFromString("59.60.75.53") // needs to share same second-level node with the address we're going to work with @@ -282,30 +417,28 @@ func TestAddressReusable(t *testing.T) { assert.Nil(t, v6) tree.Add(*v4, "Hello", nil) - found, tag, err := tree.FindDeepestTag(*v4) + found, tag := tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "Hello", tag) - assert.NoError(t, err) // search again with same address - found, tag, err = tree.FindDeepestTag(*v4) + found, tag = tree.FindDeepestTag(*v4) assert.True(t, found) assert.Equal(t, "Hello", tag) - assert.NoError(t, err) // search again with same address - tags, err := tree.FindTags(*v4) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, *v4) if assert.Equal(t, 1, len(tags)) { assert.Equal(t, "Hello", tags[0]) } - assert.NoError(t, err) // search again with same address - tags, err = tree.FindTags(*v4) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, *v4) if assert.Equal(t, 1, len(tags)) { assert.Equal(t, "Hello", tags[0]) } - assert.NoError(t, err) } func TestSimpleTree1(t *testing.T) { @@ -319,24 +452,198 @@ func TestSimpleTree1(t *testing.T) { tree.Add(ipv4b, "tagB", nil) tree.Add(ipv4c, "tagC", nil) - found, tag, err := tree.FindDeepestTag(ipv4FromBytes([]byte{98, 139, 183, 24}, 32)) - assert.NoError(t, err) + found, tag := tree.FindDeepestTag(ipv4FromBytes([]byte{98, 139, 183, 24}, 32)) assert.True(t, found) assert.Equal(t, "tagA", tag) - found, tag, err = tree.FindDeepestTag(ipv4FromBytes([]byte{198, 186, 190, 179}, 32)) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(ipv4FromBytes([]byte{198, 186, 190, 179}, 32)) assert.True(t, found) assert.Equal(t, "tagB", tag) - found, tag, err = tree.FindDeepestTag(ipv4FromBytes([]byte{151, 101, 124, 84}, 32)) - assert.NoError(t, err) + found, tag = tree.FindDeepestTag(ipv4FromBytes([]byte{151, 101, 124, 84}, 32)) assert.True(t, found) assert.Equal(t, "tagC", tag) } +// TestSimpleTree1Append tests that FindTagsAppend appends +func TestSimpleTree1Append(t *testing.T) { + tree := NewTreeV4() + + ipv4a := ipv4FromBytes([]byte{98, 139, 183, 24}, 32) + ipv4b := ipv4FromBytes([]byte{198, 186, 190, 179}, 32) + ipv4c := ipv4FromBytes([]byte{151, 101, 124, 84}, 32) + + tree.Add(ipv4a, "tagA", nil) + tree.Add(ipv4b, "tagB", nil) + tree.Add(ipv4c, "tagC", nil) + + tags := make([]GeneratedType, 0) + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{98, 139, 183, 24}, 32)) + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "tagA", tags[0]) + + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{198, 186, 190, 179}, 32)) + assert.Equal(t, 2, len(tags)) + assert.Equal(t, "tagA", tags[0]) + assert.Equal(t, "tagB", tags[1]) + + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{151, 101, 124, 84}, 32)) + assert.Equal(t, 3, len(tags)) + assert.Equal(t, "tagA", tags[0]) + assert.Equal(t, "tagB", tags[1]) + assert.Equal(t, "tagC", tags[2]) +} + +// TestSimpleTree1Append tests the self-allocating FindTags +func TestSimpleTree1FindTags(t *testing.T) { + tree := NewTreeV4() + + ipv4a := ipv4FromBytes([]byte{98, 139, 183, 24}, 32) + ipv4b := ipv4FromBytes([]byte{198, 186, 190, 179}, 32) + ipv4c := ipv4FromBytes([]byte{151, 101, 124, 84}, 32) + + tree.Add(ipv4a, "tagA", nil) + tree.Add(ipv4b, "tagB", nil) + tree.Add(ipv4c, "tagC", nil) + + tags := make([]GeneratedType, 0) + tags = tree.FindTags(ipv4FromBytes([]byte{98, 139, 183, 24}, 32)) + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "tagA", tags[0]) + + tags = tree.FindTags(ipv4FromBytes([]byte{198, 186, 190, 179}, 32)) + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "tagB", tags[0]) + + tags = tree.FindTags(ipv4FromBytes([]byte{151, 101, 124, 84}, 32)) + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "tagC", tags[0]) +} + +// TestSimpleTree1FilterAppend tests that FindTagsWithFilterAppend appends +func TestSimpleTree1FilterAppend(t *testing.T) { + assert := assert.New(t) + + include := true + filterFunc := func(val GeneratedType) bool { + return include + } + + tree := NewTreeV4() + + ipv4a := ipv4FromBytes([]byte{98, 139, 183, 24}, 32) + ipv4b := ipv4FromBytes([]byte{198, 186, 190, 179}, 32) + ipv4c := ipv4FromBytes([]byte{151, 101, 124, 84}, 32) + + tree.Add(ipv4a, "tagA", nil) + tree.Add(ipv4b, "tagB", nil) + tree.Add(ipv4c, "tagC", nil) + + include = false + tags := make([]GeneratedType, 0) + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{98, 139, 183, 24}, 32), filterFunc) + assert.Equal(0, len(tags)) + + include = true + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{98, 139, 183, 24}, 32), filterFunc) + assert.Equal(1, len(tags)) + assert.Equal("tagA", tags[0]) + + include = false + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{198, 186, 190, 179}, 32), filterFunc) + assert.Equal(1, len(tags)) + + include = true + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{198, 186, 190, 179}, 32), filterFunc) + assert.Equal(2, len(tags)) + assert.Equal("tagA", tags[0]) + assert.Equal("tagB", tags[1]) + + include = false + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(2, len(tags)) + + include = true + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(3, len(tags)) + assert.Equal("tagA", tags[0]) + assert.Equal("tagB", tags[1]) + assert.Equal("tagC", tags[2]) + + include = false + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(3, len(tags)) + + include = true + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(4, len(tags)) + assert.Equal("tagA", tags[0]) + assert.Equal("tagB", tags[1]) + assert.Equal("tagC", tags[2]) + assert.Equal("tagC", tags[2]) +} + +// TestSimpleTree1Filter tests that FindTagsWithFilter +func TestSimpleTree1Filter(t *testing.T) { + assert := assert.New(t) + + include := true + filterFunc := func(val GeneratedType) bool { + return include + } + + tree := NewTreeV4() + + ipv4a := ipv4FromBytes([]byte{98, 139, 183, 24}, 32) + ipv4b := ipv4FromBytes([]byte{198, 186, 190, 179}, 32) + ipv4c := ipv4FromBytes([]byte{151, 101, 124, 84}, 32) + + tree.Add(ipv4a, "tagA", nil) + tree.Add(ipv4b, "tagB", nil) + tree.Add(ipv4c, "tagC", nil) + + include = false + tags := make([]GeneratedType, 0) + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{98, 139, 183, 24}, 32), filterFunc) + assert.Equal(0, len(tags)) + + include = true + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{98, 139, 183, 24}, 32), filterFunc) + assert.Equal(1, len(tags)) + assert.Equal("tagA", tags[0]) + + include = false + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{198, 186, 190, 179}, 32), filterFunc) + assert.Equal(0, len(tags)) + + include = true + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{198, 186, 190, 179}, 32), filterFunc) + assert.Equal(1, len(tags)) + assert.Equal("tagB", tags[0]) + + include = false + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(0, len(tags)) + + include = true + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(1, len(tags)) + assert.Equal("tagC", tags[0]) + + include = false + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(0, len(tags)) + + include = true + tags = tree.FindTagsWithFilter(ipv4FromBytes([]byte{151, 101, 124, 84}, 32), filterFunc) + assert.Equal(1, len(tags)) + assert.Equal("tagC", tags[0]) +} + // Test having a couple of inner nodes func TestSimpleTree2(t *testing.T) { + buf := make([]GeneratedType, 0) + ipA, _, _ := patricia.ParseIPFromString("203.143.220.0/23") ipB, _, _ := patricia.ParseIPFromString("203.143.220.198/32") ipC, _, _ := patricia.ParseIPFromString("203.143.0.0/16") @@ -350,30 +657,67 @@ func TestSimpleTree2(t *testing.T) { tree.Add(*ipD, "D", nil) // find the 4 addresses - found, _, _ := tree.FindDeepestTag(*ipA) + found, _ := tree.FindDeepestTag(*ipA) assert.True(t, found) - found, _, _ = tree.FindDeepestTag(*ipB) + found, _ = tree.FindDeepestTag(*ipB) assert.True(t, found) - found, _, _ = tree.FindDeepestTag(*ipC) + found, _ = tree.FindDeepestTag(*ipC) assert.True(t, found) - found, _, _ = tree.FindDeepestTag(*ipD) + found, _ = tree.FindDeepestTag(*ipD) assert.True(t, found) // delete each one matchFunc := func(a GeneratedType, b GeneratedType) bool { return a == b } - deleteCount, err := tree.Delete(*ipA, matchFunc, "A") - assert.NoError(t, err) + deleteCount := tree.DeleteWithBuffer(buf, *ipA, matchFunc, "A") assert.Equal(t, 1, deleteCount) - deleteCount, err = tree.Delete(*ipB, matchFunc, "B") - assert.NoError(t, err) + deleteCount = tree.DeleteWithBuffer(buf, *ipB, matchFunc, "B") assert.Equal(t, 1, deleteCount) - deleteCount, err = tree.Delete(*ipC, matchFunc, "C") - assert.NoError(t, err) + deleteCount = tree.DeleteWithBuffer(buf, *ipC, matchFunc, "C") assert.Equal(t, 1, deleteCount) - deleteCount, err = tree.Delete(*ipD, matchFunc, "D") - assert.NoError(t, err) + deleteCount = tree.DeleteWithBuffer(buf, *ipD, matchFunc, "D") + assert.Equal(t, 1, deleteCount) + + // should have zero logical nodes except for root + assert.Equal(t, 1, tree.countNodes(1)) +} + +// Test having a couple of inner nodes - with self-allocating Delete method +func TestSimpleTree2WithDelete(t *testing.T) { + ipA, _, _ := patricia.ParseIPFromString("203.143.220.0/23") + ipB, _, _ := patricia.ParseIPFromString("203.143.220.198/32") + ipC, _, _ := patricia.ParseIPFromString("203.143.0.0/16") + ipD, _, _ := patricia.ParseIPFromString("203.143.221.75/32") + + // add the 4 addresses + tree := NewTreeV4() + tree.Add(*ipA, "A", nil) + tree.Add(*ipB, "B", nil) + tree.Add(*ipC, "C", nil) + tree.Add(*ipD, "D", nil) + + // find the 4 addresses + found, _ := tree.FindDeepestTag(*ipA) + assert.True(t, found) + found, _ = tree.FindDeepestTag(*ipB) + assert.True(t, found) + found, _ = tree.FindDeepestTag(*ipC) + assert.True(t, found) + found, _ = tree.FindDeepestTag(*ipD) + assert.True(t, found) + + // delete each one + matchFunc := func(a GeneratedType, b GeneratedType) bool { + return a == b + } + deleteCount := tree.Delete(*ipA, matchFunc, "A") + assert.Equal(t, 1, deleteCount) + deleteCount = tree.Delete(*ipB, matchFunc, "B") + assert.Equal(t, 1, deleteCount) + deleteCount = tree.Delete(*ipC, matchFunc, "C") + assert.Equal(t, 1, deleteCount) + deleteCount = tree.Delete(*ipD, matchFunc, "D") assert.Equal(t, 1, deleteCount) // should have zero logical nodes except for root @@ -381,66 +725,65 @@ func TestSimpleTree2(t *testing.T) { } func TestSimpleTree(t *testing.T) { + tags := make([]GeneratedType, 0) + tree := NewTreeV4() for i := 32; i > 0; i-- { - countIncreased, count, err := tree.Add(ipv4FromBytes([]byte{127, 0, 0, 1}, i), fmt.Sprintf("Tag-%d", i), nil) - assert.NoError(t, err) + countIncreased, count := tree.Add(ipv4FromBytes([]byte{127, 0, 0, 1}, i), fmt.Sprintf("Tag-%d", i), nil) assert.True(t, countIncreased) assert.Equal(t, 1, count) } - tags, err := tree.FindTags(ipv4FromBytes([]byte{127, 0, 0, 1}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{127, 0, 0, 1}, 32)) if assert.Equal(t, 32, len(tags)) { assert.Equal(t, "Tag-32", tags[31].(string)) assert.Equal(t, "Tag-31", tags[30].(string)) } - tags, err = tree.FindTags(ipv4FromBytes([]byte{63, 3, 0, 1}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{63, 3, 0, 1}, 32)) if assert.Equal(t, 1, len(tags)) { assert.Equal(t, "Tag-1", tags[0].(string)) } // find deepest tag: match at lowest level - found, tag, err := tree.FindDeepestTag(ipv4FromBytes([]byte{127, 0, 0, 1}, 32)) + found, tag := tree.FindDeepestTag(ipv4FromBytes([]byte{127, 0, 0, 1}, 32)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-32", tag.(string)) } // find deepest tag: match at top level - found, tag, err = tree.FindDeepestTag(ipv4FromBytes([]byte{63, 5, 4, 3}, 32)) + found, tag = tree.FindDeepestTag(ipv4FromBytes([]byte{63, 5, 4, 3}, 32)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-1", tag.(string)) } // find deepest tag: match at mid level - found, tag, err = tree.FindDeepestTag(ipv4FromBytes([]byte{119, 5, 4, 3}, 32)) + found, tag = tree.FindDeepestTag(ipv4FromBytes([]byte{119, 5, 4, 3}, 32)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-4", tag.(string)) } // find deepest tag: no match - found, tag, err = tree.FindDeepestTag(ipv4FromBytes([]byte{128, 4, 3, 2}, 32)) + found, tag = tree.FindDeepestTag(ipv4FromBytes([]byte{128, 4, 3, 2}, 32)) assert.False(t, found) assert.Nil(t, tag) // Add a couple root tags - countIncreased, count, err := tree.Add(ipv4FromBytes([]byte{127, 0, 0, 1}, 0), "root1", nil) - assert.NoError(t, err) + countIncreased, count := tree.Add(ipv4FromBytes([]byte{127, 0, 0, 1}, 0), "root1", nil) assert.True(t, countIncreased) assert.Equal(t, 1, count) - countIncreased, count, err = tree.Add(patricia.IPv4Address{}, "root2", nil) - assert.NoError(t, err) + countIncreased, count = tree.Add(patricia.IPv4Address{}, "root2", nil) assert.True(t, countIncreased) assert.Equal(t, 2, count) - tags, err = tree.FindTags(patricia.IPv4Address{}) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, patricia.IPv4Address{}) if assert.Equal(t, 2, len(tags)) { assert.Equal(t, "root1", tags[0].(string)) assert.Equal(t, "root2", tags[1].(string)) @@ -468,7 +811,9 @@ func tagArraysEqual(a []GeneratedType, b []string) bool { return true } -func TestTree1FindTags(t *testing.T) { +func TestTree1FindTagsAppend(t *testing.T) { + tags := make([]GeneratedType, 0) + tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -481,27 +826,29 @@ func TestTree1FindTags(t *testing.T) { tree.Add(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), tagC, nil) // three tags in a hierarchy - ask for all but the most specific - tags, err := tree.FindTags(ipv4FromBytes([]byte{128, 142, 133, 1}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 142, 133, 1}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagZ})) // three tags in a hierarchy - ask for an exact match, receive all 3 - tags, err = tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagZ})) // three tags in a hierarchy - get just the first - tags, err = tree.FindTags(ipv4FromBytes([]byte{162, 1, 0, 5}, 30)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{162, 1, 0, 5}, 30)) assert.True(t, tagArraysEqual(tags, []string{tagB, tagZ})) // three tags in hierarchy - get none - tags, err = tree.FindTags(ipv4FromBytes([]byte{1, 0, 0, 0}, 1)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{1, 0, 0, 0}, 1)) assert.True(t, tagArraysEqual(tags, []string{tagZ})) } -func TestTree1FindTagsWithFilter(t *testing.T) { +func TestTree1FindTagsWithFilterAppend(t *testing.T) { + tags := make([]GeneratedType, 0) + tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -518,28 +865,30 @@ func TestTree1FindTagsWithFilter(t *testing.T) { tree.Add(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), tagC, nil) // three tags in a hierarchy - ask for all but the most specific - tags, err := tree.FindTagsWithFilter(ipv4FromBytes([]byte{128, 142, 133, 1}, 32), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{128, 142, 133, 1}, 32), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // three tags in a hierarchy - ask for an exact match, receive all 3 - tags, err = tree.FindTagsWithFilter(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // three tags in a hierarchy - get just the first - tags, err = tree.FindTagsWithFilter(ipv4FromBytes([]byte{162, 1, 0, 5}, 30), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{162, 1, 0, 5}, 30), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagB})) // three tags in hierarchy - get none - tags, err = tree.FindTagsWithFilter(ipv4FromBytes([]byte{1, 0, 0, 0}, 1), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv4FromBytes([]byte{1, 0, 0, 0}, 1), filterFunc) assert.Zero(t, len(tags)) } // Test that all queries get the root nodes func TestRootNode(t *testing.T) { + tags := make([]GeneratedType, 0) + tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -553,31 +902,31 @@ func TestRootNode(t *testing.T) { tree.Add(patricia.IPv4Address{}, tagB, nil) // query the root node with no address - tags, err := tree.FindTags(patricia.IPv4Address{}) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, patricia.IPv4Address{}) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // query a node that doesn't exist - tags, err = tree.FindTags(ipv4FromBytes([]byte{1, 2, 3, 4}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{1, 2, 3, 4}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // create a new /16 node with C & D tree.Add(ipv4FromBytes([]byte{1, 2, 3, 4}, 16), tagC, nil) tree.Add(ipv4FromBytes([]byte{1, 2, 3, 4}, 16), tagD, nil) - tags, err = tree.FindTags(ipv4FromBytes([]byte{1, 2, 3, 4}, 16)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{1, 2, 3, 4}, 16)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagD})) // create a node under the /16 node tree.Add(ipv4FromBytes([]byte{1, 2, 3, 4}, 32), tagZ, nil) - tags, err = tree.FindTags(ipv4FromBytes([]byte{1, 2, 3, 4}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{1, 2, 3, 4}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagD, tagZ})) // check the /24 and make sure we still get the /16 and root - tags, err = tree.FindTags(ipv4FromBytes([]byte{1, 2, 3, 4}, 24)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{1, 2, 3, 4}, 24)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagD})) } @@ -586,83 +935,72 @@ func TestAdd(t *testing.T) { address := ipv4FromBytes([]byte{1, 2, 3, 4}, 32) tree := NewTreeV4() - countIncreased, count, err := tree.Add(address, "hi", nil) - assert.NoError(t, err) + countIncreased, count := tree.Add(address, "hi", nil) assert.True(t, countIncreased) assert.Equal(t, 1, count) - countIncreased, count, err = tree.Add(address, "hi", nil) - assert.NoError(t, err) + countIncreased, count = tree.Add(address, "hi", nil) assert.True(t, countIncreased) assert.Equal(t, 2, count) - countIncreased, count, err = tree.Add(address, "hi", nil) - assert.NoError(t, err) + countIncreased, count = tree.Add(address, "hi", nil) assert.True(t, countIncreased) assert.Equal(t, 3, count) } // Test setting a value to a node, rather than adding to a list func TestSet(t *testing.T) { + buf := make([]GeneratedType, 0) + address := ipv4FromBytes([]byte{1, 2, 3, 4}, 32) tree := NewTreeV4() // add a parent node, just to mix things up - countIncreased, count, err := tree.Set(ipv4FromBytes([]byte{1, 2, 3, 0}, 24), "parent") - assert.NoError(t, err) - assert.True(t, countIncreased) + countIncreased, count := tree.Set(ipv4FromBytes([]byte{1, 2, 3, 0}, 24), "parent") assert.Equal(t, 1, count) - countIncreased, count, err = tree.Set(address, "tagA") - assert.NoError(t, err) + countIncreased, count = tree.Set(address, "tagA") assert.True(t, countIncreased) assert.Equal(t, 1, count) - found, tag, err := tree.FindDeepestTag(address) + found, tag := tree.FindDeepestTag(address) assert.True(t, found) - assert.NoError(t, err) assert.Equal(t, "tagA", tag) - countIncreased, count, err = tree.Set(address, "tagB") + countIncreased, count = tree.Set(address, "tagB") assert.Equal(t, 1, count) assert.False(t, countIncreased) - assert.NoError(t, err) - found, tag, err = tree.FindDeepestTag(address) + found, tag = tree.FindDeepestTag(address) assert.True(t, found) - assert.NoError(t, err) assert.Equal(t, "tagB", tag) - countIncreased, count, err = tree.Set(address, "tagC") + countIncreased, count = tree.Set(address, "tagC") assert.Equal(t, 1, count) assert.False(t, countIncreased) - assert.NoError(t, err) - found, tag, err = tree.FindDeepestTag(address) + found, tag = tree.FindDeepestTag(address) assert.True(t, found) - assert.NoError(t, err) assert.Equal(t, "tagC", tag) - countIncreased, count, err = tree.Set(address, "tagD") + countIncreased, count = tree.Set(address, "tagD") assert.Equal(t, 1, count) assert.False(t, countIncreased) - assert.NoError(t, err) - found, tag, err = tree.FindDeepestTag(address) + found, tag = tree.FindDeepestTag(address) assert.True(t, found) - assert.NoError(t, err) assert.Equal(t, "tagD", tag) // now delete the tag - delCount, err := tree.Delete(address, func(a GeneratedType, b GeneratedType) bool { return true }, "") + delCount := tree.DeleteWithBuffer(buf, address, func(a GeneratedType, b GeneratedType) bool { return true }, "") assert.Equal(t, 1, delCount) - assert.NoError(t, err) // verify it's gone - should get the parent - found, tag, err = tree.FindDeepestTag(address) + found, tag = tree.FindDeepestTag(address) assert.True(t, found) - assert.NoError(t, err) assert.Equal(t, "parent", tag) } func TestDelete1(t *testing.T) { + tags := make([]GeneratedType, 0) + matchFunc := func(tagData GeneratedType, val GeneratedType) bool { return tagData.(string) == val.(string) } @@ -693,57 +1031,57 @@ func TestDelete1(t *testing.T) { // verify status of internal nodes collections assert.Zero(t, len(tree.availableIndexes)) - assert.Equal(t, "tagZ", tree.tagsForNode(1)[0]) - assert.Equal(t, "tagA", tree.tagsForNode(2)[0]) - assert.Equal(t, "tagB", tree.tagsForNode(3)[0]) - assert.Equal(t, "tagC", tree.tagsForNode(4)[0]) + assert.Equal(t, "tagZ", tree.tagsForNode(tags, 1, nil)[0], nil) + tags = tags[:0] + assert.Equal(t, "tagA", tree.tagsForNode(tags, 2, nil)[0], nil) + tags = tags[:0] + assert.Equal(t, "tagB", tree.tagsForNode(tags, 3, nil)[0], nil) + tags = tags[:0] + assert.Equal(t, "tagC", tree.tagsForNode(tags, 4, nil)[0], nil) + tags = tags[:0] // three tags in a hierarchy - ask for an exact match, receive all 3 and the root - tags, err := tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagZ})) // 1. delete a tag that doesn't exist count := 0 - count, err = tree.Delete(ipv4FromBytes([]byte{9, 9, 9, 9}, 32), matchFunc, "bad tag") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(tags, ipv4FromBytes([]byte{9, 9, 9, 9}, 32), matchFunc, "bad tag") assert.Equal(t, 0, count) assert.Equal(t, 4, tree.countNodes(1)) assert.Equal(t, 4, tree.countTags(1)) // 2. delete a tag on an address that exists, but doesn't have the tag - count, err = tree.Delete(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), matchFunc, "bad tag") + count = tree.DeleteWithBuffer(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32), matchFunc, "bad tag") assert.Equal(t, 0, count) - assert.NoError(t, err) // verify - tags, err = tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagZ})) assert.Equal(t, 4, tree.countNodes(1)) assert.Equal(t, 4, tree.countTags(1)) // 3. delete the default/root tag - count, err = tree.Delete(ipv4FromBytes([]byte{0, 0, 0, 0}, 0), matchFunc, "tagZ") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(tags, ipv4FromBytes([]byte{0, 0, 0, 0}, 0), matchFunc, "tagZ") assert.Equal(t, 1, count) assert.Equal(t, 4, tree.countNodes(1)) // doesn't delete anything assert.Equal(t, 3, tree.countTags(1)) assert.Equal(t, 0, len(tree.availableIndexes)) // three tags in a hierarchy - ask for an exact match, receive all 3, not the root, which we deleted - tags, err = tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC})) // 4. delete tagA - count, err = tree.Delete(ipv4FromBytes([]byte{128, 0, 0, 0}, 7), matchFunc, "tagA") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(tags, ipv4FromBytes([]byte{128, 0, 0, 0}, 7), matchFunc, "tagA") assert.Equal(t, 1, count) // verify - tags, err = tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagB, tagC})) assert.Equal(t, 3, tree.countNodes(1)) assert.Equal(t, 2, tree.countTags(1)) @@ -751,13 +1089,12 @@ func TestDelete1(t *testing.T) { assert.Equal(t, uint(2), tree.availableIndexes[0]) // 5. delete tag B - count, err = tree.Delete(ipv4FromBytes([]byte{128, 0, 0, 0}, 2), matchFunc, "tagB") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(tags, ipv4FromBytes([]byte{128, 0, 0, 0}, 2), matchFunc, "tagB") assert.Equal(t, 1, count) // verify - tags, err = tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{tagC})) assert.Equal(t, 2, tree.countNodes(1)) assert.Equal(t, 1, tree.countTags(1)) @@ -773,16 +1110,17 @@ func TestDelete1(t *testing.T) { // this should be recycling tagB assert.Equal(t, 1, len(tree.availableIndexes)) assert.Equal(t, uint(2), tree.availableIndexes[0]) - assert.Equal(t, "tagE", tree.tagsForNode(3)[0]) + + tags = tags[:0] + assert.Equal(t, "tagE", tree.tagsForNode(tags, 3, nil)[0]) // 6. delete tag C - count, err = tree.Delete(ipv4FromBytes([]byte{128, 3, 6, 240}, 32), matchFunc, "tagC") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32), matchFunc, "tagC") assert.Equal(t, 1, count) // verify - tags, err = tree.FindTags(ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv4FromBytes([]byte{128, 3, 6, 240}, 32)) assert.True(t, tagArraysEqual(tags, []string{})) assert.Equal(t, 2, tree.countNodes(1)) assert.Equal(t, 2, tree.countTags(1)) @@ -802,6 +1140,8 @@ func TestTryToBreak(t *testing.T) { } func TestTagsMap(t *testing.T) { + tags := make([]GeneratedType, 0) + tree := NewTreeV4() // insert tags @@ -813,23 +1153,34 @@ func TestTagsMap(t *testing.T) { // verify assert.Equal(t, 3, tree.nodes[1].TagCount) assert.Equal(t, "tagA", tree.firstTagForNode(1)) - assert.Equal(t, 3, len(tree.tagsForNode(1))) - assert.Equal(t, "tagA", tree.tagsForNode(1)[0]) - assert.Equal(t, "tagB", tree.tagsForNode(1)[1]) - assert.Equal(t, "tagC", tree.tagsForNode(1)[2]) + assert.Equal(t, 3, len(tree.tagsForNode(tags, 1, nil))) + tags = tags[:0] + + assert.Equal(t, "tagA", tree.tagsForNode(tags, 1, nil)[0]) + tags = tags[:0] + + assert.Equal(t, "tagB", tree.tagsForNode(tags, 1, nil)[1]) + tags = tags[:0] + + assert.Equal(t, "tagC", tree.tagsForNode(tags, 1, nil)[2]) + tags = tags[:0] // delete tagB matchesFunc := func(payload GeneratedType, val GeneratedType) bool { return payload == val } - deleted, kept := tree.deleteTag(1, "tagB", matchesFunc) + deleted, kept := tree.deleteTag(tags, 1, "tagB", matchesFunc) + assert.Equal(t, 0, len(tags)) // verify assert.Equal(t, 1, deleted) assert.Equal(t, 2, kept) assert.Equal(t, 2, tree.nodes[1].TagCount) - assert.Equal(t, "tagA", tree.tagsForNode(1)[0]) - assert.Equal(t, "tagC", tree.tagsForNode(1)[1]) + assert.Equal(t, "tagA", tree.tagsForNode(tags, 1, nil)[0]) + tags = tags[:0] + + assert.Equal(t, "tagC", tree.tagsForNode(tags, 1, nil)[1]) + tags = tags[:0] } // test duplicate tags with no match func @@ -838,27 +1189,23 @@ func TestDuplicateTagsWithNoMatchFunc(t *testing.T) { tree := NewTreeV4() - wasAdded, count, err := tree.Add(patricia.IPv4Address{}, "FOO", matchFunc) // default + wasAdded, count := tree.Add(patricia.IPv4Address{}, "FOO", matchFunc) // default assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) // add another at previous node - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "FOOBAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "FOOBAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) // add a dupe to the previous node - will be fine since match is nil - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 3, count) - assert.NoError(t, err) } // test duplicate tags with match func that always returns false @@ -869,27 +1216,23 @@ func TestDuplicateTagsWithFalseMatchFunc(t *testing.T) { tree := NewTreeV4() - wasAdded, count, err := tree.Add(patricia.IPv4Address{}, "FOO", matchFunc) // default + wasAdded, count := tree.Add(patricia.IPv4Address{}, "FOO", matchFunc) // default assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) // add another at previous node - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "FOOBAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "FOOBAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) // add a dupe to the previous node - will be fine since match is nil - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 3, count) - assert.NoError(t, err) } // test duplicate tags with match func that does something @@ -900,27 +1243,23 @@ func TestDuplicateTagsWithMatchFunc(t *testing.T) { tree := NewTreeV4() - wasAdded, count, err := tree.Add(patricia.IPv4Address{}, "FOO", matchFunc) // default + wasAdded, count := tree.Add(patricia.IPv4Address{}, "FOO", matchFunc) // default assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) // add another at previous node - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "FOOBAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "FOOBAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) // add a dupe to the previous node - will be fine since match is nil - wasAdded, count, err = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv4FromBytes([]byte{129, 0, 0, 1}, 7), "BAR", matchFunc) assert.False(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) } func payloadToByteArrays(tags []GeneratedType) [][]byte { diff --git a/template/tree_v6_generated.go b/template/tree_v6_generated.go index 602e018..afd3ca8 100644 --- a/template/tree_v6_generated.go +++ b/template/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag GeneratedType, nodeIndex uint, matchFunc MatchesFunc return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []GeneratedType { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []GeneratedType, nodeIndex uint, filterFunc FilterFunc) []GeneratedType { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]GeneratedType, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]GeneratedType, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) GeneratedType { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag GeneratedType, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []GeneratedType, nodeIndex uint, matchTag GeneratedType, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag GeneratedType, matchFunc Mat // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag GeneratedType, matchFunc Mat // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag GeneratedType) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag GeneratedType) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag GeneratedType, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag GeneratedType, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag GeneratedType, matchFunc } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal GeneratedType) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal GeneratedType) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []GeneratedType, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal GeneratedType) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]GeneratedType, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []GeneratedType { ret := make([]GeneratedType, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []GeneratedType, address patricia.IPv6Address) []GeneratedType { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []GeneratedType { + ret := make([]GeneratedType, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]GeneratedType, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []GeneratedType, address patricia.IPv6Address, filterFunc FilterFunc) []GeneratedType { var matchCount uint root := &t.nodes[1] - ret := make([]GeneratedType, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]GeneratedType, error) } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]GeneratedType, error) // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, GeneratedType, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, GeneratedType) { root := &t.nodes[1] var found bool var ret GeneratedType @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, GeneratedTy if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, GeneratedTy // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, GeneratedTy if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, GeneratedTy } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []GeneratedType, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []GeneratedType) { + ret := make([]GeneratedType, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []GeneratedType, address patricia.IPv6Address) (bool, []GeneratedType) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []Generate if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []Generate // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []Generate if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/template/tree_v6_manual.go b/template/tree_v6_manual.go index bc1d627..26688b3 100644 --- a/template/tree_v6_manual.go +++ b/template/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]GeneratedType, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/template/tree_v6_test.go b/template/tree_v6_test.go index 49cd151..2458855 100644 --- a/template/tree_v6_test.go +++ b/template/tree_v6_test.go @@ -35,10 +35,12 @@ func BenchmarkFindTagsV6(b *testing.B) { tree.Add(ipv6FromString("2001:db8:0:0:0:5:2:1/128", 16), tagB, nil) // 160 -> 128 tree.Add(ipv6FromString("2001:db7:0:0:0:0:2:1/128", 77), tagC, nil) + buf := make([]GeneratedType, 0) address := ipv6FromString("2001:db7:0:0:0:0:2:1/128", 32) b.ResetTimer() for n := 0; n < b.N; n++ { - tree.FindTags(address) + tree.FindTagsAppend(buf, address) + buf = buf[:0] } } @@ -55,84 +57,84 @@ func BenchmarkFindDeepestTagV6(b *testing.B) { } func TestSimpleTreeV6(t *testing.T) { + tags := make([]GeneratedType, 0) + tree := NewTreeV6() for i := 128; i > 0; i-- { - countIncreased, count, err := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", i), fmt.Sprintf("Tag-%d", i), nil) - assert.NoError(t, err) + countIncreased, count := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", i), fmt.Sprintf("Tag-%d", i), nil) assert.True(t, countIncreased) assert.Equal(t, 1, count) } - tags, err := tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) if assert.Equal(t, 128, len(tags)) { assert.Equal(t, "Tag-128", tags[127].(string)) assert.Equal(t, "Tag-32", tags[31].(string)) } - tags, err = tree.FindTags(ipv6FromString("4001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("4001:db8:0:0:0:0:2:1/128", 128)) if assert.Equal(t, 1, len(tags)) { assert.Equal(t, "Tag-1", tags[0]) } // find deepest tag: match at lowest level - found, tag, err := tree.FindDeepestTag(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) + found, tag := tree.FindDeepestTag(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-128", tag) } // find deepest tag: match at top level - found, tag, err = tree.FindDeepestTag(ipv6FromString("7001:db8:0:0:0:0:2:1/128", 128)) + found, tag = tree.FindDeepestTag(ipv6FromString("7001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-1", tag.(string)) } - found, tag, err = tree.FindDeepestTag(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 1)) + found, tag = tree.FindDeepestTag(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 1)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-1", tag.(string)) } // find deepest tag: match at mid level - found, tag, err = tree.FindDeepestTag(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 32)) + found, tag = tree.FindDeepestTag(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 32)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-32", tag.(string)) } - found, tag, err = tree.FindDeepestTag(ipv6FromString("2001:db8:FFFF:0:0:0:2:1/128", 128)) + found, tag = tree.FindDeepestTag(ipv6FromString("2001:db8:FFFF:0:0:0:2:1/128", 128)) assert.True(t, found) if assert.NotNil(t, tag) { assert.Equal(t, "Tag-32", tag.(string)) } // find deepest tag: no match - found, tag, err = tree.FindDeepestTag(ipv6FromString("F001:db8:1:0:0:0:2:1/128", 32)) + found, tag = tree.FindDeepestTag(ipv6FromString("F001:db8:1:0:0:0:2:1/128", 32)) assert.False(t, found) assert.Nil(t, tag) // Add a couple root tags - countIncreased, count, err := tree.Add(ipv6FromString("2001:db8:1:0:0:0:2:1/128", 0), "root1", nil) - assert.NoError(t, err) + countIncreased, count := tree.Add(ipv6FromString("2001:db8:1:0:0:0:2:1/128", 0), "root1", nil) assert.True(t, countIncreased) assert.Equal(t, 1, count) - countIncreased, count, err = tree.Add(patricia.IPv6Address{}, "root2", nil) - assert.NoError(t, err) + countIncreased, count = tree.Add(patricia.IPv6Address{}, "root2", nil) assert.True(t, countIncreased) assert.Equal(t, 2, count) - tags, err = tree.FindTags(patricia.IPv6Address{}) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, patricia.IPv6Address{}) if assert.Equal(t, 2, len(tags)) { assert.Equal(t, "root1", tags[0].(string)) assert.Equal(t, "root2", tags[1].(string)) } - } func TestTree1V6(t *testing.T) { + tags := make([]GeneratedType, 0) + tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -145,33 +147,35 @@ func TestTree1V6(t *testing.T) { tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), tagC, nil) // three tags in a hierarchy - ask for all but the most specific - tags, err := tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:0/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:0/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagZ})) - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 127)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 127)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagZ})) // three tags in a hierarchy - ask for an exact match, receive all 3 - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagZ})) // three tags in a hierarchy - get just the first - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:1:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:1:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagB, tagZ})) // three tags in hierarchy - get none - tags, err = tree.FindTags(ipv6FromString("8001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("8001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagZ})) - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 66)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 66)) assert.True(t, tagArraysEqual(tags, []string{tagZ})) } func TestTree1V6WithFilter(t *testing.T) { + tags := make([]GeneratedType, 0) + tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -188,34 +192,36 @@ func TestTree1V6WithFilter(t *testing.T) { tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), tagC, nil) // three tags in a hierarchy - ask for all but the most specific - tags, err := tree.FindTagsWithFilter(ipv6FromString("2001:db8:0:0:0:0:2:0/128", 128), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:0/128", 128), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) - tags, err = tree.FindTagsWithFilter(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 127), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 127), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // three tags in a hierarchy - ask for an exact match, receive all 3 - tags, err = tree.FindTagsWithFilter(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // three tags in a hierarchy - get just the first - tags, err = tree.FindTagsWithFilter(ipv6FromString("2001:db8:0:0:0:1:2:1/128", 128), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv6FromString("2001:db8:0:0:0:1:2:1/128", 128), filterFunc) assert.True(t, tagArraysEqual(tags, []string{tagB})) // three tags in hierarchy - get none - tags, err = tree.FindTagsWithFilter(ipv6FromString("8001:db8:0:0:0:0:2:1/128", 128), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv6FromString("8001:db8:0:0:0:0:2:1/128", 128), filterFunc) assert.Zero(t, len(tags)) - tags, err = tree.FindTagsWithFilter(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 66), filterFunc) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsWithFilterAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 66), filterFunc) assert.Zero(t, len(tags)) } // Test that all queries get the root nodes func TestRootNodeV6(t *testing.T) { + tags := make([]GeneratedType, 0) + tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -229,39 +235,42 @@ func TestRootNodeV6(t *testing.T) { tree.Add(patricia.IPv6Address{}, tagB, nil) // query the root node with no address - tags, err := tree.FindTags(patricia.IPv6Address{}) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, patricia.IPv6Address{}) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // query a node that doesn't exist - tags, err = tree.FindTags(ipv6FromString("FFFF:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("FFFF:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB})) // create a new /65 node with C & D tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 65), tagC, nil) tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 65), tagD, nil) - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagD})) // create a node under the /65 node tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), tagZ, nil) - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagD, tagZ})) // check the /77 and make sure we still get the /65 and root - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 77)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 77)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagD})) } func TestDelete1V6(t *testing.T) { + tags := make([]GeneratedType, 0) + matchFunc := func(tagData GeneratedType, val GeneratedType) bool { return tagData.(string) == val.(string) } + buf := make([]GeneratedType, 0, 10) tagA := "tagA" tagB := "tagB" tagC := "tagC" @@ -279,74 +288,68 @@ func TestDelete1V6(t *testing.T) { assert.Equal(t, 4, tree.countNodes(1)) // three tags in a hierarchy - ask for an exact match, receive all 3 and the root - tags, err := tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagZ})) // 1. delete a tag that doesn't exist count := 0 - count, err = tree.Delete(ipv6FromString("F001:db8:0:0:0:0:2:1/128", 128), matchFunc, "bad tag") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(buf, ipv6FromString("F001:db8:0:0:0:0:2:1/128", 128), matchFunc, "bad tag") assert.Equal(t, 0, count) assert.Equal(t, 4, tree.countTags(1)) assert.Equal(t, 4, tree.countNodes(1)) // 2. delete a tag on an address that exists, but doesn't have the tag - count, err = tree.Delete(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 67), matchFunc, "bad tag") + count = tree.DeleteWithBuffer(buf, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 67), matchFunc, "bad tag") assert.Equal(t, 0, count) - assert.NoError(t, err) // verify - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC, tagZ})) assert.Equal(t, 4, tree.countNodes(1)) assert.Equal(t, 4, tree.countTags(1)) // 3. delete the default/root tag - count, err = tree.Delete(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 0), matchFunc, "tagZ") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(buf, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 0), matchFunc, "tagZ") assert.Equal(t, 1, count) assert.Equal(t, 4, tree.countNodes(1)) // doesn't delete anything assert.Equal(t, 3, tree.countTags(1)) // three tags in a hierarchy - ask for an exact match, receive all 3, not the root, which we deleted - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagA, tagB, tagC})) // 4. delete tagA - count, err = tree.Delete(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 67), matchFunc, "tagA") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(buf, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 67), matchFunc, "tagA") assert.Equal(t, 1, count) // verify - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagB, tagC})) assert.Equal(t, 3, tree.countNodes(1)) assert.Equal(t, 2, tree.countTags(1)) // 5. delete tag B - count, err = tree.Delete(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 2), matchFunc, "tagB") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(buf, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 2), matchFunc, "tagB") assert.Equal(t, 1, count) // verify - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{tagC})) assert.Equal(t, 2, tree.countNodes(1)) assert.Equal(t, 1, tree.countTags(1)) // 6. delete tag C - count, err = tree.Delete(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), matchFunc, "tagC") - assert.NoError(t, err) + count = tree.DeleteWithBuffer(buf, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), matchFunc, "tagC") assert.Equal(t, 1, count) // verify - tags, err = tree.FindTags(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) - assert.NoError(t, err) + tags = tags[:0] + tags = tree.FindTagsAppend(tags, ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128)) assert.True(t, tagArraysEqual(tags, []string{})) assert.Equal(t, 1, tree.countNodes(1)) assert.Equal(t, 0, tree.countTags(1)) @@ -358,27 +361,23 @@ func TestDuplicateTagsWithNoMatchFuncV6(t *testing.T) { tree := NewTreeV6() - wasAdded, count, err := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), "FOO", matchFunc) + wasAdded, count := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), "FOO", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) // add another at previous node - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "FOOBAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "FOOBAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) // add a dupe to the previous node - will be fine since match is nil - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 3, count) - assert.NoError(t, err) } // test duplicate tags with match func that always returns false @@ -389,27 +388,23 @@ func TestDuplicateTagsWithFalseMatchFuncV6(t *testing.T) { tree := NewTreeV6() - wasAdded, count, err := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), "FOO", matchFunc) + wasAdded, count := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), "FOO", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) // add another at previous node - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "FOOBAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "FOOBAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) // add a dupe to the previous node - will be fine since match is nil - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 3, count) - assert.NoError(t, err) } // test duplicate tags with match func that does something @@ -420,25 +415,21 @@ func TestDuplicateTagsWithMatchFuncV6(t *testing.T) { tree := NewTreeV6() - wasAdded, count, err := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), "FOO", matchFunc) + wasAdded, count := tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:1/128", 128), "FOO", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 1, count) - assert.NoError(t, err) // add another at previous node - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "FOOBAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "FOOBAR", matchFunc) assert.True(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) // add a dupe to the previous node - will be fine since match is nil - wasAdded, count, err = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) + wasAdded, count = tree.Add(ipv6FromString("2001:db8:0:0:0:0:2:2/128", 128), "BAR", matchFunc) assert.False(t, wasAdded) assert.Equal(t, 2, count) - assert.NoError(t, err) } diff --git a/uint16_tree/tree_v4.go b/uint16_tree/tree_v4.go index a60d4dc..8ef258d 100644 --- a/uint16_tree/tree_v4.go +++ b/uint16_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag uint16, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []uint16 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []uint16, nodeIndex uint, filterFunc FilterFunc) []uint16 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint16, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint16, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) uint16 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint16, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []uint16, nodeIndex uint, matchTag uint16, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint16, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint16, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag uint16) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag uint16) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag uint16, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag uint16, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint16, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint16) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint16) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []uint16, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint16) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]uint16, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []uint16 { ret := make([]uint16, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []uint16, address patricia.IPv4Address) []uint16 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []uint16 { + ret := make([]uint16, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint16, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []uint16, address patricia.IPv4Address, filterFunc FilterFunc) []uint16 { var matchCount uint root := &t.nodes[1] - ret := make([]uint16, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint16, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint16, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint16, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint16) { root := &t.nodes[1] var found bool var ret uint16 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint16, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint16, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint16, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint16, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint16, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint16) { + ret := make([]uint16, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []uint16, address patricia.IPv4Address) (bool, []uint16) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint16, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint16, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint16, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint16_tree/tree_v4_manual.go b/uint16_tree/tree_v4_manual.go index 1d8927a..bae1cce 100644 --- a/uint16_tree/tree_v4_manual.go +++ b/uint16_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]uint16, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint16_tree/tree_v6_generated.go b/uint16_tree/tree_v6_generated.go index db87aa8..c1c2757 100644 --- a/uint16_tree/tree_v6_generated.go +++ b/uint16_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag uint16, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []uint16 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []uint16, nodeIndex uint, filterFunc FilterFunc) []uint16 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint16, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint16, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) uint16 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint16, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []uint16, nodeIndex uint, matchTag uint16, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint16, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint16, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag uint16) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag uint16) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag uint16, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag uint16, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint16, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint16) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint16) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []uint16, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint16) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]uint16, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []uint16 { ret := make([]uint16, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []uint16, address patricia.IPv6Address) []uint16 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []uint16 { + ret := make([]uint16, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint16, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []uint16, address patricia.IPv6Address, filterFunc FilterFunc) []uint16 { var matchCount uint root := &t.nodes[1] - ret := make([]uint16, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint16, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint16, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint16, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint16) { root := &t.nodes[1] var found bool var ret uint16 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint16, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint16, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint16, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint16, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint16, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint16) { + ret := make([]uint16, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []uint16, address patricia.IPv6Address) (bool, []uint16) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint16, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint16, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint16, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint16_tree/tree_v6_manual.go b/uint16_tree/tree_v6_manual.go index 8dc9a66..16b5d4b 100644 --- a/uint16_tree/tree_v6_manual.go +++ b/uint16_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]uint16, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint32_tree/tree_v4.go b/uint32_tree/tree_v4.go index 3de8bf6..b39df36 100644 --- a/uint32_tree/tree_v4.go +++ b/uint32_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag uint32, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []uint32 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []uint32, nodeIndex uint, filterFunc FilterFunc) []uint32 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint32, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint32, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) uint32 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint32, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []uint32, nodeIndex uint, matchTag uint32, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint32, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint32, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag uint32) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag uint32) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag uint32, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag uint32, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint32, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint32) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint32) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []uint32, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint32) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]uint32, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []uint32 { ret := make([]uint32, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []uint32, address patricia.IPv4Address) []uint32 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []uint32 { + ret := make([]uint32, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint32, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []uint32, address patricia.IPv4Address, filterFunc FilterFunc) []uint32 { var matchCount uint root := &t.nodes[1] - ret := make([]uint32, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint32, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint32, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint32, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint32) { root := &t.nodes[1] var found bool var ret uint32 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint32, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint32, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint32, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint32, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint32, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint32) { + ret := make([]uint32, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []uint32, address patricia.IPv4Address) (bool, []uint32) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint32, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint32, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint32, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint32_tree/tree_v4_manual.go b/uint32_tree/tree_v4_manual.go index 1d78f1d..d67cb9b 100644 --- a/uint32_tree/tree_v4_manual.go +++ b/uint32_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]uint32, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint32_tree/tree_v6_generated.go b/uint32_tree/tree_v6_generated.go index 871992e..7bb8a30 100644 --- a/uint32_tree/tree_v6_generated.go +++ b/uint32_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag uint32, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []uint32 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []uint32, nodeIndex uint, filterFunc FilterFunc) []uint32 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint32, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint32, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) uint32 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint32, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []uint32, nodeIndex uint, matchTag uint32, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint32, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint32, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag uint32) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag uint32) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag uint32, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag uint32, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint32, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint32) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint32) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []uint32, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint32) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]uint32, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []uint32 { ret := make([]uint32, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []uint32, address patricia.IPv6Address) []uint32 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []uint32 { + ret := make([]uint32, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint32, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []uint32, address patricia.IPv6Address, filterFunc FilterFunc) []uint32 { var matchCount uint root := &t.nodes[1] - ret := make([]uint32, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint32, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint32, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint32, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint32) { root := &t.nodes[1] var found bool var ret uint32 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint32, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint32, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint32, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint32, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint32, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint32) { + ret := make([]uint32, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []uint32, address patricia.IPv6Address) (bool, []uint32) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint32, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint32, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint32, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint32_tree/tree_v6_manual.go b/uint32_tree/tree_v6_manual.go index df2ca7f..76cb4fe 100644 --- a/uint32_tree/tree_v6_manual.go +++ b/uint32_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]uint32, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint64_tree/tree_v4.go b/uint64_tree/tree_v4.go index adf8639..eaa74c3 100644 --- a/uint64_tree/tree_v4.go +++ b/uint64_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag uint64, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []uint64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []uint64, nodeIndex uint, filterFunc FilterFunc) []uint64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) uint64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []uint64, nodeIndex uint, matchTag uint64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint64, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint64, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag uint64) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag uint64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag uint64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag uint64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint64, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []uint64, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]uint64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []uint64 { ret := make([]uint64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []uint64, address patricia.IPv4Address) []uint64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []uint64 { + ret := make([]uint64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []uint64, address patricia.IPv4Address, filterFunc FilterFunc) []uint64 { var matchCount uint root := &t.nodes[1] - ret := make([]uint64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint64, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint64) { root := &t.nodes[1] var found bool var ret uint64 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint64, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint64, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint64, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint64, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint64) { + ret := make([]uint64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []uint64, address patricia.IPv4Address) (bool, []uint64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint64, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint64, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint64, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint64_tree/tree_v4_manual.go b/uint64_tree/tree_v4_manual.go index e3b4a74..ff969f2 100644 --- a/uint64_tree/tree_v4_manual.go +++ b/uint64_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]uint64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint64_tree/tree_v6_generated.go b/uint64_tree/tree_v6_generated.go index 6192b1b..ad7e9da 100644 --- a/uint64_tree/tree_v6_generated.go +++ b/uint64_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag uint64, nodeIndex uint, matchFunc MatchesFunc, repla return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []uint64 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []uint64, nodeIndex uint, filterFunc FilterFunc) []uint64 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint64, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint64, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) uint64 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint64, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []uint64, nodeIndex uint, matchTag uint64, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint64, matchFunc MatchesFun // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint64, matchFunc MatchesFun // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag uint64) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag uint64) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag uint64, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag uint64, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint64, matchFunc Matches } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint64) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint64) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []uint64, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint64) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]uint64, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []uint64 { ret := make([]uint64, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []uint64, address patricia.IPv6Address) []uint64 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []uint64 { + ret := make([]uint64, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint64, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []uint64, address patricia.IPv6Address, filterFunc FilterFunc) []uint64 { var matchCount uint root := &t.nodes[1] - ret := make([]uint64, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint64, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint64, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint64, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint64) { root := &t.nodes[1] var found bool var ret uint64 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint64, err if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint64, err // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint64, err if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint64, err } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint64, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint64) { + ret := make([]uint64, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []uint64, address patricia.IPv6Address) (bool, []uint64) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint64, if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint64, // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint64, if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint64_tree/tree_v6_manual.go b/uint64_tree/tree_v6_manual.go index a8b29af..3724d3e 100644 --- a/uint64_tree/tree_v6_manual.go +++ b/uint64_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]uint64, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint8_tree/tree_v4.go b/uint8_tree/tree_v4.go index 73e5c30..9399d43 100644 --- a/uint8_tree/tree_v4.go +++ b/uint8_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag uint8, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []uint8 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []uint8, nodeIndex uint, filterFunc FilterFunc) []uint8 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint8, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint8, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) uint8 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint8, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []uint8, nodeIndex uint, matchTag uint8, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint8, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint8, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag uint8) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag uint8) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag uint8, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag uint8, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint8, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint8) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint8) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []uint8, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint8) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]uint8, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []uint8 { ret := make([]uint8, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []uint8, address patricia.IPv4Address) []uint8 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []uint8 { + ret := make([]uint8, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint8, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []uint8, address patricia.IPv4Address, filterFunc FilterFunc) []uint8 { var matchCount uint root := &t.nodes[1] - ret := make([]uint8, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint8, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint8, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint8, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint8) { root := &t.nodes[1] var found bool var ret uint8 @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint8, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint8, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint8, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint8, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint8, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint8) { + ret := make([]uint8, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []uint8, address patricia.IPv4Address) (bool, []uint8) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint8, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint8, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint8, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint8_tree/tree_v4_manual.go b/uint8_tree/tree_v4_manual.go index 9dcc911..0251084 100644 --- a/uint8_tree/tree_v4_manual.go +++ b/uint8_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]uint8, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint8_tree/tree_v6_generated.go b/uint8_tree/tree_v6_generated.go index dc00f89..87a2fc7 100644 --- a/uint8_tree/tree_v6_generated.go +++ b/uint8_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag uint8, nodeIndex uint, matchFunc MatchesFunc, replac return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []uint8 { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []uint8, nodeIndex uint, filterFunc FilterFunc) []uint8 { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint8, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint8, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) uint8 { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint8, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []uint8, nodeIndex uint, matchTag uint8, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint8, matchFunc MatchesFunc // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint8, matchFunc MatchesFunc // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag uint8) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag uint8) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag uint8, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag uint8, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint8, matchFunc MatchesF } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint8) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint8) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []uint8, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint8) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]uint8, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []uint8 { ret := make([]uint8, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []uint8, address patricia.IPv6Address) []uint8 { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []uint8 { + ret := make([]uint8, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint8, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []uint8, address patricia.IPv6Address, filterFunc FilterFunc) []uint8 { var matchCount uint root := &t.nodes[1] - ret := make([]uint8, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint8, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint8, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint8, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint8) { root := &t.nodes[1] var found bool var ret uint8 @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint8, erro if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint8, erro // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint8, erro if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint8, erro } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint8, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint8) { + ret := make([]uint8, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []uint8, address patricia.IPv6Address) (bool, []uint8) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint8, e if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint8, e // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint8, e if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint8_tree/tree_v6_manual.go b/uint8_tree/tree_v6_manual.go index e228cbb..ca42b3c 100644 --- a/uint8_tree/tree_v6_manual.go +++ b/uint8_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]uint8, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint_tree/tree_v4.go b/uint_tree/tree_v4.go index 27b30c2..d2fa247 100644 --- a/uint_tree/tree_v4.go +++ b/uint_tree/tree_v4.go @@ -79,18 +79,22 @@ func (t *TreeV4) addTag(tag uint, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV4) tagsForNode(nodeIndex uint) []uint { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV4) tagsForNode(ret []uint, nodeIndex uint, filterFunc FilterFunc) []uint { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV4) firstTagForNode(nodeIndex uint) uint { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV4) deleteTag(buf []uint, nodeIndex uint, matchTag uint, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV4) deleteTag(nodeIndex uint, matchTag uint, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Set(address patricia.IPv4Address, tag uint) (bool, int, error) { +func (t *TreeV4) Set(address patricia.IPv4Address, tag uint) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV4) Add(address patricia.IPv4Address, tag uint, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV4) Add(address patricia.IPv4Address, tag uint, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV4, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV4) add(address patricia.IPv4Address, tag uint, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV4) DeleteWithBuffer(buf []uint, address patricia.IPv4Address, matchFunc MatchesFunc, matchVal uint) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV4) Delete(address patricia.IPv4Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) ([]uint, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTagsWithFilter(address patricia.IPv4Address, filterFunc FilterFunc) []uint { ret := make([]uint, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV4) FindTagsAppend(ret []uint, address patricia.IPv4Address) []uint { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindTags(address patricia.IPv4Address) []uint { + ret := make([]uint, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV4) FindTagsWithFilterAppend(ret []uint, address patricia.IPv4Address, filterFunc FilterFunc) []uint { var matchCount uint root := &t.nodes[1] - ret := make([]uint, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV4) FindTags(address patricia.IPv4Address) ([]uint, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint, error) { +func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint) { root := &t.nodes[1] var found bool var ret uint @@ -596,7 +559,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV4) FindDeepestTag(address patricia.IPv4Address) (bool, uint, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint) { + ret := make([]uint, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV4) FindDeepestTagsAppend(ret []uint, address patricia.IPv4Address) (bool, []uint) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV4) FindDeepestTags(address patricia.IPv4Address) (bool, []uint, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint_tree/tree_v4_manual.go b/uint_tree/tree_v4_manual.go index 182c301..aff0bbf 100644 --- a/uint_tree/tree_v4_manual.go +++ b/uint_tree/tree_v4_manual.go @@ -23,7 +23,9 @@ func (t *TreeV4) newNode(address patricia.IPv4Address, prefixLength uint) uint { } func (t *TreeV4) print() { + buf := make([]uint, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefix), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } } diff --git a/uint_tree/tree_v6_generated.go b/uint_tree/tree_v6_generated.go index f4c1222..900f880 100644 --- a/uint_tree/tree_v6_generated.go +++ b/uint_tree/tree_v6_generated.go @@ -79,18 +79,22 @@ func (t *TreeV6) addTag(tag uint, nodeIndex uint, matchFunc MatchesFunc, replace return ret } -func (t *TreeV6) tagsForNode(nodeIndex uint) []uint { +// return the tags at the input node index - appending to the input slice if they pass the optional filter func +// - ret is only appended to +func (t *TreeV6) tagsForNode(ret []uint, nodeIndex uint, filterFunc FilterFunc) []uint { if nodeIndex == 0 { // useful for base cases where we haven't found anything - return make([]uint, 0) + return ret } // TODO: clean up the typing in here, between uint, uint64 tagCount := t.nodes[nodeIndex].TagCount - ret := make([]uint, tagCount) key := uint64(nodeIndex) << 32 for i := 0; i < tagCount; i++ { - ret[i] = t.tags[key+uint64(i)] + tag := t.tags[key+uint64(i)] + if filterFunc == nil || filterFunc(tag) { + ret = append(ret, tag) + } } return ret } @@ -112,13 +116,17 @@ func (t *TreeV6) firstTagForNode(nodeIndex uint) uint { } // delete tags at the input node, returning how many were deleted, and how many are left -func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint, matchFunc MatchesFunc) (int, int) { - // TODO: this could be done much more efficiently - +// - uses input slice to reduce allocations +func (t *TreeV6) deleteTag(buf []uint, nodeIndex uint, matchTag uint, matchFunc MatchesFunc) (int, int) { // get tags - tags := t.tagsForNode(nodeIndex) + buf = buf[:0] + buf = t.tagsForNode(buf, nodeIndex, nil) + if len(buf) == 0 { + return 0, 0 + } // delete tags + // TODO: this could be done smarter - delete in place? for i := 0; i < t.nodes[nodeIndex].TagCount; i++ { delete(t.tags, (uint64(nodeIndex)<<32)+uint64(i)) } @@ -127,7 +135,7 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint, matchFunc MatchesFunc) // put them back deleteCount := 0 keepCount := 0 - for _, tag := range tags { + for _, tag := range buf { if matchFunc(tag, matchTag) { deleteCount++ } else { @@ -141,21 +149,21 @@ func (t *TreeV6) deleteTag(nodeIndex uint, matchTag uint, matchFunc MatchesFunc) // Set the single value for a node - overwrites what's there // Returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Set(address patricia.IPv6Address, tag uint) (bool, int, error) { +func (t *TreeV6) Set(address patricia.IPv6Address, tag uint) (bool, int) { return t.add(address, tag, nil, true) } // Add adds a tag to the tree // - if matchFunc is non-nil, it will be used to ensure uniqueness at this node // - returns whether the tag count at this address was increased, and how many tags at this address -func (t *TreeV6) Add(address patricia.IPv6Address, tag uint, matchFunc MatchesFunc) (bool, int, error) { +func (t *TreeV6) Add(address patricia.IPv6Address, tag uint, matchFunc MatchesFunc) (bool, int) { return t.add(address, tag, matchFunc, false) } // add a tag to the tree, optionally as the single value // - overwrites the first value in the list if 'replaceFirst' is true // - returns whether the tag count was increased, and the number of tags at this address -func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFunc, replaceFirst bool) (bool, int, error) { +func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFunc, replaceFirst bool) (bool, int) { // make sure we have more than enough capacity before we start adding to the tree, which invalidates pointers into the array if (len(t.availableIndexes) + cap(t.nodes)) < (len(t.nodes) + 10) { temp := make([]treeNodeV6, len(t.nodes), (cap(t.nodes)+1)*2) @@ -168,7 +176,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu // handle root tags if address.Length == 0 { countIncreased := t.addTag(tag, 1, matchFunc, replaceFirst) - return countIncreased, t.nodes[1].TagCount, nil + return countIncreased, t.nodes[1].TagCount } // root node doesn't have any prefix, so find the starting point @@ -179,7 +187,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Left } else { @@ -187,7 +195,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) root.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } nodeIndex = root.Right } @@ -212,7 +220,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu if matchCount == node.prefixLength { // the whole prefix matched - we're done! countIncreased := t.addTag(tag, nodeIndex, matchFunc, replaceFirst) - return countIncreased, t.nodes[nodeIndex].TagCount, nil + return countIncreased, t.nodes[nodeIndex].TagCount } // the input address is shorter than the match found - need to create a new, intermediate parent @@ -240,7 +248,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu } parent.Right = newNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } if matchCount == node.prefixLength { @@ -255,7 +263,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Left = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the left - traverse it @@ -270,7 +278,7 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu newNodeIndex := t.newNode(address, address.Length) countIncreased := t.addTag(tag, newNodeIndex, matchFunc, replaceFirst) node.Right = newNodeIndex - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } // there's a node to the right - traverse it @@ -308,12 +316,19 @@ func (t *TreeV6) add(address patricia.IPv6Address, tag uint, matchFunc MatchesFu } parent.Right = newCommonParentNodeIndex } - return countIncreased, t.nodes[newNodeIndex].TagCount, nil + return countIncreased, t.nodes[newNodeIndex].TagCount } } // Delete a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed -func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint) (int, error) { +// - use DeleteWithBuffer if you can reuse slices, to cut down on allocations +func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint) int { + return t.DeleteWithBuffer(nil, address, matchFunc, matchVal) +} + +// DeleteWithBuffer a tag from the tree if it matches matchVal, as determined by matchFunc. Returns how many tags are removed +// - uses input slice to reduce allocations +func (t *TreeV6) DeleteWithBuffer(buf []uint, address patricia.IPv6Address, matchFunc MatchesFunc, matchVal uint) int { // traverse the tree, finding the node and its parent root := &t.nodes[1] var parentIndex uint @@ -339,14 +354,14 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat // traverse the tree for { if nodeIndex == 0 { - return 0, nil + return 0 } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return 0, nil + return 0 } if matchCount == address.Length { @@ -370,25 +385,25 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat if targetNode == nil || targetNode.TagCount == 0 { // no tags found - return 0, nil + return 0 } // delete matching tags - deleteCount, remainingTagCount := t.deleteTag(targetNodeIndex, matchVal, matchFunc) + deleteCount, remainingTagCount := t.deleteTag(buf, targetNodeIndex, matchVal, matchFunc) if remainingTagCount > 0 { // target node still has tags - we're not deleting it - return deleteCount, nil + return deleteCount } if targetNodeIndex == 1 { // can't delete the root node - return deleteCount, nil + return deleteCount } // compact the tree, if possible if targetNode.Left != 0 && targetNode.Right != 0 { // target has two children - nothing we can do - not deleting the node - return deleteCount, nil + return deleteCount } else if targetNode.Left != 0 { // target node only has only left child if parent.Left == targetNodeIndex { @@ -453,91 +468,41 @@ func (t *TreeV6) Delete(address patricia.IPv6Address, matchFunc MatchesFunc, mat targetNode.Left = 0 targetNode.Right = 0 t.availableIndexes = append(t.availableIndexes, targetNodeIndex) - return deleteCount, nil + return deleteCount } // FindTagsWithFilter finds all matching tags that passes the filter function -func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) ([]uint, error) { - root := &t.nodes[1] - if filterFunc == nil { - return t.FindTags(address) - } - - var matchCount uint +// - use FindTagsWithFilterAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTagsWithFilter(address patricia.IPv6Address, filterFunc FilterFunc) []uint { ret := make([]uint, 0) + return t.FindTagsWithFilterAppend(ret, address, filterFunc) +} - if root.TagCount > 0 { - for _, tag := range t.tagsForNode(1) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if address.Length == 0 { - // caller just looking for root tags - return ret, nil - } - - var nodeIndex uint - if !address.IsLeftBitSet() { - nodeIndex = root.Left - } else { - nodeIndex = root.Right - } - - // traverse the tree - count := 0 - for { - count++ - if nodeIndex == 0 { - return ret, nil - } - node := &t.nodes[nodeIndex] - - matchCount = node.MatchCount(address) - if matchCount < node.prefixLength { - // didn't match the entire node - we're done - return ret, nil - } - - // matched the full node - get its tags, then chop off the bits we've already matched and continue - if node.TagCount > 0 { - for _, tag := range t.tagsForNode(nodeIndex) { - if filterFunc(tag) { - ret = append(ret, tag) - } - } - } - - if matchCount == address.Length { - // exact match - we're done - return ret, nil - } +// FindTagsAppend finds all matching tags for given address and appends them to ret +func (t *TreeV6) FindTagsAppend(ret []uint, address patricia.IPv6Address) []uint { + return t.FindTagsWithFilterAppend(ret, address, nil) +} - // there's still more address - keep traversing - address.ShiftLeft(matchCount) - if !address.IsLeftBitSet() { - nodeIndex = node.Left - } else { - nodeIndex = node.Right - } - } +// FindTags finds all matching tags for given address +// - use FindTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindTags(address patricia.IPv6Address) []uint { + ret := make([]uint, 0) + return t.FindTagsAppend(ret, address) } -// FindTags finds all matching tags that passes the filter function -func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint, error) { +// FindTagsWithFilterAppend finds all matching tags that passes the filter function +// - results are appended to the input slice +func (t *TreeV6) FindTagsWithFilterAppend(ret []uint, address patricia.IPv6Address, filterFunc FilterFunc) []uint { var matchCount uint root := &t.nodes[1] - ret := make([]uint, 0) if root.TagCount > 0 { - ret = append(ret, t.tagsForNode(1)...) + ret = t.tagsForNode(ret, 1, filterFunc) } if address.Length == 0 { // caller just looking for root tags - return ret, nil + return ret } var nodeIndex uint @@ -548,28 +513,26 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint, error) { } // traverse the tree - count := 0 for { - count++ if nodeIndex == 0 { - return ret, nil + return ret } node := &t.nodes[nodeIndex] matchCount = node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return ret, nil + return ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue if node.TagCount > 0 { - ret = append(ret, t.tagsForNode(nodeIndex)...) + ret = t.tagsForNode(ret, nodeIndex, filterFunc) } if matchCount == address.Length { // exact match - we're done - return ret, nil + return ret } // there's still more address - keep traversing @@ -584,7 +547,7 @@ func (t *TreeV6) FindTags(address patricia.IPv6Address) ([]uint, error) { // FindDeepestTag finds a tag at the deepest level in the tree, representing the closest match. // - if that target node has multiple tags, the first in the list is returned -func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint, error) { +func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint) { root := &t.nodes[1] var found bool var ret uint @@ -596,7 +559,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint, error if address.Length == 0 { // caller just looking for root tags - return found, ret, nil + return found, ret } var nodeIndex uint @@ -609,14 +572,14 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint, error // traverse the tree for { if nodeIndex == 0 { - return found, ret, nil + return found, ret } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, ret, nil + return found, ret } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -627,7 +590,7 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint, error if matchCount == address.Length { // exact match - we're done - return found, ret, nil + return found, ret } // there's still more address - keep traversing @@ -641,8 +604,15 @@ func (t *TreeV6) FindDeepestTag(address patricia.IPv6Address) (bool, uint, error } // FindDeepestTags finds all tags at the deepest level in the tree, representing the closest match -// - returns empty array if nothing found -func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint, error) { +// - use FindDeepestTagsAppend if you can reuse slices, to cut down on allocations +func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint) { + ret := make([]uint, 0) + return t.FindDeepestTagsAppend(ret, address) +} + +// FindDeepestTagsAppend finds all tags at the deepest level in the tree, representing the closest match +// - appends results to the input slice +func (t *TreeV6) FindDeepestTagsAppend(ret []uint, address patricia.IPv6Address) (bool, []uint) { root := &t.nodes[1] var found bool var retTagIndex uint @@ -654,7 +624,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint, er if address.Length == 0 { // caller just looking for root tags - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } var nodeIndex uint @@ -667,14 +637,14 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint, er // traverse the tree for { if nodeIndex == 0 { - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } node := &t.nodes[nodeIndex] matchCount := node.MatchCount(address) if matchCount < node.prefixLength { // didn't match the entire node - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // matched the full node - get its tags, then chop off the bits we've already matched and continue @@ -685,7 +655,7 @@ func (t *TreeV6) FindDeepestTags(address patricia.IPv6Address) (bool, []uint, er if matchCount == address.Length { // exact match - we're done - return found, t.tagsForNode(retTagIndex), nil + return found, t.tagsForNode(ret, retTagIndex, nil) } // there's still more address - keep traversing diff --git a/uint_tree/tree_v6_manual.go b/uint_tree/tree_v6_manual.go index 04596fd..1095ec5 100644 --- a/uint_tree/tree_v6_manual.go +++ b/uint_tree/tree_v6_manual.go @@ -23,7 +23,9 @@ func (t *TreeV6) newNode(address patricia.IPv6Address, prefixLength uint) uint { } func (t *TreeV6) print() { + buf := make([]uint, 0) for i := range t.nodes { - fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(uint(i))) + buf = buf[:0] + fmt.Printf("%d: \tleft: %d, right: %d, prefix: %032b %032b (%d), tags: (%d): %v\n", i, int(t.nodes[i].Left), int(t.nodes[i].Right), int(t.nodes[i].prefixLeft), int(t.nodes[i].prefixRight), int(t.nodes[i].prefixLength), t.nodes[i].TagCount, t.tagsForNode(buf, uint(i), nil)) } }