Browse Source

Initial import.

Yawning Angel 1 year ago
commit
5dd3a1eba4
4 changed files with 726 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 122 0
      LICENSE
  3. 505 0
      avl.go
  4. 97 0
      avl_test.go

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*.swp
+*~

+ 122 - 0
LICENSE

@@ -0,0 +1,122 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
+

+ 505 - 0
avl.go

@@ -0,0 +1,505 @@
+// avl.go - An AVL tree implementation.
+//
+// To the extent possible under law, Yawning Angel has waived all copyright
+// and related or neighboring rights to avl, using the Creative
+// Commons "CC0" public domain dedication. See LICENSE or
+// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
+
+// Package avl implements an AVL tree.
+package avl
+
+// This is a fairly straight forward adaptation of the CC0 C implementation
+// from https://github.com/ebiggers/avl_tree/ by Eric Biggers into what is
+// hopefully idiomatic Go.
+//
+// The primary differences from the original package are:
+//  * The balance factor is not stored separately from the parent pointer.
+//  * The container is non-intrusive.
+//  * Only in-order traversal is currently supported.
+
+import "errors"
+
+var (
+	errNoCmpFn          = errors.New("avl: no comparison function")
+	errInvalidDirection = errors.New("avl: invalid direction")
+)
+
+// CompareFunc is the function used to compare entries in the Tree to maintain
+// ordering.  It MUST return < 0, 0, or > 0 if the a is less than,
+// equal to, or greater than b respectively.
+//
+// Note: All calls made to the comparison function will pass the user supplied
+// value as a, and the in-Tree value as b.
+type CompareFunc func(a, b interface{}) int
+
+// Direction is the direction associated with an iterator.
+type Direction int8
+
+const (
+	// Backward is backward (in-order).
+	Backward Direction = -1
+
+	// Forward is forward (in-order).
+	Forward Direction = 1
+)
+
+// Iterator is a Tree iterator.  Modifying the Tree while iterating is
+// unsupported except for removing the current Node.
+type Iterator struct {
+	tree        *Tree
+	cur, next   *Node
+	sign        int8
+	initialized bool
+}
+
+// First moves the iterator to the first Node in the Tree and returns the
+// first Node or nil iff the Tree is empty.
+func (it *Iterator) First() *Node {
+	it.cur, it.next = it.tree.firstOrLastInOrder(-it.sign), nil
+	if it.cur != nil {
+		it.next = it.cur.nextOrPrevInOrder(it.sign)
+	}
+	it.initialized = true
+	return it.cur
+}
+
+// Get returns the Node currently pointed to by the iterator.  It is safe to
+// remove the Node returned from the Tree.
+func (it *Iterator) Get() *Node {
+	if !it.initialized {
+		return it.First()
+	}
+	return it.cur
+}
+
+// Next advances the iterator and returns the Node or nil iff the end of the
+// Tree has been reached.
+func (it *Iterator) Next() *Node {
+	if !it.initialized {
+		it.First()
+	}
+	if it.next == nil {
+		return nil
+	}
+
+	it.cur = it.next
+	it.next = it.cur.nextOrPrevInOrder(it.sign)
+	return it.cur
+}
+
+// Node is a node of a Tree.
+type Node struct {
+	// Value is the value stored by the Node.
+	Value interface{}
+
+	parent, left, right *Node
+	balance             int8
+}
+
+func (n *Node) reset() {
+	// Note: This deliberately leaves Value intact.
+	n.parent, n.left, n.right = nil, nil, nil
+	n.balance = 0
+}
+
+func (n *Node) setParentBalance(parent *Node, balance int8) {
+	n.parent = parent
+	n.balance = balance
+}
+
+func (n *Node) getChild(sign int8) *Node {
+	if sign < 0 {
+		return n.left
+	}
+	return n.right
+}
+
+func (n *Node) nextOrPrevInOrder(sign int8) *Node {
+	var next, tmp *Node
+
+	if next = n.getChild(+sign); next != nil {
+		for {
+			tmp = next.getChild(-sign)
+			if tmp == nil {
+				break
+			}
+			next = tmp
+		}
+	} else {
+		tmp, next = n, n.parent
+		for next != nil && tmp == next.getChild(+sign) {
+			tmp, next = next, next.parent
+		}
+	}
+
+	return next
+}
+
+func (n *Node) setChild(sign int8, child *Node) {
+	if sign < 0 {
+		n.left = child
+	} else {
+		n.right = child
+	}
+}
+
+func (n *Node) adjustBalanceFactor(amount int8) {
+	n.balance += amount
+}
+
+// Tree represents an AVL tree.
+type Tree struct {
+	root  *Node
+	cmpFn CompareFunc
+	size  int
+}
+
+// Len returns the number of elements in the Tree.
+func (t *Tree) Len() int {
+	return t.size
+}
+
+// First returns the first node in the Tree (in-order) or nil iff the Tree is
+// empty.
+func (t *Tree) First() *Node {
+	return t.firstOrLastInOrder(-1)
+}
+
+// Last returns the last element in the Tree (in-order) or nil iff the Tree is
+// empty.
+func (t *Tree) Last() *Node {
+	return t.firstOrLastInOrder(1)
+}
+
+// Find finds the value in the Tree, and returns the Node or nil iff the value
+// is not present.
+func (t *Tree) Find(v interface{}) *Node {
+	if t.cmpFn == nil {
+		panic(errNoCmpFn)
+	}
+
+	cur := t.root
+	for cur != nil {
+		cmp := t.cmpFn(v, cur.Value)
+		switch {
+		case cmp < 0:
+			cur = cur.left
+		case cmp > 0:
+			cur = cur.right
+		default:
+			return cur
+		}
+	}
+
+	return nil
+}
+
+// Insert inserts the value into the Tree, and returns the newly created Node
+// or the existing Node iff the value is already present in the tree.
+func (t *Tree) Insert(v interface{}) *Node {
+	if t.cmpFn == nil {
+		panic(errNoCmpFn)
+	}
+
+	var cur *Node
+	curPtr := &t.root
+	for *curPtr != nil {
+		cur = *curPtr
+		cmp := t.cmpFn(v, cur.Value)
+		switch {
+		case cmp < 0:
+			curPtr = &cur.left
+		case cmp > 0:
+			curPtr = &cur.right
+		default:
+			return cur
+		}
+	}
+
+	n := &Node{
+		Value:   v,
+		parent:  cur,
+		balance: 0,
+	}
+	*curPtr = n
+	t.rebalanceAfterInsert(n)
+	t.size++
+
+	return n
+}
+
+// Remove removes the Node from the Tree.
+func (t *Tree) Remove(node *Node) {
+	var parent *Node
+	var leftDeleted bool
+
+	if node.left != nil && node.right != nil {
+		parent, leftDeleted = t.swapWithSuccessor(node)
+	} else {
+		child := node.left
+		if child == nil {
+			child = node.right
+		}
+		parent = node.parent
+		if parent != nil {
+			if node == parent.left {
+				parent.left = child
+				leftDeleted = true
+			} else {
+				parent.right = child
+				leftDeleted = false
+			}
+			if child != nil {
+				child.parent = parent
+			}
+		} else {
+			if child != nil {
+				child.parent = parent
+			}
+			t.root = child
+			node.reset()
+			return
+		}
+	}
+
+	for {
+		if leftDeleted {
+			parent = t.handleSubtreeShrink(parent, +1, &leftDeleted)
+		} else {
+			parent = t.handleSubtreeShrink(parent, -1, &leftDeleted)
+		}
+		if parent == nil {
+			break
+		}
+	}
+	node.reset()
+}
+
+// Iterator returns an iterator that traverses the tree (in-order) in the
+// specified direction.  Modifying the Tree while iterating is unsupported
+// except for removing the current Node.
+func (t *Tree) Iterator(direction Direction) *Iterator {
+	switch direction {
+	case Forward, Backward:
+	default:
+		panic(errInvalidDirection)
+	}
+
+	return &Iterator{
+		tree: t,
+		sign: int8(direction),
+	}
+}
+
+func (t *Tree) firstOrLastInOrder(sign int8) *Node {
+	first := t.root
+	if first != nil {
+		for {
+			tmp := first.getChild(+sign)
+			if tmp == nil {
+				break
+			}
+			first = tmp
+		}
+	}
+	return first
+}
+
+func (t *Tree) replaceChild(parent, oldChild, newChild *Node) {
+	if parent != nil {
+		if oldChild == parent.left {
+			parent.left = newChild
+		} else {
+			parent.right = newChild
+		}
+	} else {
+		t.root = newChild
+	}
+}
+
+func (t *Tree) rotate(a *Node, sign int8) {
+	b := a.getChild(-sign)
+	e := b.getChild(+sign)
+	p := a.parent
+
+	a.setChild(-sign, e)
+	a.parent = b
+
+	b.setChild(+sign, a)
+	b.parent = p
+
+	if e != nil {
+		e.parent = a
+	}
+
+	t.replaceChild(p, a, b)
+}
+
+func (t *Tree) doDoubleRotate(b, a *Node, sign int8) *Node {
+	e := b.getChild(+sign)
+	f := e.getChild(-sign)
+	g := e.getChild(+sign)
+	p := a.parent
+	eBal := e.balance
+
+	a.setChild(-sign, g)
+	aBal := -eBal
+	if sign*eBal >= 0 {
+		aBal = 0
+	}
+	a.setParentBalance(e, aBal)
+
+	b.setChild(+sign, f)
+	bBal := -eBal
+	if sign*eBal <= 0 {
+		bBal = 0
+	}
+	b.setParentBalance(e, bBal)
+
+	e.setChild(+sign, a)
+	e.setChild(-sign, b)
+	e.setParentBalance(p, 0)
+
+	if g != nil {
+		g.parent = a
+	}
+
+	if f != nil {
+		f.parent = b
+	}
+
+	t.replaceChild(p, a, e)
+
+	return e
+}
+
+func (t *Tree) handleSubtreeGrowth(node, parent *Node, sign int8) bool {
+	oldBalanceFactor := parent.balance
+	if oldBalanceFactor == 0 {
+		parent.adjustBalanceFactor(sign)
+		return false
+	}
+
+	newBalanceFactor := oldBalanceFactor + sign
+	if newBalanceFactor == 0 {
+		parent.adjustBalanceFactor(sign)
+		return true
+	}
+
+	if sign*node.balance > 0 {
+		t.rotate(parent, -sign)
+		parent.adjustBalanceFactor(-sign)
+		node.adjustBalanceFactor(-sign)
+	} else {
+		t.doDoubleRotate(node, parent, -sign)
+	}
+
+	return true
+}
+
+func (t *Tree) rebalanceAfterInsert(inserted *Node) {
+	node, parent := inserted, inserted.parent
+	switch {
+	case parent == nil:
+		return
+	case node == parent.left:
+		parent.adjustBalanceFactor(-1)
+	default:
+		parent.adjustBalanceFactor(+1)
+	}
+
+	if parent.balance == 0 {
+		return
+	}
+
+	for done := false; !done; {
+		node = parent
+		if parent = node.parent; parent == nil {
+			return
+		}
+
+		if node == parent.left {
+			done = t.handleSubtreeGrowth(node, parent, -1)
+		} else {
+			done = t.handleSubtreeGrowth(node, parent, +1)
+		}
+	}
+}
+
+func (t *Tree) swapWithSuccessor(x *Node) (*Node, bool) {
+	var ret *Node
+	var leftDeleted bool
+
+	y := x.right
+	if y.left == nil {
+		ret = y
+	} else {
+		var q *Node
+
+		for {
+			q = y
+			if y = y.left; y.left == nil {
+				break
+			}
+		}
+
+		if q.left = y.right; q.left != nil {
+			q.left.parent = q
+		}
+		y.right = x.right
+		x.right.parent = y
+		ret = q
+		leftDeleted = true
+	}
+
+	y.left = x.left
+	x.left.parent = y
+
+	y.parent = x.parent
+	y.balance = x.balance
+
+	t.replaceChild(x.parent, x, y)
+
+	return ret, leftDeleted
+}
+
+func (t *Tree) handleSubtreeShrink(parent *Node, sign int8, leftDeleted *bool) *Node {
+	oldBalanceFactor := parent.balance
+	if oldBalanceFactor == 0 {
+		parent.adjustBalanceFactor(sign)
+		return nil
+	}
+
+	var node *Node
+	newBalanceFactor := oldBalanceFactor + sign
+	if newBalanceFactor == 0 {
+		parent.adjustBalanceFactor(sign)
+		node = parent
+	} else {
+		node = parent.getChild(sign)
+		if sign*node.balance >= 0 {
+			t.rotate(parent, -sign)
+			if node.balance == 0 {
+				node.adjustBalanceFactor(-sign)
+				return nil
+			}
+			parent.adjustBalanceFactor(-sign)
+			node.adjustBalanceFactor(-sign)
+		} else {
+			node = t.doDoubleRotate(node, parent, -sign)
+		}
+	}
+	if parent = node.parent; parent != nil {
+		*leftDeleted = node == parent.left
+	}
+	return parent
+}
+
+// New returns an initialized Tree.
+func New(cmpFn CompareFunc) *Tree {
+	if cmpFn == nil {
+		panic(errNoCmpFn)
+	}
+
+	return &Tree{cmpFn: cmpFn}
+}

