Skip to content

Commit 10f0582

Browse files
Implement trie as a Map
Store the character trie as a Map instead of an Object. Enable ES6 types in TypeScript.
1 parent fc6f121 commit 10f0582

File tree

6 files changed

+62
-26
lines changed

6 files changed

+62
-26
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@babel/core": "^7.0.0-beta.54",
4242
"@babel/preset-env": "^7.0.0-beta.54",
4343
"@babel/preset-typescript": "^7.0.0-beta.54",
44+
"@types/es6-shim": "^0.31.37",
4445
"@types/jest": "^23.3.1",
4546
"@types/lodash": "^4.14.114",
4647
"husky": "^0.14.3",

src/types/iCharTree.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
type Char = string;
2-
interface ICharTrie extends Record<Char, CharNode> {}
3-
type CharNode = ICharTrie;
2+
interface ICharTrie extends Map<Char, ICharTrie> {
3+
[headChar: string]: ICharTrie;
4+
}

src/utils/trie.spec.ts

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,125 @@
11
import { build } from './trie';
22

3+
interface ICharTrieData {
4+
[prefix: string]: ICharTrieData;
5+
}
6+
7+
function convertObjectToTrie(trieData: ICharTrieData): ICharTrie {
8+
const trieDataPairs = Object.entries(trieData);
9+
10+
const trie = trieDataPairs.reduce(
11+
(map, [prefix, suffix]) => map.set(prefix, convertObjectToTrie(suffix)),
12+
new Map()
13+
);
14+
15+
return trie as ICharTrie;
16+
}
17+
318
describe('build', () => {
419
it('returns empty trie when no words', () => {
5-
expect(build([])).toEqual({});
20+
const expectedResult = convertObjectToTrie({});
21+
expect(build([])).toEqual(expectedResult);
622
});
723

824
it('returns the entirety of words when no common head', () => {
925
const words = ['bar', 'foo'];
10-
expect(build(words)).toEqual({
26+
const expectedResult = convertObjectToTrie({
1127
bar: {},
1228
foo: {},
1329
});
30+
expect(build(words)).toEqual(expectedResult);
1431
});
1532

1633
it('returns the entirety of repeated equal words', () => {
1734
const words = ['foo', 'foo', 'foo', 'foo'];
18-
expect(build(words)).toEqual({
35+
const expectedResult = convertObjectToTrie({
1936
foo: {},
2037
});
38+
expect(build(words)).toEqual(expectedResult);
2139
});
2240

2341
it('returns the entirety of multiple, repeated equal words', () => {
2442
const words = ['bar', 'bar', 'bar', 'bar', 'foo', 'foo', 'foo', 'foo'];
25-
expect(build(words)).toEqual({
43+
const expectedResult = convertObjectToTrie({
2644
bar: {},
2745
foo: {},
2846
});
47+
expect(build(words)).toEqual(expectedResult);
2948
});
3049

3150
it('returns an empty string for partial words', () => {
3251
const words = ['foo', 'foobar'];
33-
expect(build(words)).toEqual({
52+
const expectedResult = convertObjectToTrie({
3453
foo: { '': {}, bar: {} },
3554
});
55+
expect(build(words)).toEqual(expectedResult);
3656
});
3757

3858
it('returns the common head of two words', () => {
3959
const words = ['bar', 'baz'];
40-
expect(build(words)).toEqual({
60+
const expectedResult = convertObjectToTrie({
4161
ba: { r: {}, z: {} },
4262
});
63+
expect(build(words)).toEqual(expectedResult);
4364
});
4465

4566
it('returns multiple depths of partial words', () => {
4667
const words = ['foo', 'foobar', 'foobaz'];
47-
expect(build(words)).toEqual({
68+
const expectedResult = convertObjectToTrie({
4869
foo: { '': {}, ba: { r: {}, z: {} } },
4970
});
71+
expect(build(words)).toEqual(expectedResult);
5072
});
5173

5274
it('returns the common head of multiple words', () => {
5375
const words = ['bar', 'baz', 'quux', 'quuz'];
54-
expect(build(words)).toEqual({
76+
const expectedResult = convertObjectToTrie({
5577
ba: { r: {}, z: {} },
5678
quu: { x: {}, z: {} },
5779
});
80+
expect(build(words)).toEqual(expectedResult);
5881
});
5982

6083
it('preserves leading whitespace', () => {
6184
const words = [' foo', 'foo'];
62-
expect(build(words)).toEqual({
85+
const expectedResult = convertObjectToTrie({
6386
' foo': {},
6487
foo: {},
6588
});
89+
expect(build(words)).toEqual(expectedResult);
6690
});
6791

6892
it('preserves trailing whitespace', () => {
6993
const words = ['foo ', 'foo'];
70-
expect(build(words)).toEqual({
94+
const expectedResult = convertObjectToTrie({
7195
foo: { '': {}, ' ': {} },
7296
});
97+
expect(build(words)).toEqual(expectedResult);
7398
});
7499

75100
it('preserves mid-word whitespace', () => {
76101
const words = ['foo bar', 'foobar'];
77-
expect(build(words)).toEqual({
102+
const expectedResult = convertObjectToTrie({
78103
foo: { ' bar': {}, bar: {} },
79104
});
105+
expect(build(words)).toEqual(expectedResult);
80106
});
81107

82108
it('is case-sensitive with string heads', () => {
83109
const words = ['Foo', 'foo'];
84-
expect(build(words)).toEqual({
110+
const expectedResult = convertObjectToTrie({
85111
Foo: {},
86112
foo: {},
87113
});
114+
expect(build(words)).toEqual(expectedResult);
88115
});
89116

90117
it('is case-sensitive with string tails', () => {
91118
const words = ['foo', 'foO'];
92-
expect(build(words)).toEqual({
119+
const expectedResult = convertObjectToTrie({
93120
fo: { o: {}, O: {} },
94121
});
122+
expect(build(words)).toEqual(expectedResult);
95123
});
96124

97125
it('handles complex words', () => {
@@ -116,7 +144,7 @@ describe('build', () => {
116144
'Oklahoma',
117145
'Oregon',
118146
];
119-
expect(build(words)).toEqual({
147+
const expectedResult = convertObjectToTrie({
120148
M: {
121149
a: { ine: {}, ryland: {}, ssachusetts: {} },
122150
i: { chigan: {}, nnesota: {}, ss: { issippi: {}, ouri: {} } },
@@ -132,5 +160,6 @@ describe('build', () => {
132160
},
133161
O: { hio: {}, klahoma: {}, regon: {} },
134162
});
163+
expect(build(words)).toEqual(expectedResult);
135164
});
136165
});

src/utils/trie.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { keys, map, partition, uniq } from 'lodash';
1+
import { partition, uniq } from 'lodash';
22

3-
const leafNode: CharNode = {};
3+
const leafNode = new Map() as ICharTrie;
44

55
/**
66
* Arrange a head character and its suffixes into a trie.
@@ -13,13 +13,12 @@ const leafNode: CharNode = {};
1313
* @returns A character trie with tailGroup branching from headChar
1414
*/
1515
function mergeGroups(headChar: Char, tailGroup: ICharTrie): ICharTrie {
16-
const tails = keys(tailGroup);
17-
if (tails.length > 1) {
18-
return { [headChar]: tailGroup };
16+
if (tailGroup.size !== 1) {
17+
return new Map([[headChar, tailGroup]]) as ICharTrie;
1918
}
2019

21-
const onlyTail = tails[0];
22-
return { [headChar + onlyTail]: tailGroup[onlyTail] };
20+
const [onlyTail, onBranch] = tailGroup.entries().next().value;
21+
return new Map([[headChar + onlyTail, onBranch]]) as ICharTrie;
2322
}
2423

2524
/**
@@ -39,19 +38,19 @@ function buildUnique(words: string[]): ICharTrie {
3938
// End of the target word reached. Include an empty string to signify that
4039
// a word ends at this spot, and group any remaining words in the trie.
4140
const [, nonEmptyWords] = partition(words, word => word === '');
42-
return { '': leafNode, ...build(nonEmptyWords) };
41+
return new Map([['', leafNode], ...build(nonEmptyWords)]) as ICharTrie;
4342
}
4443

4544
// Begin a new trie containing all words starting with the same letter as wordToMatch
4645
const charToMatch = wordToMatch[0];
4746
const [wordsMatched, wordsMissed] = partition(words, ['[0]', charToMatch]);
4847

49-
const tailsMatched = map(wordsMatched, word => word.substring(1));
48+
const tailsMatched = wordsMatched.map(word => word.substring(1));
5049
const tailsMatchedGrouped = build(tailsMatched);
5150

5251
const groupWithChildren = mergeGroups(charToMatch, tailsMatchedGrouped);
5352

54-
return { ...groupWithChildren, ...build(wordsMissed) };
53+
return new Map([...groupWithChildren, ...build(wordsMissed)]) as ICharTrie;
5554
}
5655

5756
/** @borrows buildUnique as build */

tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"allowJs": true,
44
"allowSyntheticDefaultImports": true,
55
"baseUrl": "./",
6+
"downlevelIteration": true,
67
"rootDir": "./",
8+
"lib": ["es2017"],
79
"module": "es2015",
810
"noEmit": true,
911
"noImplicitAny": true,

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,10 @@
543543
lodash "^4.17.5"
544544
to-fast-properties "^2.0.0"
545545

546+
"@types/es6-shim@^0.31.37":
547+
version "0.31.37"
548+
resolved "https://registry.yarnpkg.com/@types/es6-shim/-/es6-shim-0.31.37.tgz#d01fa9b61a6692e57387dde5483b0255659ba70f"
549+
546550
"@types/jest@^23.3.1":
547551
version "23.3.1"
548552
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf"

0 commit comments

Comments
 (0)