Most efficient way to split string into tree structure - javascript

I have a dynamic array of the string. Each string represents the node of the tree. If any string has "_", I want to split into each node and store it in another array.
For. e.g.
'0_2_0_0' needs to split into "0", "0_2", "0_2_0", & "0_2_0_0" then store it in the new array.
I've achieved it by using the multiple for loops. I do not think this is the most efficient way of doing it.
let visibleTreeNodes = [];
const treeData = ['0_0', '0_1', '0_2_0_0', '1'];
for (let i = 0; i < treeData.length; i++) {
if (treeData[i].includes('_')) {
const nodesArray = treeData[i].split('_');
for (let i = 0; i < nodesArray.length; i++) {
let node = nodesArray[0];
for (let j = 0; j <= i; j++) {
if (j !== 0) {
node = node + '_' + nodesArray[j];
}
}
if (visibleTreeNodes.indexOf(node) === -1) {
visibleTreeNodes.push(node)
}
}
} else if (visibleTreeNodes.indexOf(treeData[i]) === -1) {
visibleTreeNodes.push(treeData[i])
}
}
console.log(treeData);
console.log(visibleTreeNodes);

All that is left for me is to explain (and remember) the part of building the tree by #Nina Scholz. I'll basically rename the variables and use familiar syntax.
// data can be directories lists
const data = ['0_0', '0_1', '0_2_0_0', '1']
// the classic group by using reduce
var result = data.reduce(function(agg, item) {
// buliding tree path incrementally
var pointer = agg;
item.split('_').forEach(function(part) {
pointer[part] = pointer[part] || {}
pointer = pointer[part];
});
return agg;
}, {});
// result is classic object tree
console.log(result);
// iterate to print desired output:
function iterate(tree, parent) {
Object.keys(tree).forEach(function(key) {
var value = tree[key];
var full = (parent ? parent + "_" : '') + key
console.log(full)
if (typeof value === 'object') {
iterate(value, full)
}
})
}
iterate(result)
.as-console-wrapper {max-height: 100% !important}

You could build a tree first and then get the pathes from the keys.
const
getFlat = object => Object.keys(object).flatMap(k => [
k,
...getFlat(object[k]).map(p => `${k}_${p}`)
]),
data = ['0_0', '0_1', '0_2_0_0', '1'],
result = getFlat(data.reduce((t, s) => {
s.split('_').reduce((o, k) => o[k] ??= {}, t);
return t;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

I tried to simplify your equation. I reached this below snippet. It has a forEach and then a while loop inside forEach for every input string.
let visibleTreeNodes = [];
const treeData = ['0_0', '0_1', '0_2_0_0', '1'];
treeData.forEach(x => {
const arr = x.split("_");
let i = 0;
let node = arr[0];
while (i < arr.length) {
if (!visibleTreeNodes.includes(node)) {
visibleTreeNodes.push(node);
}
node = [node, arr[i + 1]].join("_");
i++;
}
});
console.log(treeData);
console.log(visibleTreeNodes);
You are free to figure out any better solution.
Thanks

A "one-liner":
const treeData = ['0_0', '0_1', '0_2_0_0', '1'];
const visibleTreeNodes = [...new Set(treeData.flatMap(
s=>s.split('_').reduce((a, si, i)=> [...a, a[i-1]+'_'+si])
))];// Set used to remove duplicates
console.log(visibleTreeNodes)
though efficiency must be tested - a more concise solution doesn't automatically result in a shorter run time

Related

Information about to Array in JAvascript

I would like to get find elements having same characters but in different order in an array. I made javascript below,is there any way to create Javascript function more basic? Can you give me an idea? Thank you in advance..
<p id="demo"></p>
<script>
const arr1 = ["tap", "pat", "apt", "cih", "hac", "ach"];
var sameChars = 0;
var subArr1 = [];
for(var i = 0; i < arr1.length; i++){
for(var j = i+1; j < arr1.length; j++){
if(!subArr1.includes(arr1[i]) && !subArr1.includes(sortAlphabets(arr1[i]))){
subArr1.push(arr1[i]);
sameChars++;
}
if(sortAlphabets(arr1[i]) == sortAlphabets(arr1[j])){
if(!subArr1.includes(arr1[j])){
subArr1.push(arr1[j]);
}
}
}
}
function sortAlphabets(text1) {
return text1.split('').sort().join('');
};
document.getElementById("demo").innerHTML = sameChars;
</script>
I would just use reduce. Loop over split the string, sort it, join it back. Use it as a key in an object with an array and push the items onto it.
const arr1 = ["tap", "pat", "apt", "cih", "hac", "ach"];
const results = arr1.reduce((obj, str) => {
const key = str.split('').sort().join('');
obj[key] = obj[key] || [];
obj[key].push(str);
return obj;
}, {});
console.log(Object.values(results));
You can get the max frequency value by building a map and getting the max value of the values.
const frequencyMap = (data, keyFn) =>
data.reduce(
(acc, val) =>
(key => acc.set(key, (acc.get(key) ?? 0) + 1))
(keyFn(val)),
new Map());
const groupMap = (data, keyFn) =>
data.reduce(
(acc, val) =>
(key => acc.set(key, [...(acc.get(key) ?? []), val]))
(keyFn(val)),
new Map());
const
data = ["tap", "pat", "apt", "cih", "hac", "ach"],
sorted = (text) => text.split('').sort().join(''),
freq = frequencyMap(data, sorted),
max = Math.max(...freq.values()),
groups = groupMap(data, sorted);
document.getElementById('demo').textContent = max;
console.log(Object.fromEntries(freq.entries()));
console.log(Object.fromEntries(groups.entries()));
.as-console-wrapper { top: 2em; max-height: 100% !important; }
<div id="demo"></div>
Maybe split the code into two functions - one to do the sorting and return a new array, and another to take that array and return an object with totals.
const arr = ['tap', 'pat', 'apt', 'cih', 'hac', 'ach'];
// `sorter` takes an array of strings
// splits each string into an array, sorts it
// and then returns the joined string
function sorter(arr) {
return arr.map(str => {
return [...str].sort().join('');
});
}
// `checker` declares an object and
// loops over the array that `sorter` returned
// creating keys from the strings if they don't already
// exist on the object, and then incrementing their value
function checker(arr) {
const obj = {};
for (const str of arr) {
// All this line says is if the key
// already exists, keep it, and add 1 to the value
// otherwise initialise it with 0, and then add 1
obj[str] = (obj[str] || 0) + 1;
}
return obj;
}
// Call `checker` with the array from `sorter`
console.log(checker(sorter(arr)));
<p id="demo"></p>
Additional documentation
map
Loops and iteration
Spread syntax

Javascript nested array: halve items and sum them up

I'm trying to create a function which returns me halve the data sumed up. I was able to do it on a non-nested Array but failing on the nested Array. I get the error Cannot read properties of undefined (reading 'push').
How the returned data should look like:
var data = [{"Key":1,"values":[
{"LastOnline":"21-11-29","Value":2},
{"LastOnline":"21-12-01","Value":2},
{"LastOnline":"21-12-03","Value":2}
]}];
What I have right now:
var data = [{"Key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data){
var newData = [];
var temp = [{"key":data.key,"values":[{}]}];
// sum 2 togheter
for(var i=1;i<data.values.length;i++){
if(data.values[i]){
temp.values[i].push({"LastOnline":data.values[i].LastOnline, "Value":(data.values[i].Value + data.values[[i-1]].Value)});
}
}
for(var i=0;i<temp.values.length;i++){
if(i % 2 == 0){
newData.push(temp.values[i]);
}
}
return newData;
}
console.log(halveMonth(data));
JS variables are case sensitive. Keep the key consistent everywhere. If you don't plan to use reduce here is the solution.
var data = [{"key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data){
let newData = []
for (let i = 0; i < data.length; i++) {
let temp = {"key":data[i].key,"values":[]}
for (let j = 0; j < data[i].values.length; j += 2) {
const res = (j+1===data[i].values.length) ? data[i].values[j].Value : data[i].values[j].Value + data[i].values[j+1].Value
temp.values.push({"LastOnline":(j+1===data[i].values.length)?data[i].values[j].LastOnline:data[i].values[j+1].LastOnline,"Value":res});
}
newData.push(temp);
}
return newData
}
console.log(halveMonth(data));
The variable data you declare at the first line of your snippet is an array. So you can't do data.values. You first need to indicate which index of your array you want to read. In this case : data[0].values
First things first, you data is itself an array - so assuming your real data has more than 1 element you'll need to do the same thing for each one.
It's helpful to start off with a method which does the work on just 1 element
const justASingle = {"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1}]};
function halveMonthSingle(data) {
return {
...data,
values: data.values.reduce((acc, item, idx) => {
if ((idx % 2) != 0)
acc.push({
...item,
Value: data.values[idx - 1].Value + item.Value
})
return acc;
}, [])
}
}
console.log(halveMonthSingle(justASingle))
Once you have that you can just use map do do it for every element
const data = [{"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1}]}];
function halveMonthSingle(data) {
return {
...data,
values: data.values.reduce((acc, item, idx) => {
if ((idx % 2) != 0)
acc.push({
...item,
Value: data.values[idx - 1].Value + item.Value
})
return acc;
}, [])
}
}
const result = data.map(halveMonthSingle)
console.log(result)
I would use reduce - saves me from trying to figure out why your two loops do not work other than data.values should be data[0].values
var data = [{"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1},]}];
const newArr = data.slice(0); // to not mutate original
newArr[0].values = data[0].values.reduce((acc,item,i) => {
if (i%2 !== 0) { // every second
acc.push(data[0].values[i]); // push the item
acc[acc.length-1].Value += data[0].values[i-1].Value; // add the first
}
return acc
},[])
console.log(newArr)
This works too. I basically did your idea, skipping the temp array and merging the two steps into one.
const data = [{"Key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data) {
const newData = [];
newData.push({
Key: data[0].Key,
values: []
});
for(let i = 0; i < data[0].values.length; i++){
if (i % 2 !== 0) {
newData[0].values.push({
LastOnline: data[0].values[i].LastOnline,
Value: data[0].values[i].Value + data[0].values[i-1].Value
});
}
}
return newData;
}
console.log(halveMonth(data));

Script to group elements where every character is same to each other

For input:
["abc","def","okg","fed","bca"]
expected output should be:
["abc","bca"],["def","fed"],["okg"]
here "abc", "bca" and "def", "fed" contains same character and "okg" there is no element which contains these character
const arr = ["abc", "def", "okg", "fed", "bca"];
let find = (arr) => {
let res = [];
for (let i = 0; i < arr.length; i++) {
for (let j = 1; j < arr.length; j++) {
if (arr[i].search(arr[j])) {
res.push(arr[j]);
}
}
}
return res;
}
console.log(find(arr))
A reduce will do the trick - it seems the shortest code here (apart from the one using lodash)
const arr = ["abc", "def", "okg", "fed", "bca"],
res = Object.values(arr.reduce((acc, ele) => {
const key = ele.split("").sort();
(acc[key] = acc[key] || []).push(ele)
return acc
}, {}))
console.log(res)
.search returns a number indicating the index of where the match was found. Check that the result isn't -1 instead of checking that the result is truthy. But...
.search isn't the right tool here anyway, because it won't find different combinations of the same character. You need a different approach. One way would be to create an object whose keys are the characters found, and the values are the number of occurrences, then use a sorted representation of that object as a key. For example, have both abc and bca turn into something like:
a,1-b,1-c,1
Iterate through the input array, generating a key for each string, and putting the string on an object with that key. At the end, take the object's values.
const strToKey = (str) => {
const grouped = {};
for (const char of str) {
grouped[char] = (grouped[char] || 0) + 1;
}
return Object.entries(grouped)
.sort((a, b) => a[0].localeCompare(b[0]))
.join('-');
};
let find = (arr) => {
const grouped = {};
for (const str of arr) {
const key = strToKey(str);
grouped[key] ??= [];
grouped[key].push(str);
}
return Object.values(grouped);
}
console.log(find(["abc", "def", "okg", "fed", "bca"]));
Another option, when creating the keys, instead of sorting the object afterwards, you could sort the string first:
const strToKey = (str) => {
const grouped = {};
for (const char of [...str].sort()) {
grouped[char] = (grouped[char] || 0) + 1;
}
return Object.entries(grouped).join('-');
};
let find = (arr) => {
const grouped = {};
for (const str of arr) {
const key = strToKey(str);
grouped[key] ??= [];
grouped[key].push(str);
}
return Object.values(grouped);
}
console.log(find(["abc", "def", "okg", "fed", "bca"]));
const input = ["abc","def","okg","fed","bca"]
function getSortedString (str) {
return [...str].sort().join('');
};
function groupBy(input) {
const grouped = [];
while(input.length) {
const nextInput = [];
const first = input[0];
const matched = [first];
for (let i = 1; i < input.length; i++) {
if(getSortedString(first) === getSortedString(input[i])) {
matched.push(input[i])
} else {
nextInput.push(input[i])
}
}
input = nextInput;
grouped.push(matched);
}
console.log(grouped);
}
groupBy(input);
Using Object.values and groupBy (from lodash), you can get a straightforward solution:
You group your array elements by their "sorted" form and then use Object.values to get the output array.
const arr = ["abc", "def", "okg", "fed", "bca"];
const sortString = (str) => str.split("").sort().join("")
const result = Object.values(_.groupBy(arr, sortString));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

Array/Object Sorting

So there's a JSON Object, it has a list of arrays with objects in it, like AssortmentID and AssortmentParentID with a Name, AssortimentID's with an AssortmentParentID of 000-000-000 are folders, the AssortmentID's with the AssortmentParentID of another AssortmentID are the children of that folder.
How do I output the folders first, and then the children in each folder(parent).
Example:
The HTTP Request:
const url = "\url";
async function getAssortList() {
const response = await fetch(url);
const data = response.json();
const { Assortments } = data;
for (let i = 0; i < Assortments.length; i++) {
const assortItem = Assortments[i];
...(where I'm stuck)...
/////////////////////////////////////
The JSON Response:
Assortments: [
{
"AssortmentID": 123-123-123-123,
"AssortmentParentID": 000-000-000
"Name": "I am a parent"
},
{
"AssortmentID": 111-111-111-111,
"AssortmentParentID": 123-123-123-123,
"Name": "I am a kid"
}
Maybe this will help you.
Assortments.reduce((acc, curr) => {
let parent = curr["AssortmentParentID"];
if(acc[parent]){
acc[parent].push(curr)
}
else{
acc[parent] = [curr]
}
return acc;
}, {})
try below code in javascript
Assortments.sort(
function (a, b) {
var x = a.AssortmentParentID.toLowerCase();
var y = b.AssortmentParentID.toLowerCase();
if (x < y) {return -1;}
if (x > y) {return 1;}
return 0;
})
by this you will have sorted array on AssortmentParentID
Kind of, solved it myself. Still have things to do.
const parents = []; // created a new array that I'll push objects into later.
for (let i = 0; i < Assortments.length; i++) {
if (Assortments[i].AssortimentParentID == "000-000-000") {
parents.push(Assortments[i]);
}
}
for (let i = 0; i < parents.length; i++) {
parents.sort((a, b) => a.Name.localeCompare(b.Name)); // sort them alphabetically
}
If someone can show me how to do the same thing, using filter/find/etc., it would be great. Thank you!
UPDATE:
Managed to sort the parents, output them in DOM, here's the code:
for (let x in parents) {
parents[x].isFolder === false ? parents.push( parents.splice(x,1)[0] ) : 0;
}
let assortItem = parents[i].Name;
let subnavList = document.createElement("li");
let subnavLink = document.createElement("a");
subnavList.classList.add("subnav-list");
subnavList.appendChild(subnavLink);
subnavLink.classList.add("subnav-link");
subnavLink.innerHTML += assortItem;
item.appendChild(subnavList);

Alibaba interview: print a sentence with min spaces

I saw this interview question and gave a go. I got stuck. The interview question is:
Given a string
var s = "ilikealibaba";
and a dictionary
var d = ["i", "like", "ali", "liba", "baba", "alibaba"];
try to give the s with min space
The output may be
i like alibaba (2 spaces)
i like ali baba (3 spaces)
but pick no.1
I have some code, but got stuck in the printing.
If you have better way to do this question, let me know.
function isStartSub(part, s) {
var condi = s.startsWith(part);
return condi;
}
function getRestStr(part, s) {
var len = part.length;
var len1 = s.length;
var out = s.substring(len, len1);
return out;
}
function recPrint(arr) {
if(arr.length == 0) {
return '';
} else {
var str = arr.pop();
return str + recPrint(arr);
}
}
// NOTE: have trouble to print
// Or if you have better ways to do this interview question, please let me know
function myPrint(arr) {
return recPrint(arr);
}
function getMinArr(arr) {
var min = Number.MAX_SAFE_INTEGER;
var index = 0;
for(var i=0; i<arr.length; i++) {
var sub = arr[i];
if(sub.length < min) {
min = sub.length;
index = i;
} else {
}
}
return arr[index];
}
function rec(s, d, buf) {
// Base
if(s.length == 0) {
return;
} else {
}
for(var i=0; i<d.length; i++) {
var subBuf = [];
// baba
var part = d[i];
var condi = isStartSub(part, s);
if(condi) {
// rest string
var restStr = getRestStr(part, s);
rec(restStr, d, subBuf);
subBuf.unshift(part);
buf.unshift(subBuf);
} else {
}
} // end loop
}
function myfunc(s, d) {
var buf = [];
rec(s, d, buf);
console.log('-- test --');
console.dir(buf, {depth:null});
return myPrint(buf);
}
// Output will be
// 1. i like alibaba (with 2 spaces)
// 2. i like ali baba (with 3 spaces)
// we pick no.1, as it needs less spaces
var s = "ilikealibaba";
var d = ["i", "like", "ali", "liba", "baba", "alibaba"];
var out = myfunc(s, d);
console.log(out);
Basically, my output is, not sure how to print it....
[ [ 'i', [ 'like', [ 'alibaba' ], [ 'ali', [ 'baba' ] ] ] ] ]
This problem is best suited for a dynamic programming approach. The subproblem is, "what is the best way to create a prefix of s". Then, for a given prefix of s, we consider all words that match the end of the prefix, and choose the best one using the results from the earlier prefixes.
Here is an implementation:
var s = "ilikealibaba";
var arr = ["i", "like", "ali", "liba", "baba", "alibaba"];
var dp = []; // dp[i] is the optimal solution for s.substring(0, i)
dp.push("");
for (var i = 1; i <= s.length; i++) {
var best = null; // the best way so far for s.substring(0, i)
for (var j = 0; j < arr.length; j++) {
var word = arr[j];
// consider all words that appear at the end of the prefix
if (!s.substring(0, i).endsWith(word))
continue;
if (word.length == i) {
best = word; // using single word is optimal
break;
}
var prev = dp[i - word.length];
if (prev === null)
continue; // s.substring(i - word.length) can't be made at all
if (best === null || prev.length + word.length + 1 < best.length)
best = prev + " " + word;
}
dp.push(best);
}
console.log(dp[s.length]);
pkpnd's answer is along the right track. But word dictionaries tend to be quite large sets, and iterating over the entire dictionary at every character of the string is going to be inefficient. (Also, saving the entire sequence for each dp cell may consume a large amount of space.) Rather, we can frame the question, as we iterate over the string, as: given all the previous indexes of the string that had dictionary matches extending back (either to the start or to another match), which one is both a dictionary match when we include the current character, and has a smaller length in total. Generally:
f(i) = min(
f(j) + length(i - j) + (1 if j is after the start of the string)
)
for all j < i, where string[j] ended a dictionary match
and string[j+1..i] is in the dictionary
Since we only add another j when there is a match and a new match can only extend back to a previous match or to the start of the string, our data structure could be an array of tuples, (best index this match extends back to, total length up to here). We add another tuple if the current character can extend a dictionary match back to another record we already have. We can also optimize by exiting early from the backwards search once the matched substring would be greater than the longest word in the dictionary, and building the substring to compare against the dictionary as we iterate backwards.
JavaScript code:
function f(str, dict){
let m = [[-1, -1, -1]];
for (let i=0; i<str.length; i++){
let best = [null, null, Infinity];
let substr = '';
let _i = i;
for (let j=m.length-1; j>=0; j--){
let [idx, _j, _total] = m[j];
substr = str.substr(idx + 1, _i - idx) + substr;
_i = idx;
if (dict.has(substr)){
let total = _total + 1 + i - idx;
if (total < best[2])
best = [i, j, total];
}
}
if (best[0] !== null)
m.push(best);
}
return m;
}
var s = "ilikealibaba";
var d = new Set(["i", "like", "ali", "liba", "baba", "alibaba"]);
console.log(JSON.stringify(f(s,d)));
We can track back our result:
[[-1,-1,-1],[0,0,1],[4,1,6],[7,2,10],[11,2,14]]
[11, 2, 14] means a total length of 14,
where the previous index in m is 2 and the right index
of the substr is 11
=> follow it back to m[2] = [4, 1, 6]
this substr ended at index 4 (which means the
first was "alibaba"), and followed m[1]
=> [0, 0, 1], means this substr ended at index 1
so the previous one was "like"
And there you have it: "i like alibaba"
As you're asked to find a shortest answer probably Breadth-First Search would be a possible solution. Or you could look into A* Search.
Here is working example with A* (cause it's less bring to do than BFS :)), basically just copied from Wikipedia article. All the "turning string into a graph" magick happens in the getNeighbors function
https://jsfiddle.net/yLeps4v5/4/
var str = 'ilikealibaba'
var dictionary = ['i', 'like', 'ali', 'baba', 'alibaba']
var START = -1
var FINISH = str.length - 1
// Returns all the positions in the string that we can "jump" to from position i
function getNeighbors(i) {
const matchingWords = dictionary.filter(word => str.slice(i + 1, i + 1 + word.length) == word)
return matchingWords.map(word => i + word.length)
}
function aStar(start, goal) {
// The set of nodes already evaluated
const closedSet = {};
// The set of currently discovered nodes that are not evaluated yet.
// Initially, only the start node is known.
const openSet = [start];
// For each node, which node it can most efficiently be reached from.
// If a node can be reached from many nodes, cameFrom will eventually contain the
// most efficient previous step.
var cameFrom = {};
// For each node, the cost of getting from the start node to that node.
const gScore = dictionary.reduce((acc, word) => { acc[word] = Infinity; return acc }, {})
// The cost of going from start to start is zero.
gScore[start] = 0
while (openSet.length > 0) {
var current = openSet.shift()
if (current == goal) {
return reconstruct_path(cameFrom, current)
}
closedSet[current] = true;
getNeighbors(current).forEach(neighbor => {
if (closedSet[neighbor]) {
return // Ignore the neighbor which is already evaluated.
}
if (openSet.indexOf(neighbor) == -1) { // Discover a new node
openSet.push(neighbor)
}
// The distance from start to a neighbor
var tentative_gScore = gScore[current] + 1
if (tentative_gScore >= gScore[neighbor]) {
return // This is not a better path.
}
// This path is the best until now. Record it!
cameFrom[neighbor] = current
gScore[neighbor] = tentative_gScore
})
}
throw new Error('path not found')
}
function reconstruct_path(cameFrom, current) {
var answer = [];
while (cameFrom[current] || cameFrom[current] == 0) {
answer.push(str.slice(cameFrom[current] + 1, current + 1))
current = cameFrom[current];
}
return answer.reverse()
}
console.log(aStar(START, FINISH));
You could collect all possible combinations of the string by checking the starting string and render then the result.
If more than one result has the minimum length, all results are taken.
It might not work for extrema with string who just contains the same base string, like 'abcabc' and 'abc'. In this case I suggest to use the shortest string and update any part result by iterating for finding longer strings and replace if possible.
function getWords(string, array = []) {
words
.filter(w => string.startsWith(w))
.forEach(s => {
var rest = string.slice(s.length),
temp = array.concat(s);
if (rest) {
getWords(rest, temp);
} else {
result.push(temp);
}
});
}
var string = "ilikealibaba",
words = ["i", "like", "ali", "liba", "baba", "alibaba"],
result = [];
getWords(string);
console.log('all possible combinations:', result);
console.log('result:', result.reduce((r, a) => {
if (!r || r[0].length > a.length) {
return [a];
}
if (r[0].length === a.length) {
r.push(a);
}
return r;
}, undefined))
Use trie data structure
Construct a trie data structure based on the dictionary data
Search the sentence for all possible slices and build a solution tree
Deep traverse the solution tree and sort the final combinations
const sentence = 'ilikealibaba';
const words = ['i', 'like', 'ali', 'liba', 'baba', 'alibaba',];
class TrieNode {
constructor() { }
set(a) {
this[a] = this[a] || new TrieNode();
return this[a];
}
search(word, marks, depth = 1) {
word = Array.isArray(word) ? word : word.split('');
const a = word.shift();
if (this[a]) {
if (this[a]._) {
marks.push(depth);
}
this[a].search(word, marks, depth + 1);
} else {
return 0;
}
}
}
TrieNode.createTree = words => {
const root = new TrieNode();
words.forEach(word => {
let currentNode = root;
for (let i = 0; i < word.length; i++) {
currentNode = currentNode.set(word[i]);
}
currentNode.set('_');
});
return root;
};
const t = TrieNode.createTree(words);
function searchSentence(sentence) {
const marks = [];
t.search(sentence, marks);
const ret = {};
marks.map(mark => {
ret[mark] = searchSentence(sentence.slice(mark));
});
return ret;
}
const solutionTree = searchSentence(sentence);
function deepTraverse(tree, sentence, targetLen = sentence.length) {
const stack = [];
const sum = () => stack.reduce((acc, mark) => acc + mark, 0);
const ret = [];
(function traverse(tree) {
const keys = Object.keys(tree);
keys.forEach(key => {
stack.push(+key);
if (sum() === targetLen) {
const result = [];
let tempStr = sentence;
stack.forEach(mark => {
result.push(tempStr.slice(0, mark));
tempStr = tempStr.slice(mark);
});
ret.push(result);
}
if(tree[key]) {
traverse(tree[key]);
}
stack.pop();
});
})(tree);
return ret;
}
const solutions = deepTraverse(solutionTree, sentence);
solutions.sort((s1, s2) => s1.length - s2.length).forEach((s, i) => {
console.log(`${i + 1}. ${s.join(' ')} (${s.length - 1} spaces)`);
});
console.log('pick no.1');

Categories

Resources