diff --git a/src/dictionary-tree.mjs b/src/dictionary-tree.mjs index fbf9a37e8ed66a347e0a23ae3ac5d139f18a960a..291a4e7e7ead1bf7249b895a9fbe568cc15d5fac 100644 --- a/src/dictionary-tree.mjs +++ b/src/dictionary-tree.mjs @@ -5,12 +5,13 @@ export function buildTreeStruct(monoLineString) { const stringAsArray = monoLineString.split(''); const rawTree = leftParser(stringAsArray); const outOfScope = stringAsArray.length; - if (outOfScope) throw `fail to build tree from : "${monoLineString}" parsed: ${JSON.stringify(rawTree)} unparsed/failed: ${stringAsArray.join('')}`; + if (outOfScope) throw new Error(`fail to build tree from : "${monoLineString}" parsed: ${JSON.stringify(rawTree)} unparsed/failed: ${stringAsArray.join('')}`); let lastTree, tree = rawTree; do { lastTree = JSON.stringify(tree); tree = flattenTree(tree); } while (JSON.stringify(tree) !== lastTree) + preRouteAlt(tree); return tree; } @@ -18,7 +19,7 @@ export function buildTreeStruct(monoLineString) { * leftParser stuff */ function flushNoEmptyString(data) { - if (data.str.length) data.tree.alt[data.tree.alt.length - 1].step.push(data.str); + if (data.str.length) data.tree.alt[data.tree.alt.length - 1].step.push({str:data.str}); data.str = ''; } @@ -73,7 +74,7 @@ function concatStrings(array) { tree: {alt: [{step: []}]}, // Output array }; array.forEach(e => { - if (isString(e)) appendToString(e, data); + if (isString(e)) appendToString(e.str, data); else { flushNoEmptyString(data) data.tree.alt[data.tree.alt.length - 1].step.push(e); @@ -87,7 +88,7 @@ function flattenTree(tree) { if (isString(tree)) return tree; const objType = isAlt(tree)?'alt':'step'; tree[objType] = tree[objType].map(flattenTree); - if (tree[objType].length === 0) return ''; + if (tree[objType].length === 0) return {str:''}; if (tree[objType].length === 1) return tree[objType][0]; if (isAlt(tree)) { @@ -102,7 +103,7 @@ function flattenTree(tree) { } function isString(element) { - return typeof element === 'string'; + return typeof element === 'object' && typeof element.str === 'string'; } function isStep(element) { @@ -121,18 +122,63 @@ function trivialDedup(tree) { } export function serialize(treeStruct) { - if (isString(treeStruct)) return treeStruct; + if (isString(treeStruct)) return treeStruct.str; if (isStep(treeStruct)) return treeStruct.step.map(serialize).join(''); if (isAlt(treeStruct)) return `(${treeStruct.alt.map(serialize).join('|')})`; throw new Error(`Error: how to serialize ${JSON.stringify(treeStruct)} RAW: ${treeStruct}`); } +function preRouteAlt(treeStruct){ + if (isString(treeStruct)) { + treeStruct.altCount =1; + treeStruct.indexCost=1; + return 1; + } + if (isAlt(treeStruct)) { + treeStruct.altCount = treeStruct.alt.reduce((acc, cur) => acc + preRouteAlt(cur), 0); + treeStruct.indexCost = treeStruct.altCount-1; + return treeStruct.altCount; + } + if (isStep(treeStruct)) { + treeStruct.altCount = treeStruct.step.reduce((acc, cur) => acc * preRouteAlt(cur), 1); + // last alt indexCost*1, previous alt group indexCost * last alt altCount, firstAltGroup * nextAltGroup * nextAltGroup... * lastAltGroup + let weigth = 1; + for(let i = treeStruct.step.length-1;i>=0;i--){ + const step = treeStruct.step[i]; + if (isAlt(step)){ + step.alt.forEach(alt=>applyCostWeigth(alt,weigth)); + weigth*=step.altCount; + } + } + treeStruct.indexCost = treeStruct.altCount; + return treeStruct.altCount; + } +} +function applyCostWeigth(tree,weigth){ + tree.indexCost*=weigth; + if (isStep(tree)) tree.step.forEach(subTree=>applyCostWeigth(subTree,weigth)); + if (isAlt(tree)) tree.alt.forEach(subTree=>applyCostWeigth(subTree,weigth)); +} export function altCount(treeStruct) { - if (isString(treeStruct)) return 1; - if (isStep(treeStruct)) return treeStruct.step.reduce((acc, cur) => acc * altCount(cur), 1); - if (isAlt(treeStruct)) return treeStruct.reduce((acc, cur) => acc + altCount(cur), 0); + return treeStruct.altCount; } -export function getAlternative(altNumber, tree) { - return 'abc'; +export function getAlternative(altIndex, tree) { + const refAltIndex = {index:altIndex}; + return _getAlternative(refAltIndex, tree); +} +export function _getAlternative(refAltIndex, tree) { + if (isString(tree)) return tree.str; + if (isStep(tree)) { + return tree.step.map(subTree =>_getAlternative(refAltIndex, subTree) ).join(''); + } + if (isAlt(tree)){ + for (const altTree of tree.alt){ + // Keep me for debug case + // Keep console.debug(`In ${serialize(tree)} ${refAltIndex.index} < ${altTree.indexCost} ${refAltIndex.index < altTree.indexCost?'choose':'avoid'} ${serialize(altTree)}`); + if(refAltIndex.index < altTree.indexCost) return _getAlternative(refAltIndex, altTree); + else refAltIndex.index -= altTree.indexCost; + } + } + throw new Error(`index out of bound : ${refAltIndex.index+altCount(tree)}/${altCount(tree)} in ${serialize(tree)}`); } diff --git a/src/dictionary-tree.test.mjs b/src/dictionary-tree.test.mjs index 8fd0f4c695e8ebf8261d9c45aae5640cafb4867d..b84f43769ae8d50b9e81a6e87c2cd48e61760342 100644 --- a/src/dictionary-tree.test.mjs +++ b/src/dictionary-tree.test.mjs @@ -5,7 +5,7 @@ const buildTreeThenSerialize = str => app.serialize(app.buildTreeStruct(str)); test('simple string still simple string', t => t.is(buildTreeThenSerialize('abc'), 'abc')); test('(a|b) alt still (a|b)', t => t.is(buildTreeThenSerialize('(a|b)'), '(a|b)')); -test('a)b throw', t => t.throws(() => buildTreeStruct('a)b'))); +test('a)b throw', t => t.throws(()=>app.buildTreeStruct('a)b'))); // Ok to be permissive test('(a throw',t=>t.throws(()=>buildTreeStruct('(a'))); // Ok to be permissive test('a|b throw',t=>t.throws(()=>buildTreeStruct('a|b'))); test('(|b) keep empty choice', t => t.is(buildTreeThenSerialize('(|b)'), '(|b)')); @@ -17,9 +17,25 @@ test('build complexe tree with (|) pattern', t => t.is(buildTreeThenSerialize('( test('serialize incorrect tree throw', t => t.throws(() => app.serialize({plop: ['a']}))); -const exampleTree = () => app.buildTreeStruct('a(b(c|d)|e(f|g|h)ij(k|l)|@@@@m)') -test.skip('mono altCount', t => t.is(app.altCount(app.buildTreeStruct('ipsum')), 1)); -test.skip('simple altCount', t => t.is(app.altCount(app.buildTreeStruct('(lore|ipsu)m')), 2)); -test.skip('multi altCount', t => t.is(app.altCount(app.buildTreeStruct('(a|b|c)(d|e|f)g(h|i|j|k)')), 36)); -test.skip('multi level tree altCount', t => t.is(app.altCount(app.buildTreeStruct('a(b(c|d)|e(f|g|h)ij(k|l)|@@@@m)')), 9)); -test.skip('getAlternative 0', t => t.is(app.getAlternative(0, exampleTree()), 'abc')); +test('mono altCount', t => t.is(app.altCount(app.buildTreeStruct('ipsum')), 1)); +test('simple altCount', t => t.is(app.altCount(app.buildTreeStruct('(lore|ipsu)m')), 2)); +test('multi altCount', t => t.is(app.altCount(app.buildTreeStruct('(a|b|c)(d|e|f)g(h|i|j|k)')), 36)); +test('multi level tree altCount', t => t.is(app.altCount(app.buildTreeStruct('a(b(c|d)|e(f|g|h)ij(k|l)|@@@@m)')), 9)); + +const exampleTree = () => app.buildTreeStruct('a(b(c|d)|e(f|g(h|i)|j)kl(m|n(o|p)|q(r|s)|t)|(u|v)w)'); +console.log(JSON.stringify(exampleTree())); +console.log(app.serialize(exampleTree())); +test('getAlternative 0', t => t.is(app.getAlternative(0, exampleTree()), 'abc')); +test('getAlternative 1', t => t.is(app.getAlternative(1, exampleTree()), 'abd')); +test('getAlternative 2', t => t.is(app.getAlternative(2, exampleTree()), 'aefklm')); +test('getAlternative 3', t => t.is(app.getAlternative(3, exampleTree()), 'aefklno')); +test('getAlternative 4', t => t.is(app.getAlternative(4, exampleTree()), 'aefklnp')); +test('getAlternative 5', t => t.is(app.getAlternative(5, exampleTree()), 'aefklqr')); +test('getAlternative 6', t => t.is(app.getAlternative(6, exampleTree()), 'aefklqs')); +test('getAlternative 7', t => t.is(app.getAlternative(7, exampleTree()), 'aefklt')); +test('getAlternative 8', t => t.is(app.getAlternative(8, exampleTree()), 'aeghklm')); +test('getAlternative 9', t => t.is(app.getAlternative(9, exampleTree()), 'aeghklno')); +test('getAlternative 14', t => t.is(app.getAlternative(14, exampleTree()), 'aegiklm')); +test('getAlternative 20', t => t.is(app.getAlternative(20, exampleTree()), 'aejklm')); +test.skip('getAlternative 26', t => t.is(app.getAlternative(26, exampleTree()), 'aqr')); +test('getAlternative 999 throw', t => t.throws(()=>app.getAlternative(999, exampleTree())));