fix bug in trie
This commit is contained in:
parent
075cc1244f
commit
fe6e86e1a4
2 changed files with 112 additions and 24 deletions
|
|
@ -6,6 +6,21 @@ export const emptyTrie = {
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// invariant: the keys of a trie node should always be sorted
|
||||||
|
export const isProperlySorted = trie => {
|
||||||
|
for (let i=0; i<trie.children.length-1; i++) {
|
||||||
|
if (trie.children[i] >= trie.children[i+1]) {
|
||||||
|
return false; // not properly sorted!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [_, node] of trie.children) {
|
||||||
|
if (!isProperlySorted(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// find maximal common prefix, and whether string A is smaller than B
|
// find maximal common prefix, and whether string A is smaller than B
|
||||||
const commonPrefix = (strA, strB) => {
|
const commonPrefix = (strA, strB) => {
|
||||||
let i=0
|
let i=0
|
||||||
|
|
@ -41,6 +56,14 @@ const __binarySearch = (ls, key, min, max) => {
|
||||||
return __binarySearch(ls, key, middle+1, max);
|
return __binarySearch(ls, key, middle+1, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const check = trie => {
|
||||||
|
// uncomment if you think shit is broken:
|
||||||
|
// if (!isProperlySorted(trie)) {
|
||||||
|
// throw new Error('not properly sorted!')
|
||||||
|
// }
|
||||||
|
return trie;
|
||||||
|
}
|
||||||
|
|
||||||
// insert (key,value) into trie.
|
// insert (key,value) into trie.
|
||||||
export const insert = trie => key => value => {
|
export const insert = trie => key => value => {
|
||||||
if (key.length === 0) {
|
if (key.length === 0) {
|
||||||
|
|
@ -54,54 +77,59 @@ export const insert = trie => key => value => {
|
||||||
const [insertPos, prefix] = binarySearch(trie.children, key);
|
const [insertPos, prefix] = binarySearch(trie.children, key);
|
||||||
if (insertPos === trie.children.length) {
|
if (insertPos === trie.children.length) {
|
||||||
// insert node at end
|
// insert node at end
|
||||||
return {
|
return check({
|
||||||
value: trie.value,
|
value: trie.value,
|
||||||
children: [
|
children: [
|
||||||
...trie.children,
|
...trie.children,
|
||||||
[key, {value, children:[]}],
|
[key, {value, children:[]}],
|
||||||
],
|
],
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
if (prefix.length === 0) {
|
if (prefix.length === 0) {
|
||||||
// nothing in common...
|
// nothing in common...
|
||||||
// insert new node into children
|
// insert new node into children
|
||||||
return {
|
return check({
|
||||||
value: trie.value,
|
value: trie.value,
|
||||||
children: trie.children.toSpliced(
|
children: trie.children.toSpliced(
|
||||||
insertPos, // insert position
|
insertPos, // insert position
|
||||||
0, // delete nothing
|
0, // delete nothing
|
||||||
[key, {value, children:[]}],
|
[key, {value, children:[]}],
|
||||||
),
|
),
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const [haveKey, haveChildNode] = trie.children[insertPos];
|
const [haveKey, haveChildNode] = trie.children[insertPos];
|
||||||
if (prefix.length === haveKey.length) {
|
if (prefix.length === haveKey.length) {
|
||||||
// recurse
|
// recurse
|
||||||
return {
|
return check({
|
||||||
value: trie.value,
|
value: trie.value,
|
||||||
children: trie.children.with(
|
children: trie.children.with(
|
||||||
insertPos, // position to update
|
insertPos, // position to update
|
||||||
[haveKey, insert(haveChildNode)(key.slice(prefix.length))(value)],
|
[haveKey, insert(haveChildNode)(key.slice(prefix.length))(value)],
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, split entry:
|
// otherwise, split entry:
|
||||||
const havePostFix = haveKey.slice(prefix.length);
|
const havePostFix = haveKey.slice(prefix.length);
|
||||||
const postFix = key.slice(prefix.length);
|
const postFix = key.slice(prefix.length);
|
||||||
return {
|
return check({
|
||||||
value: trie.value,
|
value: trie.value,
|
||||||
children: trie.children.with(
|
children: trie.children.with(
|
||||||
insertPos, // position to update
|
insertPos, // position to update
|
||||||
[prefix, {
|
[prefix, {
|
||||||
children: [
|
children: (havePostFix < postFix)
|
||||||
|
? [
|
||||||
|
[havePostFix, haveChildNode],
|
||||||
|
[postFix, {value, children: []}],
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
[postFix, {value, children: []}],
|
||||||
[havePostFix, haveChildNode],
|
[havePostFix, haveChildNode],
|
||||||
[postFix, {value, children: []}
|
|
||||||
],
|
],
|
||||||
]}],
|
}],
|
||||||
),
|
),
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// given a prefix, return a string X such that prefix+X is a possibly larger prefix for the same entries as the original prefix.
|
// given a prefix, return a string X such that prefix+X is a possibly larger prefix for the same entries as the original prefix.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
import { pretty } from "../lib/util/pretty.js";
|
import * as assert from "node:assert";
|
||||||
import { insert, emptyTrie, growPrefix, suggest } from "../lib/util/trie.js";
|
|
||||||
|
import { module2Env } from "../lib/environment/env.js";
|
||||||
|
import { ModuleStd } from "../lib/stdlib.js";
|
||||||
|
import { pretty } from "../lib/util/pretty.js";
|
||||||
|
import { emptyTrie, growPrefix, insert, isProperlySorted, suggest } from "../lib/util/trie.js";
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// Setup TRIE 1 ...
|
||||||
|
|
||||||
// insertion
|
|
||||||
const with1Item = insert(emptyTrie)('abba')('dancing queen');
|
const with1Item = insert(emptyTrie)('abba')('dancing queen');
|
||||||
console.log(pretty(with1Item));
|
console.log(pretty(with1Item));
|
||||||
const with2Items = insert(with1Item)('aboriginal')('australia');
|
const with2Items = insert(with1Item)('aboriginal')('australia');
|
||||||
|
|
@ -19,14 +26,67 @@ console.log(pretty(with7Items));
|
||||||
const with8Items = insert(with7Items)('')('hi!');
|
const with8Items = insert(with7Items)('')('hi!');
|
||||||
console.log(pretty(with8Items));
|
console.log(pretty(with8Items));
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// Setup TRIE 2 ...
|
||||||
|
|
||||||
|
const bigTrie = module2Env(ModuleStd).name2dyn;
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// Test...
|
||||||
|
|
||||||
// grow key (for auto-complete)
|
// grow key (for auto-complete)
|
||||||
console.log(growPrefix(with6Items)("a")); // b
|
assert.equal(growPrefix(with6Items)("a" ), "b" );
|
||||||
console.log(growPrefix(with6Items)("ab")); // (empty string)
|
assert.equal(growPrefix(with6Items)("ab" ), "" );
|
||||||
console.log(growPrefix(with6Items)("abb")); // a
|
assert.equal(growPrefix(with6Items)("abb"), "a" );
|
||||||
console.log(growPrefix(with6Items)("f")); // ood
|
assert.equal(growPrefix(with6Items)("f" ), "ood" );
|
||||||
console.log(growPrefix(with6Items)("abo")); // riginal
|
assert.equal(growPrefix(with6Items)("abo"), "riginal");
|
||||||
|
|
||||||
// suggest (also for auto-complete)
|
// suggest (also for auto-complete)
|
||||||
console.log(suggest(with8Items)("a")(3)); // 'ab', 'abba', 'aboriginal'
|
assert.deepEqual(
|
||||||
console.log(suggest(with8Items)("a")(4)); // 'ab', 'abba', 'aboriginal', 'aboriginally'
|
suggest(with8Items)("a")(3),
|
||||||
console.log(suggest(with8Items)("a")(5)); // 'ab', 'abba', 'aboriginal', 'aboriginally', 'absent'
|
[
|
||||||
|
[ 'ab', 'yup' ],
|
||||||
|
[ 'abba', 'dancing queen' ],
|
||||||
|
[ 'aboriginal', 'australia' ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
suggest(with8Items)("a")(4),
|
||||||
|
[
|
||||||
|
[ 'ab', 'yup' ],
|
||||||
|
[ 'abba', 'dancing queen' ],
|
||||||
|
[ 'aboriginal', 'australia' ],
|
||||||
|
[ 'aboriginally', '??' ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
suggest(with8Items)("a")(5),
|
||||||
|
[
|
||||||
|
[ 'ab', 'yup' ],
|
||||||
|
[ 'abba', 'dancing queen' ],
|
||||||
|
[ 'aboriginal', 'australia' ],
|
||||||
|
[ 'aboriginally', '??' ],
|
||||||
|
[ 'absent', 'not here' ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
suggest(with8Items)("a")(15),
|
||||||
|
[
|
||||||
|
[ 'ab', 'yup' ],
|
||||||
|
[ 'abba', 'dancing queen' ],
|
||||||
|
[ 'aboriginal', 'australia' ],
|
||||||
|
[ 'aboriginally', '??' ],
|
||||||
|
[ 'absent', 'not here' ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
isProperlySorted(with8Items),
|
||||||
|
true,
|
||||||
|
"trie should always be properly sorted!"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
isProperlySorted(bigTrie),
|
||||||
|
true,
|
||||||
|
"trie should always be properly sorted!"
|
||||||
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue