7

I'm new to Go and have implemented a binary search tree. The tree can store any value (specifically, anything that implements interface{}).

I'd like to build upon this implementation to create a self-balancing red-black tree. In an object-oriented language, I'd define a subclass of BinarySearchTree that adds a color data member, then override the Insert method to perform a balancing operation.

Question: How can I implement a binary search tree and red-black tree in Go without duplicating code?

Current Binary Search Tree Implementation

Here's my binary search tree implementation:

package trees

import (
    "github.com/modocache/cargo/comparators"
    "reflect"
)

type BinarySearchTree struct {
    Parent *BinarySearchTree
    Left   *BinarySearchTree
    Right  *BinarySearchTree
    Value  interface{}      // Can hold any value
    less   comparators.Less // A comparator function to determine whether
                            // an inserted value is placed left or right
}

func NewBinarySearchTree(value interface{}, less comparators.Less) *BinarySearchTree {
    return &BinarySearchTree{Value: value, less: less}
}

func (tree *BinarySearchTree) Insert(value interface{}) *BinarySearchTree {
    if tree.less(value, tree.Value) {
        return tree.insertLeft(value)
    } else {
        return tree.insertRight(value)
    }
}

func (tree *BinarySearchTree) insertLeft(value interface{}) *BinarySearchTree {
    if tree.Left == nil {
        tree.Left = &BinarySearchTree{Value: value, Parent: tree, less: tree.less}
        return tree.Left
    } else {
        return tree.Left.Insert(value)
    }
}

func (tree *BinarySearchTree) insertRight(value interface{}) *BinarySearchTree {
    if tree.Right == nil {
        tree.Right = &BinarySearchTree{Value: value, Parent: tree, less: tree.less}
        return tree.Right
    } else {
        return tree.Right.Insert(value)
    }
}

func (tree *BinarySearchTree) Find(value interface{}) *BinarySearchTree {
    if reflect.DeepEqual(value, tree.Value) {
        return tree
    } else if tree.less(value, tree.Value) {
        return tree.findLeft(value)
    } else {
        return tree.findRight(value)
    }
}

func (tree *BinarySearchTree) findLeft(value interface{}) *BinarySearchTree {
    if tree.Left == nil {
        return nil
    } else {
        return tree.Left.Find(value)
    }
}

func (tree *BinarySearchTree) findRight(value interface{}) *BinarySearchTree {
    if tree.Right == nil {
        return nil
    } else {
        return tree.Right.Find(value)
    }
}

Here's an example of how this struct may be used:

tree := NewBinarySearchTree(100, func(value, treeValue interface{}) bool {
    return value.(int) < treeValue.(int)
})
tree.Insert(200)
tree.Insert(300)
tree.Insert(250)
tree.Insert(150)
tree.Insert(275)
tree.Find(250) // Returns tree.Right.Right.Left

Desired (But Impossible) Red-Black Tree Implementation

I'd like to "extend" the BinarySearchTree struct like so:

type RedBlackTree struct {
    Parent *RedBlackTree     // These must be able to store
    Left   *RedBlackTree     // pointers to red-black trees
    Right  *RedBlackTree
    Value  interface{}
    less   comparators.Less
    color RedBlackTreeColor  // Each tree must maintain a color property
}

And then "override" the .Insert() method like so:

func (tree *RedBlackTree) Insert(value interface{}) *RedBlackTree {
    var inserted *RedBlackTree

    // Insertion logic is identical to BinarySearchTree
    if tree.less(value, tree.Value) {
        inserted = tree.insertLeft(value)
    } else {
        inserted tree.insertRight(value)
    }

    // .balance() is a private method on RedBlackTree that balances
    // the tree based on each node's color
    inserted.balance()

    // Returns a *RedBlackTree
    return inserted
}

I don't think this is idiomatic Go code, however.

  • Since BinarySearchTree is defined with pointers to other BinarySearchTree structs, a RedBlackTree that "extends" BinarySearchTree still has pointers to BinarySearchTree objects.
  • There's no way to "override" .Insert(). My only option is to define another method, such as .BalancedInsert().

Currently Trying

One idea I'm currently trying is to define an interface such as this one:

type BinarySearchable interface {
    Parent() *BinarySearchable
    SetParent(searchable *BinarySearchable)

    Left() *BinarySearchable
    SetLeft(searchable *BinarySearchable)

    Right() *BinarySearchable
    SetRight(searchable *BinarySearchable)

    Value() interface{}
    Less() comparators.Less
    Insert(searchable *BinarySearchable) *BinarySearchable
    Find(value interface{}) *BinarySearchable
}

The BinarySearchTree and RedBlackTree will then implement these interfaces. One problem is how to share the .Insert() logic, however. Perhaps define a private function that each struct will use?

Any and all suggestions welcome.

Brian Gesiak
  • 6,648
  • 4
  • 35
  • 50

2 Answers2

4

This is what I came up with. I'd rather accept another answer, but this is the best so far.

The BinarySearchable Interface

Both BinarySearchTree and RedBlackTree conform to this interface. The file also defines functions common to all binary-searchable structs, including insert(), .find(), leftRotate(), and so on.

In order to dynamically create objects of various types, the insert() function takes a childConstructor function parameter. This function is used by BinarySearchTree and RedBlackTree to create child trees of arbitrary types.

// binary_searchable.go

