Skip to content

Commit

Permalink
rename isLeaf, make trie case insensitive, correct limit value, add u…
Browse files Browse the repository at this point in the history
…nit tests for update and use timing
  • Loading branch information
Karim-30 committed Oct 15, 2022
1 parent 6f1b47c commit af2dff7
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ const CONST = {
REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500,
TOOLTIP_SENSE: 1000,
SPINNER_TIMEOUT: 15 * 1000,
TRIE_INITIALIZATION: 'trie_initialization',
},
PRIORITY_MODE: {
GSD: 'gsd',
Expand Down
5 changes: 5 additions & 0 deletions src/libs/EmojiTrie.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import emojis from '../../assets/emojis';
import Trie from './Trie';
import Timing from './actions/Timing';
import CONST from '../CONST';

Timing.start(CONST.TIMING.TRIE_INITIALIZATION);

// Create a Trie object
const emojisTrie = new Trie();
Expand Down Expand Up @@ -27,5 +31,6 @@ for (let i = 0; i < emojis.length; i++) {
}
}
}
Timing.end(CONST.TIMING.TRIE_INITIALIZATION);

export default emojisTrie;
4 changes: 3 additions & 1 deletion src/libs/EmojiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ function addToFrequentlyUsedEmojis(frequentlyUsedEmojis, newEmoji) {
function replaceEmojis(text) {
let newText = text;
const emojiData = text.match(CONST.REGEX.EMOJI_NAME);
if (!emojiData || emojiData.length === 0) { return text; }
if (!emojiData || emojiData.length === 0) {
return text;
}
for (let i = 0; i < emojiData.length; i++) {
const checkEmoji = emojisTrie.search(emojiData[i].slice(1, -1));
if (checkEmoji && checkEmoji.metaData.code) {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Trie/TrieNode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class TrieNode {
constructor() {
this.children = {};
this.isLeaf = false;
this.isEndOfWord = false;
this.metaData = {};
}
}
Expand Down
40 changes: 23 additions & 17 deletions src/libs/Trie/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ class Trie {
* @param {Boolean} [allowEmptyWords] - empty word doesn't have any char, you shouldn't pass a true value for it because we are disallowing adding an empty word
*/
add(word, metaData = {}, node = this.root, allowEmptyWords = false) {
const newWord = word.toLowerCase();
const newNode = node;
if (word.length === 0 && !allowEmptyWords) {
if (newWord.length === 0 && !allowEmptyWords) {
throw new Error('Cannot insert empty word into Trie');
}
if (word.length === 0) {
newNode.isLeaf = true;
if (newWord.length === 0) {
newNode.isEndOfWord = true;
newNode.metaData = metaData;
return;
}
if (!newNode.children[word[0]]) {
newNode.children[word[0]] = new TrieNode();
this.add(word.substring(1), metaData, newNode.children[word[0]], true);
if (!newNode.children[newWord[0]]) {
newNode.children[newWord[0]] = new TrieNode();
this.add(newWord.substring(1), metaData, newNode.children[newWord[0]], true);
}
this.add(word.substring(1), metaData, newNode.children[word[0]], true);
this.add(newWord.substring(1), metaData, newNode.children[newWord[0]], true);
}

/**
Expand All @@ -36,16 +37,17 @@ class Trie {
* @returns {Object|null} – the node for the word if it's found, or null if it's not found
*/
search(word) {
let newWord = word;
let newWord = word.toLowerCase();
let node = this.root;
while (newWord.length > 1) {
if (!node.children[newWord[0]]) {
return null;
}
node = node.children[newWord[0]];

newWord = newWord.substring(1);
}
return node.children[newWord] && node.children[newWord].isLeaf ? node.children[newWord] : null;
return node.children[newWord] && node.children[newWord].isEndOfWord ? node.children[newWord] : null;
}

/**
Expand All @@ -54,9 +56,12 @@ class Trie {
* @param {Object} metaData
*/
update(word, metaData) {
let newWord = word;
let newWord = word.toLowerCase();
let node = this.root;
while (newWord.length > 1) {
if (!node.children[newWord[0]]) {
throw new Error('Word does not exist in the Trie');
}
node = node.children[newWord[0]];
newWord = newWord.substring(1);
}
Expand All @@ -69,13 +74,14 @@ class Trie {
* @param {Number} [limit] - matching words limit
* @returns {Array}
*/
getAllMatchingWords(substr, limit = 4) {
getAllMatchingWords(substr, limit = 5) {
const newSubstr = substr.toLowerCase();
let node = this.root;
let prefix = '';
for (let i = 0; i < substr.length; i++) {
prefix += substr[i];
if (node.children[substr[i]]) {
node = node.children[substr[i]];
for (let i = 0; i < newSubstr.length; i++) {
prefix += newSubstr[i];
if (node.children[newSubstr[i]]) {
node = node.children[newSubstr[i]];
} else {
return [];
}
Expand All @@ -93,10 +99,10 @@ class Trie {
*/
getChildMatching(node, prefix, limit, words = []) {
const matching = words;
if (matching.length > limit) {
if (matching.length >= limit) {
return matching;
}
if (node.isLeaf) {
if (node.isEndOfWord) {
matching.unshift({name: prefix, metaData: node.metaData});
}
const children = _.keys(node.children);
Expand Down
22 changes: 19 additions & 3 deletions tests/unit/TrieTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('Trie', () => {
wordTrie.add('rofl', {code: '🤣'});
expect(wordTrie.search('eyes')).toBeNull();
expect(wordTrie.search('joy').metaData).toEqual({code: '😂'});
expect(wordTrie.search('gRiN').metaData).toEqual({code: '😁'});
});

it('Test finding all leaf nodes starting with a substring', () => {
Expand All @@ -18,11 +19,12 @@ describe('Trie', () => {
wordTrie.add('Robertson', {code: '👨🏽', suggestions: []});
wordTrie.add('Rock', {code: '👨🏼', suggestions: []});
const expected = [
{name: 'Rock', metaData: {code: '👨🏼', suggestions: []}},
{name: 'Robertson', metaData: {code: '👨🏽', suggestions: []}},
{name: 'Robert', metaData: {code: '👨🏾', suggestions: []}},
{name: 'rock', metaData: {code: '👨🏼', suggestions: []}},
{name: 'robertson', metaData: {code: '👨🏽', suggestions: []}},
{name: 'robert', metaData: {code: '👨🏾', suggestions: []}},
];
expect(wordTrie.getAllMatchingWords('Ro')).toEqual(expected);
expect(wordTrie.getAllMatchingWords('ro')).toEqual(expected);
});

it('Test finding only the first 5 matching words', () => {
Expand Down Expand Up @@ -58,4 +60,18 @@ describe('Trie', () => {
wordTrie.add('');
}).toThrow('Cannot insert empty word into Trie');
});

it('Test updating a Trie node', () => {
const wordTrie = new Trie();
wordTrie.add('John', {code: '👨🏼'});
wordTrie.update('John', {code: '👨🏻'});
expect(wordTrie.search('John').metaData).toEqual({code: '👨🏻'});
});

it('Test throwing an error when try to update a word that does not exist in the Trie.', () => {
const wordTrie = new Trie();
expect(() => {
wordTrie.update('smile');
}).toThrow('Word does not exist in the Trie');
});
});

0 comments on commit af2dff7

Please sign in to comment.