+ 97 - 0
avl_test.go

@@ -0,0 +1,97 @@
+// avl_test.go - AVL tree tests.
+//
+// To the extent possible under law, Yawning Angel has waived all copyright
+// and related or neighboring rights to avl, using the Creative
+// Commons "CC0" public domain dedication. See LICENSE or
+// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
+
+package avl
+
+import (
+	"math/rand"
+	"sort"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestAVLTree(t *testing.T) {
+	require := require.New(t)
+
+	cmpInt := func(a, b interface{}) int {
+		aInt, bInt := a.(int), b.(int)
+		switch {
+		case aInt < bInt:
+			return -1
+		case aInt > bInt:
+			return 1
+		default:
+			return 0
+		}
+	}
+
+	tree := New(cmpInt)
+	require.Equal(0, tree.Len(), "Len(): empty")
+	require.Nil(tree.First(), "First(): empty")
+	require.Nil(tree.Last(), "Last(): empty")
+
+	iter := tree.Iterator(Forward)
+	require.Nil(iter.First(), "Iterator: First(), empty")
+	require.Nil(iter.Next(), "Iterator: Next(), empty")
+
+	// Test insertion.
+	const nrEntries = 1024
+	insertedMap := make(map[int]*Node)
+	for len(insertedMap) != nrEntries {
+		v := rand.Int()
+		if insertedMap[v] != nil {
+			continue
+		}
+		insertedMap[v] = tree.Insert(v)
+	}
+	require.Equal(nrEntries, tree.Len(), "Len(): After insertion")
+
+	// Ensure that all entries can be found.
+	for k, v := range insertedMap {
+		require.Equal(v, tree.Find(k), "Find(): %v", k)
+		require.Equal(k, v.Value, "Find(): %v Value", k)
+	}
+
+	// Test the forward/backward iterators.
+	inOrder := make([]int, 0, nrEntries)
+	for k := range insertedMap {
+		inOrder = append(inOrder, k)
+	}
+	sort.Ints(inOrder)
+
+	iter = tree.Iterator(Forward)
+	visited := 0
+	for node := iter.First(); node != nil; node = iter.Next() {
+		v, idx := node.Value.(int), visited
+		require.Equal(inOrder[visited], v, "Iterator: Forward[%v]", idx)
+		require.Equal(node, iter.Get(), "Iterator: Forward[%v]: Get()", idx)
+		visited++
+	}
+	require.Equal(nrEntries, visited, "Iterator: Forward: Visited")
+
+	iter = tree.Iterator(Backward)
+	visited = 0
+	for node := iter.First(); node != nil; node = iter.Next() {
+		v, idx := node.Value.(int), nrEntries-1-visited
+		require.Equal(inOrder[idx], v, "Iterator: Backward[%v]", idx)
+		require.Equal(node, iter.Get(), "Iterator: Backward[%v]: Get()", idx)
+		visited++
+	}
+	require.Equal(nrEntries, visited, "Iterator: Backward: Visited")
+
+	// Test removal.
+	for _, idx := range rand.Perm(nrEntries) {
+		v := inOrder[idx]
+		n := tree.Find(v)
+		require.Equal(v, n.Value, "Find(): %v (Pre-remove)", v)
+		tree.Remove(n)
+
+		n = tree.Find(v)
+		require.Nil(n, "Find(): %v (Post-remove)", v)
+	}
+}