Skip to content

Commit

Permalink
generic
Browse files Browse the repository at this point in the history
  • Loading branch information
gaissmai committed Jan 10, 2024
1 parent 7772023 commit 3fd646f
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 79 deletions.
10 changes: 5 additions & 5 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var intMap = map[int]string{

func BenchmarkLookup(b *testing.B) {
for k := 1; k <= 100_000; k *= 10 {
rt := new(cidrtree.Table)
rt := new(cidrtree.Table[any])
cidrs := shuffleFullTable(k)
for _, cidr := range cidrs {
rt.Insert(cidr, nil)
Expand All @@ -45,7 +45,7 @@ func BenchmarkLookup(b *testing.B) {

func BenchmarkLookupPrefix(b *testing.B) {
for k := 1; k <= 100_000; k *= 10 {
rt := new(cidrtree.Table)
rt := new(cidrtree.Table[any])
cidrs := shuffleFullTable(k)
for _, cidr := range cidrs {
rt.Insert(cidr, nil)
Expand All @@ -64,7 +64,7 @@ func BenchmarkLookupPrefix(b *testing.B) {

func BenchmarkClone(b *testing.B) {
for k := 1; k <= 100_000; k *= 10 {
rt := new(cidrtree.Table)
rt := new(cidrtree.Table[any])
for _, cidr := range shuffleFullTable(k) {
rt.Insert(cidr, nil)
}
Expand All @@ -80,7 +80,7 @@ func BenchmarkClone(b *testing.B) {

func BenchmarkInsert(b *testing.B) {
for k := 1; k <= 100_000; k *= 10 {
rt := new(cidrtree.Table)
rt := new(cidrtree.Table[any])
cidrs := shuffleFullTable(k)
for _, cidr := range cidrs {
rt.Insert(cidr, nil)
Expand All @@ -99,7 +99,7 @@ func BenchmarkInsert(b *testing.B) {

func BenchmarkDelete(b *testing.B) {
for k := 1; k <= 100_000; k *= 10 {
rt := new(cidrtree.Table)
rt := new(cidrtree.Table[any])
cidrs := shuffleFullTable(k)
for _, cidr := range cidrs {
rt.Insert(cidr, nil)
Expand Down
8 changes: 4 additions & 4 deletions debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
// fprintBST writes a horizontal tree diagram of the binary search tree (BST) to w.
//
// Note: This is for debugging purposes only.
func (t Table) fprintBST(w io.Writer) error {
func (t Table[T]) fprintBST(w io.Writer) error {
if t.root4 != nil {
if _, err := fmt.Fprint(w, "R "); err != nil {
return err
Expand All @@ -33,7 +33,7 @@ func (t Table) fprintBST(w io.Writer) error {
}

// fprintBST recursive helper.
func (n *node) fprintBST(w io.Writer, pad string) error {
func (n *node[T]) fprintBST(w io.Writer, pad string) error {
// stringify this node
_, err := fmt.Fprintf(w, "%v [prio:%.4g] [subtree maxUpper: %v]\n", n.cidr, float64(n.prio)/math.MaxUint64, n.maxUpper.cidr)
if err != nil {
Expand Down Expand Up @@ -80,7 +80,7 @@ func (n *node) fprintBST(w io.Writer, pad string) error {
// If the skip function is not nil, a true return value defines which nodes must be skipped in the statistics.
//
// Note: This is for debugging and testing purposes only during development.
func (t Table) statistics(skip func(netip.Prefix, any, int) bool) (size int, maxDepth int, average, deviation float64) {
func (t Table[T]) statistics(skip func(netip.Prefix, any, int) bool) (size int, maxDepth int, average, deviation float64) {
// key is depth, value is the sum of nodes with this depth
depths := make(map[int]int)

Expand Down Expand Up @@ -120,7 +120,7 @@ func (t Table) statistics(skip func(netip.Prefix, any, int) bool) (size int, max
}

// walkWithDepth in ascending prefix order.
func (n *node) walkWithDepth(cb func(netip.Prefix, any, int) bool, depth int) bool {
func (n *node[T]) walkWithDepth(cb func(netip.Prefix, any, int) bool, depth int) bool {
if n == nil {
return true
}
Expand Down
4 changes: 2 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var input = []netip.Prefix{
}

func ExampleTable_Lookup() {
rtbl := new(cidrtree.Table)
rtbl := new(cidrtree.Table[any])
for _, cidr := range input {
rtbl.Insert(cidr, nil)
}
Expand Down Expand Up @@ -83,7 +83,7 @@ func ExampleTable_Walk() {
return true
}

rtbl := new(cidrtree.Table)
rtbl := new(cidrtree.Table[any])
for _, cidr := range input {
rtbl.Insert(cidr, nil)
}
Expand Down
24 changes: 12 additions & 12 deletions stringify.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

// String returns a hierarchical tree diagram of the ordered CIDRs as string, just a wrapper for [Tree.Fprint].
func (t Table) String() string {
func (t Table[T]) String() string {
w := new(strings.Builder)
_ = t.Fprint(w)
return w.String()
Expand All @@ -17,7 +17,7 @@ func (t Table) String() string {
//
// The order from top to bottom is in ascending order of the start address
// and the subtree structure is determined by the CIDRs coverage.
func (t Table) Fprint(w io.Writer) error {
func (t Table[T]) Fprint(w io.Writer) error {
if err := t.root4.fprint(w); err != nil {
return err
}
Expand All @@ -27,16 +27,16 @@ func (t Table) Fprint(w io.Writer) error {
return nil
}

func (n *node) fprint(w io.Writer) error {
func (n *node[T]) fprint(w io.Writer) error {
if n == nil {
return nil
}

// pcm = parent-child-mapping
var pcm parentChildsMap
var pcm parentChildsMap[T]

// init map
pcm.pcMap = make(map[*node][]*node)
pcm.pcMap = make(map[*node[T]][]*node[T])

pcm = n.buildParentChildsMap(pcm)

Expand All @@ -50,11 +50,11 @@ func (n *node) fprint(w io.Writer) error {
}

// start recursion with root and empty padding
var root *node
var root *node[T]
return root.walkAndStringify(w, pcm, "")
}

func (n *node) walkAndStringify(w io.Writer, pcm parentChildsMap, pad string) error {
func (n *node[T]) walkAndStringify(w io.Writer, pcm parentChildsMap[T], pad string) error {
// the prefix (pad + glyphe) is already printed on the line on upper level
if n != nil {
if _, err := fmt.Fprintf(w, "%v (%v)\n", n.cidr, n.value); err != nil {
Expand Down Expand Up @@ -92,13 +92,13 @@ func (n *node) walkAndStringify(w io.Writer, pcm parentChildsMap, pad string) er
// parentChildsMap, needed for hierarchical tree printing, this is not BST printing!
//
// CIDR tree, parent->childs relation printed. A parent CIDR covers a child CIDR.
type parentChildsMap struct {
pcMap map[*node][]*node // parent -> []child map
stack []*node // just needed for the algo
type parentChildsMap[T any] struct {
pcMap map[*node[T]][]*node[T] // parent -> []child map
stack []*node[T] // just needed for the algo
}

// buildParentChildsMap, in-order traversal
func (n *node) buildParentChildsMap(pcm parentChildsMap) parentChildsMap {
func (n *node[T]) buildParentChildsMap(pcm parentChildsMap[T]) parentChildsMap[T] {
if n == nil {
return pcm
}
Expand All @@ -114,7 +114,7 @@ func (n *node) buildParentChildsMap(pcm parentChildsMap) parentChildsMap {
}

// pcmForNode, find parent in stack, remove cidrs from stack, put this cidr on stack.
func (n *node) pcmForNode(pcm parentChildsMap) parentChildsMap {
func (n *node[T]) pcmForNode(pcm parentChildsMap[T]) parentChildsMap[T] {
// if this cidr is covered by a prev cidr on stack
for j := len(pcm.stack) - 1; j >= 0; j-- {
that := pcm.stack[j]
Expand Down
60 changes: 30 additions & 30 deletions treap.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@ import (
)

// Table is an IPv4 and IPv6 routing table. The zero value is ready to use.
type Table struct {
type Table[T any] struct {
// make a treap for every IP version, the bits of the prefix are part of the weighted priority
root4 *node
root6 *node
root4 *node[T]
root6 *node[T]
}

// node is the recursive data structure of the treap.
type node struct {
maxUpper *node // augment the treap, see also recalc()
left *node
right *node
value any
type node[T any] struct {
maxUpper *node[T] // augment the treap, see also recalc()
left *node[T]
right *node[T]
value T
cidr netip.Prefix
prio uint64
}

// Insert adds pfx to the table with value val, changing the original table.
// If pfx is already present in the table, its value is set to val.
func (t *Table) Insert(pfx netip.Prefix, val any) {
func (t *Table[T]) Insert(pfx netip.Prefix, val T) {
pfx = pfx.Masked() // always canonicalize!

if pfx.Addr().Is4() {
Expand All @@ -46,7 +46,7 @@ func (t *Table) Insert(pfx netip.Prefix, val any) {

// InsertImmutable adds pfx to the table with value val, returning a new table.
// If pfx is already present in the table, its value is set to val.
func (t Table) InsertImmutable(pfx netip.Prefix, val any) *Table {
func (t Table[T]) InsertImmutable(pfx netip.Prefix, val T) *Table[T] {
pfx = pfx.Masked() // always canonicalize!

if pfx.Addr().Is4() {
Expand All @@ -60,7 +60,7 @@ func (t Table) InsertImmutable(pfx netip.Prefix, val any) *Table {
// insert into treap, changing nodes are copied, new treap is returned,
// old treap is modified if immutable is false.
// If node is already present in the table, its value is set to val.
func (n *node) insert(m *node, immutable bool) *node {
func (n *node[T]) insert(m *node[T], immutable bool) *node[T] {
if n == nil {
// recursion stop condition
return m
Expand Down Expand Up @@ -130,7 +130,7 @@ func (n *node) insert(m *node, immutable bool) *node {
}

// DeleteImmutable removes the prefix if it exists, returns the new table and true, false if not found.
func (t Table) DeleteImmutable(pfx netip.Prefix) (*Table, bool) {
func (t Table[T]) DeleteImmutable(pfx netip.Prefix) (*Table[T], bool) {
pfx = pfx.Masked() // always canonicalize!

is4 := pfx.Addr().Is4()
Expand All @@ -155,7 +155,7 @@ func (t Table) DeleteImmutable(pfx netip.Prefix) (*Table, bool) {
}

// Delete removes the prefix from table, returns true if it exists, false otherwise.
func (t *Table) Delete(pfx netip.Prefix) bool {
func (t *Table[T]) Delete(pfx netip.Prefix) bool {
pfx = pfx.Masked() // always canonicalize!

is4 := pfx.Addr().Is4()
Expand All @@ -180,22 +180,22 @@ func (t *Table) Delete(pfx netip.Prefix) bool {

// UnionImmutable combines any two tables immutable and returns the combined table.
// If there are duplicate entries, the value is taken from the other table.
func (t Table) UnionImmutable(other Table) *Table {
func (t Table[T]) UnionImmutable(other Table[T]) *Table[T] {
t.root4 = t.root4.union(other.root4, true, true)
t.root6 = t.root6.union(other.root6, true, true)
return &t
}

// Union combines two tables, changing the receiver table.
// If there are duplicate entries, the value is taken from the other table.
func (t *Table) Union(other Table) {
func (t *Table[T]) Union(other Table[T]) {
t.root4 = t.root4.union(other.root4, true, false)
t.root6 = t.root6.union(other.root6, true, false)
}

// union two treaps.
// flag overwrite isn't public but needed as input for rec-descent calls, see below when trepa are swapped.
func (n *node) union(b *node, overwrite bool, immutable bool) *node {
func (n *node[T]) union(b *node[T], overwrite bool, immutable bool) *node[T] {
// recursion stop condition
if n == nil {
return b
Expand Down Expand Up @@ -237,7 +237,7 @@ func (n *node) union(b *node, overwrite bool, immutable bool) *node {
// Walk iterates the cidrtree in ascending order.
// The callback function is called with the prefix and value of the respective node and the depth in the tree.
// If callback returns `false`, the iteration is aborted.
func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool) {
func (t Table[T]) Walk(cb func(pfx netip.Prefix, val T) bool) {
if !t.root4.walk(cb) {
return
}
Expand All @@ -246,7 +246,7 @@ func (t Table) Walk(cb func(pfx netip.Prefix, val any) bool) {
}

// walk tree in ascending prefix order.
func (n *node) walk(cb func(netip.Prefix, any) bool) bool {
func (n *node[T]) walk(cb func(netip.Prefix, T) bool) bool {
if n == nil {
return true
}
Expand All @@ -273,7 +273,7 @@ func (n *node) walk(cb func(netip.Prefix, any) bool) bool {
// If the ip isn't covered by any CIDR, the zero value and false is returned.
//
// Lookup does not allocate memory.
func (t Table) Lookup(ip netip.Addr) (lpm netip.Prefix, value any, ok bool) {
func (t Table[T]) Lookup(ip netip.Addr) (lpm netip.Prefix, value T, ok bool) {
if ip.Is4() {
// don't return the depth
lpm, value, ok, _ = t.root4.lpmIP(ip, 0)
Expand All @@ -285,7 +285,7 @@ func (t Table) Lookup(ip netip.Addr) (lpm netip.Prefix, value any, ok bool) {
}

// lpmIP rec-descent
func (n *node) lpmIP(ip netip.Addr, depth int) (lpm netip.Prefix, value any, ok bool, atDepth int) {
func (n *node[T]) lpmIP(ip netip.Addr, depth int) (lpm netip.Prefix, value T, ok bool, atDepth int) {
for {
// recursion stop condition
if n == nil {
Expand Down Expand Up @@ -326,7 +326,7 @@ func (n *node) lpmIP(ip netip.Addr, depth int) (lpm netip.Prefix, value any, ok
// If the prefix isn't equal or covered by any CIDR in the table, the zero value and false is returned.
//
// LookupPrefix does not allocate memory.
func (t Table) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value any, ok bool) {
func (t Table[T]) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value T, ok bool) {
pfx = pfx.Masked() // always canonicalize!

if pfx.Addr().Is4() {
Expand All @@ -340,7 +340,7 @@ func (t Table) LookupPrefix(pfx netip.Prefix) (lpm netip.Prefix, value any, ok b
}

// lpmCIDR rec-descent
func (n *node) lpmCIDR(pfx netip.Prefix, depth int) (lpm netip.Prefix, value any, ok bool, atDepth int) {
func (n *node[T]) lpmCIDR(pfx netip.Prefix, depth int) (lpm netip.Prefix, value T, ok bool, atDepth int) {
for {
// recursion stop condition
if n == nil {
Expand Down Expand Up @@ -393,13 +393,13 @@ func (n *node) lpmCIDR(pfx netip.Prefix, depth int) (lpm netip.Prefix, value any
}

// Clone, deep cloning of the routing table.
func (t Table) Clone() *Table {
func (t Table[T]) Clone() *Table[T] {
t.root4 = t.root4.clone()
t.root6 = t.root6.clone()
return &t
}

func (n *node) clone() *node {
func (n *node[T]) clone() *node[T] {
if n == nil {
return n
}
Expand All @@ -421,7 +421,7 @@ func (n *node) clone() *node {
// and greater-than the provided cidr (BST key). The resulting nodes are
// properly formed treaps or nil.
// If the split must be immutable, first copy concerned nodes.
func (n *node) split(cidr netip.Prefix, immutable bool) (left, mid, right *node) {
func (n *node[T]) split(cidr netip.Prefix, immutable bool) (left, mid, right *node[T]) {
// recursion stop condition
if n == nil {
return nil, nil, nil
Expand Down Expand Up @@ -472,7 +472,7 @@ func (n *node) split(cidr netip.Prefix, immutable bool) (left, mid, right *node)

// join combines two disjunct treaps. All nodes in treap n have keys <= that of treap m
// for this algorithm to work correctly. If the join must be immutable, first copy concerned nodes.
func (n *node) join(m *node, immutable bool) *node {
func (n *node[T]) join(m *node[T], immutable bool) *node[T] {
// recursion stop condition
if n == nil {
return m
Expand Down Expand Up @@ -511,8 +511,8 @@ func (n *node) join(m *node, immutable bool) *node {
// ###########################################################

// makeNode, create new node with cidr.
func makeNode(pfx netip.Prefix, val any) *node {
n := new(node)
func makeNode[T any](pfx netip.Prefix, val T) *node[T] {
n := new(node[T])
n.cidr = pfx.Masked() // always store the prefix in normalized form
n.value = val
n.prio = mrand.Uint64()
Expand All @@ -521,15 +521,15 @@ func makeNode(pfx netip.Prefix, val any) *node {
}

// copyNode, make a shallow copy of the pointers and the cidr.
func (n *node) copyNode() *node {
func (n *node[T]) copyNode() *node[T] {
c := *n
return &c
}

// recalc the augmented fields in treap node after each creation/modification
// with values in descendants.
// Only one level deeper must be considered. The treap datastructure is very easy to augment.
func (n *node) recalc() {
func (n *node[T]) recalc() {
if n == nil {
return
}
Expand Down
Loading

0 comments on commit 3fd646f

Please sign in to comment.