diff --git a/data-structures/dictionary/dictionary.go b/data-structures/dictionary/dictionary.go new file mode 100644 index 0000000..855c015 --- /dev/null +++ b/data-structures/dictionary/dictionary.go @@ -0,0 +1,118 @@ +// Package dictionary is word dictionary implementation using trie in go +package dictionary + +// trieNode saves trie structure +type trieNode struct { + childMap map[rune]*trieNode + isEnd bool +} + +// Dictionary struct contains data and methods +type Dictionary struct { + root *trieNode + wordCount int +} + +// NewDictionary creates new instance of dictionary +func NewDictionary() *Dictionary { + return &Dictionary{ + root: &trieNode{ + childMap: map[rune]*trieNode{}, + isEnd: false, + }, + wordCount: 0, + } +} + +// Size returns word count in dictionary +func (pr *Dictionary) Size() int { + return pr.wordCount +} + +// Insert inserts new word in dictionary +// returns false if this word already present +func (pr *Dictionary) Insert(word string) bool { + node := pr.root + for _, ch := range word { + if newNode, ok := node.childMap[ch]; ok { + node = newNode + } else { + node.childMap[ch] = &trieNode{ + childMap: map[rune]*trieNode{}, + isEnd: false, + } + node = node.childMap[ch] + } + } + if node.isEnd { + return false + } + node.isEnd = true + pr.wordCount++ + return true +} + +// InsertAll inserts all words in dictionary +// returns number of words actually inserted +func (pr *Dictionary) InsertAll(words []string) int { + var res = 0 + for _, word := range words { + if pr.Insert(word) { + res++ + } + } + return res +} + +// Retrieve retrieves all words in dictionary starting with prefix +func (pr *Dictionary) Retrieve(prefix string) []string { + node, depth := longestMatch(prefix, pr.root) + if depth != len(prefix) { + return []string{} + } + return allChild(prefix, node) +} + +// Contains checks if given word is in dictionary +func (pr *Dictionary) Contains(word string) bool { + node, depth := longestMatch(word, pr.root) + return depth == len(word) && node.isEnd +} + +// Delete deletes given word from dictionary +// returns false if word is'n in dictionary +func (pr *Dictionary) Delete(word string) bool { + node, depth := longestMatch(word, pr.root) + if depth == len(word) && node.isEnd { + node.isEnd = false + return true + } + return false +} + +func longestMatch(prefix string, root *trieNode) (*trieNode, int) { + node := root + var depth = 0 + for _, ch := range prefix { + if newNode, ok := node.childMap[ch]; ok { + node = newNode + depth++ + } else { + return node, depth + } + } + return node, depth +} + +func allChild(prefix string, node *trieNode) []string { + var res []string + if node.isEnd { + res = append(res, prefix) + } + + for ch, childNode := range node.childMap { + newStr := prefix + string(ch) + res = append(res, allChild(newStr, childNode)...) + } + return res +} diff --git a/data-structures/dictionary/dictionary_test.go b/data-structures/dictionary/dictionary_test.go new file mode 100644 index 0000000..23b0905 --- /dev/null +++ b/data-structures/dictionary/dictionary_test.go @@ -0,0 +1,142 @@ +package dictionary + +import ( + "testing" + + "github.com/RincLiu/Go-Algorithm/data-structures/dictionary" +) + +func TestEmptyDictionary(t *testing.T) { + var dict = dictionary.NewDictionary() + if dict.Size() != 0 { + t.Errorf("dictionary should be empty") + } + +} +func TestInsertingSame(t *testing.T) { + var dict = dictionary.NewDictionary() + + if dict.Insert("abc") == false { + t.Errorf("dictionary should be empty") + } + if dict.Insert("abc") == true { + t.Errorf("abc should already be in dictionary") + } + +} + +func TestRetrieve(t *testing.T) { + var dict = dictionary.NewDictionary() + + if dict.Insert("abc") == false { + t.Errorf("dictionary should be empty") + } + if dict.Insert("abb") == false { + t.Errorf("abb should't be in directory") + } + if dict.Insert("abcc") == false { + t.Errorf("abcc should't be in directory") + } + + if len(dict.Retrieve("a")) != 3 { + t.Errorf("there sould be 3 words starting with a") + } + if len(dict.Retrieve("ab")) != 3 { + t.Errorf("there sould be 3 words starting with ab") + } + if len(dict.Retrieve("abb")) != 1 { + t.Errorf("there sould be 1 words starting with abb") + } + if len(dict.Retrieve("gg")) != 0 { + t.Errorf("there sould be 0 words starting with gg") + } + +} + +func TestContains(t *testing.T) { + var dict = dictionary.NewDictionary() + if dict.Insert("abc") == false { + t.Errorf("dictionary should be empty") + } + if dict.Contains("abc") == false { + t.Errorf("abc should be in dictionary") + } + + var words = []string{ + "aaa", + "b", + "bbc", + "ccccdddd", + "d", + "ddaaaa", + "dddddddeeeeeeaaaa", + "eabbbb", + "eaccbbbbb", + } + + var containsTests = []struct { + word string // input + expected bool // expected result + }{ + {"aaa", true}, + {"ccccdddd", true}, + {"sdfdsf", false}, + {"eaccbbbba", false}, + {"", false}, + {"eabbbb", true}, + {"bb", false}, + } + dict.InsertAll(words) + + for _, tt := range containsTests { + actual := dict.Contains(tt.word) + if actual != tt.expected { + t.Errorf("contains(%s): expected %t, actual %t", tt.word, tt.expected, actual) + } + } + +} +func TestDelete(t *testing.T) { + var dict = dictionary.NewDictionary() + if dict.Insert("abc") == false { + t.Errorf("dictionary should be empty") + } + if dict.Delete("abc") == false { + t.Errorf("abc should be in dictionary") + } + + var words = []string{ + "aaa", + "b", + "bbc", + "ccccdddd", + "d", + "ddaaaa", + "dddddddeeeeeeaaaa", + "eabbbb", + "eaccbbbbb", + } + + var containsTests = []struct { + word string // input + expected bool // expected result + }{ + {"aaa", true}, + {"aaa", false}, + {"sdfdsf", false}, + {"eaccbbbba", false}, + {"", false}, + {"eabbbb", true}, + {"eabbbb", false}, + {"bb", false}, + } + dict.InsertAll(words) + + for _, tt := range containsTests { + actual := dict.Delete(tt.word) + if actual != tt.expected { + t.Errorf("delete(%s): expected %t, actual %t", tt.word, tt.expected, actual) + } + } + +}