diff --git a/lib/trees/binary_tree_adt.dart b/lib/trees/binary_tree_adt.dart index 8df9009..3cbd788 100644 --- a/lib/trees/binary_tree_adt.dart +++ b/lib/trees/binary_tree_adt.dart @@ -38,21 +38,21 @@ abstract class BinaryTreeADT @override void nullify() => root = null; - @override + /// In Order Traversal. List inOrder() { var result = []; _inOrder(root, result); return result; } - @override + /// PostOrder Traversal. List postOrder() { var result = []; _postOrder(root, result); return result; } - @override + /// PreOrder Traversal. List preOrder() { var result = []; _preOrder(root, result); diff --git a/lib/trees/tree_adt.dart b/lib/trees/tree_adt.dart index 14f3aaf..0599743 100644 --- a/lib/trees/tree_adt.dart +++ b/lib/trees/tree_adt.dart @@ -28,15 +28,6 @@ abstract class TreeADT { /// Deletes [value] from the tree and updates it's [root]. void delete(V value); - /// In Order Traversal. - List inOrder(); - /// Empty the tree. void nullify(); - - /// PostOrder Traversal. - List postOrder(); - - /// PreOrder Traversal. - List preOrder(); } diff --git a/lib/trie/trie.dart b/lib/trie/trie.dart new file mode 100644 index 0000000..523f94c --- /dev/null +++ b/lib/trie/trie.dart @@ -0,0 +1,159 @@ +/// Trie is an ordered tree data structure used to store a dynamic set +/// or associative array where the keys are usually strings. +class Trie { + /// Root of the trie. + TrieNode root; + + /// Separates the value into it's components. + final Function splitter; + + List _components; + + /// Initialises trie with custom set of values. + Trie(Set components, this.splitter) { + _components = [...components]; + } + + /// Generates trie of lower-case alphabets. + Trie.ofAlphabets() + : this({...List.generate(26, (i) => String.fromCharCode(97 + i))}, + (value) => value.split('')); + + /// Returns the set of [components] that make up a value. + Set get components => {..._components}; + + /// Tests if this trie is empty. + bool get isEmpty => root?.children == null ? true : false; + + /// Adds a [value] to the trie. + void add(V value) { + var list = _split(value); + if (isEmpty) { + root ??= TrieNode({..._components}); + } + _add(root, list); + } + + /// Checks if [value] is contained in the trie. + bool contains(V value) { + var list = _split(value); + return isEmpty ? false : _contains(root, list); + } + + /// Deletes [value] from the trie. + void delete(V value) { + var list = _split(value); + var returnValue = _delete(root, list); + returnValue ?? nullify(); + } + + /// Empty the trie. + void nullify() => root?.children = null; + + /// Traverses the path following [value] + /// and marks `node.isValue` to true at end. + void _add(TrieNode node, List value) { + if (value.isEmpty) { + node.isValue = true; + return; + } + var path = _indexOf(value.first); + value = value.sublist(1); + + if (node.children[path] == null) { + node.children[path] = TrieNode(components); + } + + _add(node.children[path], value); + } + + bool _contains(TrieNode node, List value) { + if (value.isEmpty) { + return node.isValue; + } + var path = _indexOf(value.first); + value = value.sublist(1); + + if (node.children[path] != null) { + return _contains(node.children[path], value); + } else { + return false; + } + } + + /// Traverses the path following [value] and marks + /// `node.isValue` to `false` at end. Deletes values eagerly i.e. + /// cleans up any parent nodes that are no longer necessary. + TrieNode _delete(TrieNode node, List value) { + if (value.isEmpty) { + // In case trie is empty and an empty value is passed. + if (node == null) return null; + + node.isValue = false; + + // Checks all the children. If null, then deletes the node. + var allNull = true; + for (var child in node.children) { + if (child != null) { + allNull = false; + break; + } + } + return allNull ? null : node; + } + + // In case trie is empty and some value is passed. + if (node == null) return null; + + var path = _indexOf(value.first); + value = value.sublist(1); + + // Path to value doesn't exist. + if (node.children[path] == null) { + return node; + } + + node.children[path] = _delete(node.children[path], value); + + // Delete node if all children are null. + if (node.children[path] == null) { + var allNull = true; + for (var child in node.children) { + if (child != null) { + allNull = false; + break; + } + } + return allNull ? null : node; + } + + return node; + } + + /// Returns index, which represents the [component] in `node.children`. + int _indexOf(V component) => _components.indexOf(component); + + List _split(V value) { + List list = splitter(value); + for (var component in list) { + // TODO: Implement an error class instead. + if (!components.contains(component)) throw ('$component ∉ $components'); + } + return list; + } +} + +/// Node of the trie, has connection to the set of components as [children]. +class TrieNode { + /// If this node represents a value in the trie. + bool isValue = false; + + /// Connection to [children]. + List children; + + /// Initializes the node to have as many [children] + /// as there are components in the trie. + TrieNode(Set components) { + children = List(components.length); + } +} diff --git a/test/trie/trie_test.dart b/test/trie/trie_test.dart new file mode 100644 index 0000000..cf9ca80 --- /dev/null +++ b/test/trie/trie_test.dart @@ -0,0 +1,63 @@ +import 'package:test/test.dart'; +import 'package:algorithms/trie/trie.dart'; + +void main() { + Trie emptyTrie, trie, customTrie; + + setUp(() { + emptyTrie = Trie.ofAlphabets(); + + trie = Trie.ofAlphabets(); + trie.add('algorithms'); + + customTrie = Trie( + {'a', 'b', 'f', 'o', 'r', 'z'}, (value) => value.toString().split('')); + customTrie.add('foo'); + }); + + test('Components', () { + expect(trie.components, + equals({...List.generate(26, (i) => String.fromCharCode(122 - i))})); + + expect(customTrie.components, equals({'f', 'o', 'b', 'a', 'r', 'z'})); + }); + + test('Empty trie', () { + expect(emptyTrie.isEmpty, isTrue); + expect(trie.isEmpty, isFalse); + }); + + test('Add value', () { + trie.add('hi'); + expect(trie.root.children[7].children[8].isValue, isTrue); + + customTrie.add('bar'); + expect(customTrie.root.children[1].children[0].children[4].isValue, isTrue); + + customTrie.add('baz'); + expect(customTrie.root.children[1].children[0].children[5].isValue, isTrue); + }); + + test('Contains value', () { + expect(emptyTrie.contains('test'), isFalse); + + expect(trie.contains('algorithm'), isFalse); + expect(trie.contains('algorithms'), isTrue); + + expect(customTrie.contains('boar'), isFalse); + expect(customTrie.contains('foo'), isTrue); + }); + + test('Delete value', () { + emptyTrie.delete(''); + emptyTrie.delete('test'); + + expect(trie.contains('algorithms'), isTrue); + trie.delete('algorithms'); + expect(trie.contains('algorithms'), isFalse); + + expect(customTrie.contains('foo'), isTrue); + customTrie.delete('foo'); + expect(customTrie.contains('foo'), isFalse); + }); +}