Skip to content

OlexiyKhokhlov/avltree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Support Ukraine Go Reference License: MIT Go Report Card Go version

AVL Tree Go's module

Version Information

The current version 2.0.0 has the same functionality as v1.0.4 but uses genrecis that was introduced in Go v1.18.

Old version 1.0.4 is placed here https://github.com/OlexiyKhokhlov/avltree/tree/main

Installation

$ go get gopkg.in/OlexiyKhokhlov/avltree.v2

Motivation

Go's standard library is still lean. Unlike other programming languages it hasn't got a container that holds a pair key-value in the determined order. This module tries to fix it!

Internal details

Ordered containsers usually based on RB-Tree. But it uses AVL Tree. There are a several reasons for that:

  • AVL-Tree always provides more balanced tree.
  • RB-Tree balancer is really lame in the inserting of ordered sequences.
  • RB-Tree is more popular so I don't want to implement one more of that.

This implementation has the next features:

  • Every tree node contains only two node pointers. It helps to reduce memory usage.
  • Iterators isn't implemented. May be I'll do that later. You can use enumeration methods instead.
  • Go hasn't const qualifier. So there is no flex way to block possibility to key changinging inside of the tree. Of course I know about copying. But isn't a good solution. First of all Go hasn't got unified way to copy any type of data. And secondly it provides a bad performance when your key is a big structure. So be carefull, avoid key changing!
  • Inserting and Erasing methods are non-recursive. So it helps to reduse stack memory usage. And also it is cool!

Examples

Tree construction
package main

import (
	"fmt"
	"strings"

	"gopkg.in/OlexiyKhokhlov/avltree.v2"
)

func main() {
	// Create a tree where Key type is 'int' and value type is 'string'.
	// It is possible to use NewAVLTreeOrderedKey since `int` type is one from `constraints.Ordered`
	tree1 := avltree.NewAVLTreeOrderedKey[int, string]()

	// Create a tree where Key type is `*int` and value type is 'string'.
	// It is possible to use NewAVLTreeOrderedKeyPtr since `int` type is one from `constraints.Ordered`
	tree2 := avltree.NewAVLTreeOrderedKeyPtr[int, string]()

	// Create a tree where Key type is a custom struct 'MyStruct1'
	// Is much better to use Key like '*MyStruct1' since it helps to avoid internal keys copying.
	// Need to use NewAVLTree with user defined comparator for that
	type MyStruct1 struct {
		key     int
		payload string
	}
	tree3 := avltree.NewAVLTree[*MyStruct1, int](func(a *MyStruct1, b *MyStruct1) int {
		if a.key == b.key {
			return 0
		}
		if a.key < b.key {
			return -1
		}
		return 1
	})

	// Create a tree where key is a struct and a key is divided on two parts
	type MyStruct2 struct {
		KeyPart1 string
		KeyPart2 int
		Payload  string
	}

	// It is much better to use pointer when key isn't a trivial data type.
	// It helps to avoid internal copying.
	tree4 := avltree.NewAVLTree[*MyStruct2, string](func(a *MyStruct2, b *MyStruct2) int {
		strcmp := strings.Compare(a.KeyPart1, b.KeyPart1)
		if strcmp != 0 {
			return strcmp
		}
		if a.KeyPart2 == b.KeyPart2 {
			return 0
		}
		if a.KeyPart1 < b.KeyPart1 {
			return -1
		}
		return 1
	})

	//Cheating Go about not used variables
	fmt.Println(tree1.Empty())
	fmt.Println(tree2.Empty())
	fmt.Println(tree3.Empty())
	fmt.Println(tree4.Empty())
}
Modification
package main

import (
	"fmt"

	"gopkg.in/OlexiyKhokhlov/avltree.v2"
)

func main() {
	// Create a tree where Key type is 'int' and value type is 'string'.
	// It is possible to use NewAVLTreeOrderedKey since `int` type is one from `constraints.Ordered`
	tree := avltree.NewAVLTreeOrderedKey[int, string]()

	//Insert key, value pairs
	err := tree.Insert(10, "10")
	// err is nil here
	if err != nil {
		fmt.Println("Insertion failed: ", err)
	} else {
		fmt.Println("Inserted")
	}

	err = tree.Insert(0, "0")
	// err is nil here
	if err != nil {
		fmt.Println("Insertion failed: ", err)
	} else {
		fmt.Println("Inserted")
	}

	err = tree.Insert(5, "5")
	// err is nil here
	if err != nil {
		fmt.Println("Insertion failed: ", err)
	} else {
		fmt.Println("Inserted")
	}

	//Now tree contains {{0,"0"}, {5,"5"}, {10, "10"}}

	//Try insert duplicate
	err = tree.Insert(5, "5")
	// err is not nil here
	if err != nil {
		fmt.Println("Insertion failed: ", err)
	} else {
		fmt.Println("Inserted")
	}

	// Erase elements by key
	err = tree.Erase(0)
	// err is nil here
	if err != nil {
		fmt.Println("Erasing failed: ", err)
	} else {
		fmt.Println("Erased")
	}

	//Now tree doesn't contains '0' key. Try remove it again
	err = tree.Erase(0)
	// err is not nil here
	if err != nil {
		fmt.Println("Erasing failed: ", err)
	} else {
		fmt.Println("Erased")
	}

	// Is possible to modificate a value that is stored inside a tree
	value := tree.Find(5)
	if value != nil {
		// if tree has a value change it
		*value = "new value"
	}
	// Find it again and print
	value = tree.Find(5)
	if value != nil {
		fmt.Println("Changed value is: ", *value)
	}

	// Clear entire a tree
	tree.Clear()
}
Element access
package main

import (
	"fmt"
	"strconv"

	"gopkg.in/OlexiyKhokhlov/avltree.v2"
)

func main() {
	// Create a tree where Key type is 'int' and value type is 'string'.
	// It is possible to use NewAVLTreeOrderedKey since `int` type is one from `constraints.Ordered`
	tree := avltree.NewAVLTreeOrderedKey[int, string]()
	// Insert a set of keys
	for i := 0; i <= 100; i += 10 {
		tree.Insert(i, strconv.Itoa(i))
	}

	// Now tree contains [0, 10, ... 100] keys with corresponded values

	// Check if a tree contains some key
	if tree.Contains(5) {
		fmt.Println("Tree contains 5")
	} else {
		fmt.Println("Tree hasn't 5")
	}

	// Get pointer on stored in the tree value by the given key
	val := tree.Find(50)
	// If tree hasn't got such key returns nill
	if val != nil {
		fmt.Println("Tree contains value for key=50: ", *val)
	}

	// Get first element in the tree
	k, v := tree.First()
	// If tree is empty can return nil, nil
	if k != nil {
		fmt.Println("First element is: ", *k, *v)
	}

	// Get last element in the tree
	k, v = tree.Last()
	// If tree is empty can return nil, nil
	if k != nil {
		fmt.Println("First element is: ", *k, *v)
	}

	// Get a next element after the given key
	// The given key not necessary has been stored in the tree
	k, v = tree.FindNextElement(1)
	// If the given key is the last can return nil, nil
	if k != nil {
		fmt.Println("Element after 1 is: ", *k, *v)
	}

	// Get a prev element after the given key
	// The given key not necessary has been stored in the tree
	k, v = tree.FindPrevElement(100)
	// If the given key is the last can return nil, nil
	if k != nil {
		fmt.Println("Element before 100 is: ", *k, *v)
	}
}
Enumeration
package main

import (
	"fmt"
	"strconv"

	"gopkg.in/OlexiyKhokhlov/avltree.v2"
)

func main() {
	// Create a tree where Key type is 'int' and value type is 'string'.
	// It is possible to use NewAVLTreeOrderedKey since `int` type is one from `constraints.Ordered`
	tree := avltree.NewAVLTreeOrderedKey[int, string]()

	// Insert a set of keys
	for i := 0; i <= 100; i += 10 {
		tree.Insert(i, strconv.Itoa(i))
	}
	// Now tree contains [0, 10, ... 100] keys with corresponded values

	// Print all elements in the ascending order
	fmt.Println("Ascending:")
	tree.Enumerate(avltree.ASCENDING, func(key int, value string) bool {
		fmt.Println("Element: ", key, " ", value)
		return true // Always return true since we don't want to interupt enumearation
	})

	// Print all elements in the descending order
	fmt.Println("Descending:")
	tree.Enumerate(avltree.ASCENDING, func(key int, value string) bool {
		fmt.Println("Element: ", key, " ", value)
		return true // Always return true since we don't want to interupt enumearation
	})

	// Print all element these are between start and finish in ascending order
	start := 20
	finish := 68
	fmt.Println("Diapason: 20..68:")
	tree.EnumerateDiapason(&start, &finish, avltree.ASCENDING, func(key int, value string) bool {
		fmt.Println("Element: ", key, " ", value)
		return true // Always return true since we don't want to interupt enumearation
	})

	// Print all element these are greater than start in ascending order
	start = 20
	fmt.Println("Diapason: 20...:")
	tree.EnumerateDiapason(&start, nil, avltree.ASCENDING, func(key int, value string) bool {
		fmt.Println("Element: ", key, " ", value)
		return true // Always return true since we don't want to interupt enumearation
	})

	// Print all 3 element these are greater than start in ascending order
	start = 55
	fmt.Println("First 3 in diapason: 55...:")
	i := 0
	tree.EnumerateDiapason(&start, nil, avltree.ASCENDING, func(key int, value string) bool {
		fmt.Println("Element: ", key, " ", value)
		i += 1
		if i == 3 {
			return false // 3 element is already printed. Return false for stop
		}
		return true
	})

}
Capacity
package main

import (
	"fmt"
	"strconv"

	"gopkg.in/OlexiyKhokhlov/avltree.v2"
)

func main() {
	// Create a tree where Key type is 'int' and value type is 'string'.
	// It is possible to use NewAVLTreeOrderedKey since `int` type is one from `constraints.Ordered`
	tree := avltree.NewAVLTreeOrderedKey[int, string]()

	// Check tree is empty
	fmt.Println(tree.Empty()) // Prints true

	//Prints elements count
	fmt.Println(tree.Size()) // Prints 0

	// Insert a set of keys
	for i := 0; i <= 100; i += 10 {
		tree.Insert(i, strconv.Itoa(i))
	}
	// Now tree contains [0, 10, ... 100] keys with corresponded values

	// Check tree is empty
	fmt.Println(tree.Empty()) // Prints false

	//Prints elements count
	fmt.Println(tree.Size()) // Prints 11
}

TODO

  • It will be interesting to provide some performance testing for this implementation and comparing with other not only Go.

Links