Golang program to implement a trie with compressed nodes


Tries are tree-like data structures used for efficient storage and retrieval of strings, making them invaluable for tasks like autocomplete, dictionary implementation, and pattern matching. The compressed node technique optimises space usage by merging common prefixes among nodes, resulting in a more memory-efficient Trie. In this article, we will explore the implementation of a Trie with Compressed Nodes in Golang using two methods to implement the Trie with Compressed Nodes, the first method utilises maps, and the second method uses arrays.

Explanation

A compressed trie is a trie data structure that saves the space by combining consecutive nodes with a single branch into a compressed node. In the below representation the root node [c] is same for all the branches. In the below structure the “card’ and “care” both represent the common prefix “car”. Instead of separate node for each character a single compressed node represent this shared prefix.

     c
    / \
  ar   o
  /     |
 red     n
         |
         d

Syntax

type TrieNode struct{ children map[rune]*TrieNode; isEnd bool }

The Syntax TrieNode struct contains a map named "children," which stores the references to child nodes using the rune (character) as the key. It allows efficient mapping between characters and child nodes, making it suitable for compressed trie nodes. The "isEnd" boolean flag is used to indicate if a word ends at the current node.

Algorithm

  • Create a struct named TrieNode to represent each node in the trie. Each TrieNode should have a map called "children" to store child nodes with keys as characters and values as pointers to TrieNode.

  • Include a boolean variable "isEnd" to indicate whether the current node represents the end of a word.

  • Initialise an empty root node as the starting point of the trie.

  • After iterating through all characters, mark the last node as the end of the word by setting "isEnd" to true.

  • To search for a word in the trie, traverse through the characters of the word starting from the root node. If at any point, a character's child node is not found or the end of the word is reached but "isEnd" is false, the word is not present in the trie.

  • To delete a word from the trie, search for the word as described earlier.

Example 1

In this example, we will implement a Trie data structure with compressed nodes using map. Here we create a Trie data structure, insert words into it, and perform search operations to check if specific words exist in the trie. This approach saves memory and reduces the overall trie size.

package main

import "fmt"

type TrieNode struct {
	children map[rune]*TrieNode
	isEnd    bool
}

type Trie struct {
	root *TrieNode
}

func NewTrie() *Trie {
	return &Trie{
		root: &TrieNode{
			children: make(map[rune]*TrieNode),
			isEnd: false,
		},
	}
}

func (t *Trie) Insert(word string) {
	node := t.root
	for _, ch := range word {
		if _, ok := node.children[ch]; !ok {
			node.children[ch] = &TrieNode{
				children: make(map[rune]*TrieNode),
				isEnd: false,
			}
		}
		node = node.children[ch]
	}
	node.isEnd = true
}

func (t *Trie) Search(word string) bool {
	node := t.root
	for _, ch := range word {
		if _, ok := node.children[ch]; !ok {
			return false
		}
		node = node.children[ch]
	}
	return node.isEnd
}

func main() {
	trie := NewTrie()

	trie.Insert("apple")
	trie.Insert("app")
	trie.Insert("banana")

	fmt.Println("Search 'apple':", trie.Search("apple"))   
	fmt.Println("Search 'app':", trie.Search("app"))       
	fmt.Println("Search 'banana':", trie.Search("banana")) 
	fmt.Println("Search 'orange':", trie.Search("orange")) 
}

Output

Search 'apple': true
Search 'app': true
Search 'banana': true
Search 'orange': false

Example 2

In this example, we will implement a Trie data structure with compressed nodes using arrays. Here we use the compressed nodes approach, but instead of a map, we use an array of pointers to TrieNode to represent a node's children. We create a Trie data structure, insert words into it, and perform search operations to check if specific words exist in the trie. This approach provides faster access and slightly reduced memory overhead compared to the map-based approach.

package main

import "fmt"

const alphabetSize = 26

type TrieNode struct {
	children [alphabetSize]*TrieNode
	isEnd    bool
}

type Trie struct {
	root *TrieNode
}

func NewTrie() *Trie {
	return &Trie{
		root: &TrieNode{},
	}
}

func (t *Trie) Insert(word string) {
	node := t.root
	for _, ch := range word {
		index := ch - 'a'
		if node.children[index] == nil {
			node.children[index] = &TrieNode{}
		}
		node = node.children[index]
	}
	node.isEnd = true
}

func (t *Trie) Search(word string) bool {
	node := t.root
	for _, ch := range word {
		index := ch - 'a'
		if node.children[index] == nil {
			return false
		}
		node = node.children[index]
	}
	return node.isEnd
}

func main() {
	trie := NewTrie()

	trie.Insert("apple")
	trie.Insert("app")
	trie.Insert("banana")

	fmt.Println("Search 'apple':", trie.Search("apple"))   
	fmt.Println("Search 'app':", trie.Search("app"))      
	fmt.Println("Search 'banana':", trie.Search("banana")) 
	fmt.Println("Search 'orange':", trie.Search("orange")) 
}

Output

Search 'apple': true
Search 'app': true
Search 'banana': true
Search 'orange': false

Real life implementation

Autocomplete and Search Suggestions

Tries with compressed nodes are often used in search engines and text prediction systems to provide autocomplete and search suggestion features. As users type, the system quickly retrieves suggestions based on the prefix they've entered. For example, when you start typing a search query in Google, it suggests relevant search terms in real time. A Trie efficiently stores a large set of words, and its compressed nodes help reduce memory usage while maintaining fast retrieval times for autocomplete suggestions.

Spell Checking and Correction

Tries with compressed nodes are useful in spelling correction and checking applications. They can quickly identify if a word is present in a dictionary and offer suggestions for correcting misspelled words. For instance, word processing software like Microsoft Word uses similar techniques to underline misspelled words and suggest corrections. The Trie structure allows for quick searching and suggestions, while the compressed nodes keep memory requirements in check, enabling efficient real-time spell checking and correction.

Conclusion

A compressed trie is a kind of data structure that saves the space available by merging common branches into compressed nodes. In this article we have looked at how we can implement a Trie with Compressed Nodes in Golang, here we have used two different examples, The first one uses maps to represent the compressed nodes, while the second example utilises arrays for the same purpose. The unique aspect of compressed nodes is their ability to save memory by merging common prefixes among nodes, this results in a more memory-efficient Trie.

Updated on: 05-Sep-2023

75 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements