Repository: vaskoz/dailycodingproblem-go (go)

Issue:
Day 348
Good morning! Here's your coding interview problem for today.

This problem was asked by Zillow.

A ternary search tree is a trie-like data structure where each node may have up to three children. Here is an example which represents the words `code, cob, be, ax, war`, and `we`.
```
       c
    /  |  \
   b   o   w
 / |   |   |
a  e   d   a
|    / |   | \ 
x   b  e   r  e  
```
The tree is structured according to the following rules:

* left child nodes link to words lexicographically earlier than the parent prefix
* right child nodes link to words lexicographically later than the parent prefix
* middle child nodes continue the current word

For instance, since `code` is the first word inserted in the tree, and `cob` lexicographically precedes `cod`, `cob` is represented as a left child extending from `cod`.

Implement insertion and search functions for a ternary search tree.

Pull request description:
Day348
Resolves #693.

Patch:
diff --git a/README.md b/README.md
index 60de29f..a70c429 100644
--- a/README.md
+++ b/README.md
@@ -354,6 +354,7 @@ problems from
 * [Day 345](https://github.com/vaskoz/dailycodingproblem-go/issues/687)
 * [Day 346](https://github.com/vaskoz/dailycodingproblem-go/issues/688)
 * [Day 347](https://github.com/vaskoz/dailycodingproblem-go/issues/689)
+* [Day 348](https://github.com/vaskoz/dailycodingproblem-go/issues/693)
 * [Day 350](https://github.com/vaskoz/dailycodingproblem-go/issues/695)
 * [Day 352](https://github.com/vaskoz/dailycodingproblem-go/issues/699)
 * [Day 353](https://github.com/vaskoz/dailycodingproblem-go/issues/701)
diff --git a/day348/problem.go b/day348/problem.go
new file mode 100644
index 0000000..d0b207f
--- /dev/null
+++ b/day348/problem.go
@@ -0,0 +1,61 @@
+package day348
+
+// TernarySearchTree implements this tree:
+// https://en.wikipedia.org/wiki/Ternary_search_tree
+type TernarySearchTree struct {
+	letter              rune
+	left, middle, right *TernarySearchTree
+}
+
+// Insert adds a word to the TernarySearchTree.
+func (tst *TernarySearchTree) Insert(word string) {
+	if word == "" {
+		return
+	}
+
+	switch remaining, r := len(word), rune(word[0]); {
+	case tst.letter == 0 || tst.letter == r:
+		tst.letter = r
+		if remaining != 1 && tst.middle == nil {
+			tst.middle = &TernarySearchTree{}
+		}
+
+		tst = tst.middle
+		tst.Insert(word[1:])
+	case tst.letter > r:
+		if tst.left == nil {
+			tst.left = &TernarySearchTree{}
+		}
+
+		tst = tst.left
+		tst.Insert(word)
+	case tst.letter < r:
+		if tst.right == nil {
+			tst.right = &TernarySearchTree{}
+		}
+
+		tst = tst.right
+		tst.Insert(word)
+	}
+}
+
+// Search tries to find the word in the TernarySearchTree.
+func (tst *TernarySearchTree) Search(word string) bool {
+	if word == "" {
+		return true
+	} else if tst == nil {
+		return false
+	}
+
+	switch r := rune(word[0]); {
+	case tst.letter == r:
+		tst = tst.middle
+		return tst.Search(word[1:])
+	case tst.letter > r:
+		tst = tst.left
+		return tst.Search(word)
+	default: // case tst.letter < r:
+		tst = tst.right
+		return tst.Search(word)
+	}
+}


Test patch:
diff --git a/day348/problem_test.go b/day348/problem_test.go
new file mode 100644
index 0000000..94629c0
--- /dev/null
+++ b/day348/problem_test.go
@@ -0,0 +1,125 @@
+package day348
+
+import (
+	"testing"
+)
+
+// nolint
+var testcases = []struct {
+	words    []string
+	tree     *TernarySearchTree
+	notWords []string
+}{
+	{
+		[]string{"code", "cob", "be", "ax", "war", "we"},
+		&TernarySearchTree{
+			'c',
+			&TernarySearchTree{
+				'b',
+				&TernarySearchTree{
+					'a',
+					nil,
+					&TernarySearchTree{
+						'x', nil, nil, nil,
+					},
+					nil,
+				},
+				&TernarySearchTree{
+					'e', nil, nil, nil,
+				},
+				nil,
+			},
+			&TernarySearchTree{
+				'o',
+				nil,
+				&TernarySearchTree{
+					'd',
+					&TernarySearchTree{
+						'b',
+						nil,
+						nil,
+						nil,
+					},
+					&TernarySearchTree{
+						'e', nil, nil, nil,
+					},
+					nil,
+				},
+				nil,
+			},
+			&TernarySearchTree{
+				'w',
+				nil,
+				&TernarySearchTree{
+					'a',
+					nil,
+					&TernarySearchTree{
+						'r', nil, nil, nil,
+					},
+					&TernarySearchTree{
+						'e', nil, nil, nil,
+					},
+				},
+				nil,
+			},
+		},
+		[]string{"foo", "bar", "baz", "blahblahblah"},
+	},
+}
+
+func TestTernarySearchTree(t *testing.T) {
+	t.Parallel()
+
+	for _, tc := range testcases {
+		tree := &TernarySearchTree{}
+
+		for _, word := range tc.words {
+			tree.Insert(word)
+		}
+
+		if !equalTernaryTree(tree, tc.tree) {
+			t.Errorf("Expected trees to be equal but they aren't")
+		}
+
+		for _, word := range tc.words {
+			if !tree.Search(word) {
+				t.Errorf("Searching for word '%v' should return true", word)
+			}
+		}
+
+		for _, word := range tc.notWords {
+			if tree.Search(word) {
+				t.Errorf("Searching for notWord '%v' should return false", word)
+			}
+		}
+	}
+}
+
+func BenchmarkTernarySearchTree(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		for _, tc := range testcases {
+			tree := &TernarySearchTree{}
+
+			for _, word := range tc.words {
+				tree.Insert(word)
+			}
+
+			for _, word := range tc.words {
+				tree.Search(word)
+			}
+		}
+	}
+}
+
+func equalTernaryTree(a, b *TernarySearchTree) bool {
+	if a == nil && b == nil {
+		return true
+	} else if a != nil && b != nil {
+		return a.letter == b.letter &&
+			equalTernaryTree(a.left, b.left) &&
+			equalTernaryTree(a.middle, b.middle) &&
+			equalTernaryTree(a.right, b.right)
+	}
+
+	return false
+}