type BinarySearchable interface {
    Parent() BinarySearchable
    SetParent(searchable BinarySearchable)
    Left() BinarySearchable
    SetLeft(searchable BinarySearchable)
    Right() BinarySearchable
    SetRight(searchable BinarySearchable)
    Value() interface{}
    Insert(value interface{}) BinarySearchable
    Find(value interface{}) BinarySearchable
    Less() comparators.Less
}

type childConstructor func(parent BinarySearchable, value interface{}) BinarySearchable

func insert(searchable BinarySearchable, value interface{}, constructor childConstructor) BinarySearchable {
    if searchable.Less()(value, searchable.Value()) {
        if searchable.Left() == nil {
            // The constructor function is used to
            // create children of dynamic types
            searchable.SetLeft(constructor(searchable, value))
            return searchable.Left()
        } else {
            return searchable.Left().Insert(value)
        }
    } else {
        if searchable.Right() == nil {
            searchable.SetRight(constructor(searchable, value))
            return searchable.Right()
        } else {
            return searchable.Right().Insert(value)
        }
    }
}

BinarySearchTree

This is the "base" struct that is embedded in other tree structs. It provides default implementations of the BinarySearchable interface methods, as well as the data attributes each tree will use to store their children.

// binary_search_tree.go

type BinarySearchTree struct {
    parent BinarySearchable
    left   BinarySearchable
    right  BinarySearchable
    value  interface{}
    less   comparators.Less
}

func (tree *BinarySearchTree) Parent() BinarySearchable {
    return tree.parent
}

func (tree *BinarySearchTree) SetParent(parent BinarySearchable) {
    tree.parent = parent
}

// ...setters and getters for left, right, value, less, etc.

func (tree *BinarySearchTree) Insert(value interface{}) BinarySearchable {
    // Pass `insert()` a constructor that creates a `*BinarySearchTree`
    constructor := func(parent BinarySearchable, value interface{}) BinarySearchable {
        return &BinarySearchTree{value: value, less: tree.less, parent: parent}
    }
    return insert(tree, value, constructor).(*BinarySearchTree)
}

func (tree *BinarySearchTree) Find(value interface{}) BinarySearchable {
    return find(tree, value)
}

RedBlackTree

This embeds BinarySearchTree and passes a custom constructor to insert(). The balancing code is omitted for brevity; you can see the whole file here.

// red_black_tree.go

type RedBlackTree struct {
    *BinarySearchTree
    color RedBlackTreeColor
}

func NewRedBlackTree(value interface{}, less comparators.Less) *RedBlackTree {
    return &RedBlackTree{&BinarySearchTree{value: value, less: less}, Black}
}

func (tree *RedBlackTree) Insert(value interface{}) BinarySearchable {
    constructor := func(parent BinarySearchable, value interface{}) BinarySearchable {
        return &RedBlackTree{&BinarySearchTree{value: value, less: tree.less, parent: parent}, Red}
    }

    inserted := insert(tree, value, constructor).(*RedBlackTree)
    inserted.balance()
    return inserted
}

func (tree *RedBlackTree) balance() {
    // ...omitted for brevity
}

If anyone has a more idiomatic design, or suggestions to improve this design, please post an answer and I will accept it.

Brian Gesiak
  • 6,648
  • 4
  • 35
  • 50
3

You could use embedding for example :

type A struct{}

func (a *A) fn()  { fmt.Println("A.fn") }
func (a *A) fn1() { fmt.Println("A.fn1") }

type B struct{ *A }

func (a *B) fn() { fmt.Println("B.fn") }

func main() {
    b := &B{&A{}}
    b.fn()
    b.fn1()
}

This should override the BST's Insert but keep all the other functions:

type RedBlackTree struct {
    *BinarySearchTree
    color RedBlackTreeColor // This is the only new attribute
}

func (tree *RedBlackTree) Insert(value interface{}) *RedBlackTree {}

http://golang.org/doc/effective_go.html#embedding

//edit

Rereading the question you will have to change the logic a little

type RedBlackTree struct {
    *BinarySearchTree
}
type rbtValue struct {
    value interface{}
    Color RedBlackTreeColor
}
func (tree *RedBlackTree) Insert(value interface{}) (inserted *RedBlackTree) {
    if tree.less(value, tree.Value) {
        inserted = tree.insertLeft(&rbt{value, Red})
    } else {
        inserted = tree.insertRight(&rbt{value, Black})
    }
    inserted.balance()
    return inserted
}

Then make a comparator that works on tree.Value.(rbtValue).Value

OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • 1
    I don't think this works. I am aware of embedding in Go, but the problem is that `.insertLeft()` sets `tree.Left = NewBinarySearchTree()`. I need this to be `tree.Left = NewRedBlackTree()`, since each node in a red-black tree must maintain a color property. However, since `Left` is defined as a `*BinarySearchTree`, I cannot set it to an instance of `*RedBlackTree`. My original post included a definition of `type RedBlackTree struct` that had pointers to `*BinarySearchTree`, which is incorrect. I updated my original post. – Brian Gesiak May 07 '14 at 03:57
  • 1
    I realized that after the answer, check the edit, however your answer is the way to go. – OneOfOne May 07 '14 at 11:35
  • Thanks for the answer, though, it helped get me in the right direction. By the way, I was wondering: you embed using a pointer, i.e.: `*BinarySearchTree`. Searching the interwebs I found an example which embeds without using a pointer, i.e.: `BinarySearchTree`. Is there a difference? – Brian Gesiak May 07 '14 at 11:43
  • 1
    Well, I used the pointer to avoid copies really, a bad habit of using pointers unless it's a native type or a very simple struct. – OneOfOne May 07 '14 at 11:49