How to find the longest common prefix in an array of strings? - javascript

I'm trying to solve this using the .every method but it's not returning true and therefore it's not adding onto my string and I'm not sure why.
var longestCommonPrefix = function(arr) {
if (arr.length === 0) {
return undefined;
}
let result = '';
for (let i = 0; i < arr.length; i++) {
if (arr.every(x => arr[i].charAt(i) === x)) {
result += arr[i].charAt(i);
}
}
return result
}
console.log(longestCommonPrefix(["flower", "flow", "flight"])); //fl

You need to iterate over one string, not over the whole array: check if the first character of the string is present everywhere, then the second character, etc:
var longestCommonPrefix = function(arr) {
if (arr.length === 0) {
return undefined;
}
let result = '';
for (let i = 0; i < arr[0].length; i++) {
if (arr.every(x => x.charAt(i) === arr[0][i])) {
result += arr[i].charAt(i);
} else break;
}
return result;
}
console.log(longestCommonPrefix(["flower", "flow", "flight"])); //fl

Your use of Array.every is along the right lines. You want to check that every string in the array has the same character at position i. I think you got confused when you named the parameter x, when it is in fact a string :)
var longestCommonPrefix = function(words) {
if (words.length === 0) {
return "";
}
const letters = [];
const lengthOfShortestWord = Math.min(...words.map(word => word.length));
for (let i = 0; i < lengthOfShortestWord; i++) {
const char = words[0][i];
if (words.every(word => word[i] === char)) {
letters.push(char);
} else {
break;
}
}
return letters.join("");
}
console.log(longestCommonPrefix(["flower", "flow", "flight"])); //fl

Unless I am mistaken the longest prefix is never going to be greater than the smallest string in the array.
In this case "fl" is both the smallest string and the longest common prefix:
["flower", "fl", "flight"]
So start with finding the smallest string in arr:
let [sm] = [...arr].sort((a, b) => a.length - b.length);
Then check that all strings in arr start with sm:
arr.every(str => str.startsWith(sm));
If that isn't the case then shorten sm by one character:
sm = sm.slice(0, -1);
And keep going until you eventually find the longest prefix or sm becomes an empty string:
const prefix = arr => {
let [sm] = [...arr].sort((a, b) => a.length - b.length);
while (sm && !arr.every(str => str.startsWith(sm))) sm = sm.slice(0, -1);
return sm;
};

Related

Longest prefix that match more than 50% of items in an array

Suppose I have array of strings: ["apple", "ape", "application", "item"].
I need to find the longest common prefix that matches more than 50% of the strings in that array.
For example, we got "ap" being the prefix of 3 strings in a 4 elements array -> so more than 50% of the array -> returns the prefix.
This is my attempt:
const longestPrefix = (arrStr) => {
if (arrStr.length === 0) return "";
let prefix = "";
let noPrefixMatched = {};
// loop the characters of first word
for (let i = 0; i < arrStr[0].length; i++) {
let char = arrStr[0][i];
let j = 0;
// loop all the words of the array except first one
for (j = 1; j < arrStr.length; j++) {
if (arrStr[j][i] !== char) {
// if first char not matched then set that word as notMatched
if (i === 0) {
noPrefixMatched[arrStr[j]] = true;
}
break;
}
// if the first characters are equal in all words and the loop reach the final word
if (arrStr[j][i] === char && j === arrStr.length - 1) {
prefix += char;
}
}
}
return prefix;
};
I try to get the most common prefix by vertical comparison, but it's not working with a word without any prefix in the array (like "item" in the above array). How should I do this?
One way to do this is to iterate all the words, constructing prefixes one letter at a time and counting the occurrence of each prefix as you see it. You can then filter that result based on the count being greater than 1/2 the length of the input array, and finally sort it by the prefix length descending, returning the first entry in the result:
const words = ["apple", "ape", "application", "item"]
const counts = words.reduce((acc, w) => {
prefix = ''
for (i = 0; i < w.length; i++) {
prefix += w[i]
acc[prefix] = (acc[prefix] || 0) + 1
}
return acc
}, {})
const bestPrefix = Object.entries(counts)
.filter(([_, v]) => v > words.length / 2.0)
.sort((a, b) => b[0].length - a[0].length)
[0][0]
console.log(bestPrefix)
The first word should not be considered special or be hardcoded in any way - it may not even be a match for the substring selected, after all.
A simple way to code this would be to iterate over all words and their possible prefixes and see which prefixes pass the test, while keeping the best one in an outer variable - then return it at the end.
const getPrefixes = str => Array.from(str, (_, i) => str.slice(0, i + 1));
const matches = (arr, prefix) => {
const matchCount = arr.reduce((a, str) => a + str.startsWith(prefix), 0);
return matchCount / arr.length > 0.5;
};
const longestPrefix = (arrStr) => {
let bestPrefix = '';
for (const str of arrStr) {
for (const prefix of getPrefixes(str)) {
if (prefix.length > bestPrefix.length && matches(arrStr, prefix)) {
bestPrefix = prefix;
}
}
}
return bestPrefix;
};
console.log(longestPrefix(["apple", "ape", "application", "item"]));
A less computationally complex but more complicated method would be to construct a tree structure of characters from each string in the input, and then iterate through the tree to identify which nodes have enough nested children, and then pick the longest such node. This has the advantage of only requiring iterating over each character of each string in the input once.
const getBestChild = (obj, totalRequired, prefix = '') => {
if (obj.count < totalRequired) return;
const thisResult = { count: obj.count, prefix };
if (!obj.children) {
return thisResult;
}
let bestChild = thisResult;
for (const [nextChar, child] of Object.entries(obj.children)) {
const result = getBestChild(child, totalRequired, prefix + nextChar);
if (result && result.prefix.length > bestChild.prefix.length) {
bestChild = result;
}
}
return bestChild;
};
const longestPrefix = (arrStr) => {
const root = {};
for (const str of arrStr) {
let obj = root;
for (const char of str) {
if (!obj.children) {
obj.children = {};
}
const { children } = obj;
if (!children[char]) {
children[char] = { count: 0 };
}
children[char].count++;
obj = children[char];
}
}
const { length } = arrStr;
const totalRequired = length % 2 === 0 ? (1 + length / 2) : Math.ceil(length / 2);
return getBestChild(root, totalRequired).prefix;
};
console.log(longestPrefix(["apple", "ape", "application", "item"]));

Return true if the string in the first element of the array contains all of the letters of the string in the second element

I tried to do this by resetting loop going trough firstword every time its letter matches with secondword letter.
function mutation(arr) {
var compare = [];
var firstword = arr[0].toLowerCase();
var secondword = arr[1].toLowerCase();
var j = 0;
for (let i = 0; i < firstword.length; i++) {
if (firstword[i] === secondword[j]) {
compare.push(secondword[i]);
i = -1;
j++;
}
}
let result = compare.join("")
if (result.length === secondword.length) {
return true;
} else {
return false;
}
}
console.log(mutation(["Noel", "Ole"]));
It works in some cases but in others, like above example, it doesn't. What seems to be the problem?
You need to compare.push(secondword[j]) instead of compare.push(secondword[i])
function mutation(arr) {
var compare = [];
var firstword = arr[0].toLowerCase();
var secondword = arr[1].toLowerCase();
var j = 0;
for (let i = 0; i < firstword.length; i++) {
if (firstword[i] === secondword[j]) {
compare.push(secondword[j]); // Correction here
i = -1;
j++;
}
}
let result = compare.join("");
if (result.length === secondword.length) {
return true;
} else {
return false;
}
}
console.log(mutation(["Noel", "Ole"]));
Also, you can consider using Array.prototype.every.
const mutation = ([first, sec]) => {
const lowerCaseFirst = first.toLowerCase();
const lowerCaseSec = sec.toLowerCase();
return Array.from(lowerCaseSec).every((ch) => lowerCaseFirst.includes(ch));
};
console.log(mutation(["Noel", "Ole"]));
If the strings are small then String.prototype.includes works fine but if they are large then you should consider using a Set.
const mutation = ([first, sec]) => {
const firstSet = new Set(first.toLowerCase());
const lowerCaseSec = sec.toLowerCase();
return Array.from(lowerCaseSec).every((ch) => firstSet.has(ch));
};
console.log(mutation(["Noel", "Ole"]));
Simple ES6 Function, we check with .every() if every characters of secondword is includes inside firstword. It return true if it does.
function mutation(arr) {
const firstword = arr[0].toLowerCase();
const secondword = arr[1].toLowerCase();
return secondword.split('').every(char => firstword.includes(char));
}
console.log(mutation(["Noel", "Ole"]));
The use of Set in SSM's answer works if you don't need to account for duplicate characters in the second string. If you do, here's an implementation that uses a Map of character counts. The map key is the character from string 1 and the value is the number of occurrences. For instance, if you want ["Noel", "Ole"] to return true, but ["Noel", "Olle"] to return false (string 1 does not contain 2 "l" characters). String 2 is then iterated through and character counts decremented if they exist. As soon as a character is not present or the count falls below 1 in the map, the function returns false.
function mutation(arr: string[]): boolean {
return s1ContainsAllCharsInS2(arr[0].toLowerCase(), arr[1].toLowerCase());
}
function s1ContainsAllCharsInS2(s1: string, s2: string): boolean {
if (s2.length > s1.length) {
return false;
}
let charCountMap: Map<string, number> = new Map<string, number>();
Array.from(s1).forEach(c => {
let currentCharCount: number = charCountMap.get(c);
charCountMap.set(c, 1 + (currentCharCount ? currentCharCount : 0));
});
return !Array.from(s2).some(c => {
let currentCharCount: number = charCountMap.get(c);
if (!currentCharCount || currentCharCount < 1){
return true;
}
charCountMap.set(c, currentCharCount - 1);
});
}
A different approach.
Mapping the characters and comparing against that map.
function mutation(arr) {
const chars = {};
for (let char of arr[0].toLowerCase()) {
chars[char] = true;
}
for (let char of arr[1].toLowerCase()) {
if (!chars[char]) {
return false;
}
}
return true;
}
console.log(mutation(["Noel", "Ole"]));
console.log(mutation(["Noel", "Oleeeeeeeeeeeee"]));
If the count also matters (your code doesn't take it into account) you can count the number of occurrences of each character and comparing these counts.
function mutation(arr) {
const chars = {};
for (let char of arr[0].toLowerCase()) {
chars[char] = (chars[char] || 0) + 1;
}
for (let char of arr[1].toLowerCase()) {
// check if chars[char] contains a (not empty == positive) count
// then decrement it for future checks
if (!chars[char]--) {
return false;
}
}
return true;
}
console.log(mutation(["Noel", "Ole"]));
console.log(mutation(["Noel", "Oleeeeeeeeeeeee"]));

How to remove only one of repeated chars in string using JavaScript

I have a string with repeated chars like : 'CANADA'.
And I am trying to get the string which removed only one of repeated chars :
'CNADA', 'CANDA', 'CANAD'.
I've tried it with subString, but it returned the part of string removed.
Also I've tried it with reduce, but it ended up removing all the repeated chars ('CND').
What is the way of removing only one char at time?
The results can be stored in array. (results = ['CNADA', 'CANDA', 'CANAD'])
Thank you.
You can achieve this by utilizing the second parameter of String#indexOf() which specifies the position from which to start the search. Here in a while loop, and using a Set to remove dulplicates before returning.
function getReplaceOptions(str, char) {
let res = [], i = str.indexOf(char, 0);
while (i !== -1) {
res.push(str.substring(0, i) + str.substring(++i));
i = str.indexOf(char, i)
}
return Array.from(new Set(res))
}
console.log(getReplaceOptions('CANADA', 'A'));
console.log(getReplaceOptions('Mississippi', 's'));
You can first count all the occurrences in the string. Later you can iterate over the script and if the count is greater than 1 you can remove that character.
const theString = 'CANADA'
const letterCount = {}
const resultArr = []
for (var i = 0; i < theString.length; i++) {
const theLetter = theString.charAt(i)
if(letterCount[theLetter]){
letterCount[theLetter] = letterCount[theLetter] + 1
}
else{
letterCount[theLetter] = 1
}
}
console.log(letterCount)
for (var i = 0; i < theString.length; i++) {
const theLetter = theString.charAt(i)
if(letterCount[theLetter] && letterCount[theLetter] > 1){
resultArr.push(theString.substr(0, i) + theString.substr(i + 1))
}
}
console.log(resultArr)
If you want to remove only the first repeating character then you can use matchAll here as:
Just see the browser compatibility before using this
const str = 'CANADA';
const firstRepeatedChar = 'A';
const result = [];
for (let { index } of str.matchAll(firstRepeatedChar)) {
result.push(str.slice(0, index) + str.slice(index + 1));
}
console.log(result);
NOTE: If you want to search for the first repeating character then remove it, then you can do as:
const str = 'CANADA';
let firstRepeatedChar = '';
const set = new Set();
for (let i = 0; i < str.length; ++i) {
if (!set.has(str[i])) {
set.add(str[i]);
} else {
firstRepeatedChar = str[i];
break;
}
}
const result = [];
for (let { index } of str.matchAll(firstRepeatedChar)) {
result.push(str.slice(0, index) + str.slice(index + 1));
}
console.log(result);
You could use some Array magic to remove duplicate characters:
function removeDuplicateCharacters(value) {
// convert string to array and loop through each character
return String(value).split('').filter(function(char, index, all) {
// return false if char found at a different index
return (index === all.indexOf(char));
})
.join(''); // convert back to a string
}
// returns `CAND`
removeDuplicateCharacters('CANADA');
// returns `helo wrd`
removeDuplicateCharacters('hello world');

Finding both characters and putting them in a new array

I've created a function that passes in a string and a character. The string is saturday and the character is a.
I want the result to be an array that contains all the index numbers of where the letter 'a' sits in saturday.
Then array ends up with only [1]. So it finds the first a sitting at the second index. I tested this by changing saturday to soturday and the console prints the array with [6].
I want the result to be [1, 6]
I've tried putting the return result outside the next set of {} braces but no joy.
const subLength = (str, cha) => {
let result = [];
for (let i = 0; i < str.length; i++) {
if (str.charAt(i) === cha) {
result.push(str.indexOf(cha));
return result;
}
}
};
console.log(subLength('Saturday', 'a'));
2 small problems with your code
The return statement is in the for loop. The first time the loop hits that your loop will stop and the function will return. This is why you are only getting 1 result. Move the return outside the loop.
Once the above is fixed you will realize that your array will now return [1, 1]. This is because str.indexOf(cha) will always return 1 since it's returning the index of the first a. To fix this, you should be appending the index i to your array instead since it represents the index of the current char.
const subLength = (str, cha) => {
let result = [];
for (let i = 0; i < str.length; i++) {
if (str.charAt(i) === cha) {
result.push(i);
}
}
return result;
};
console.log(subLength('Saturday', 'a'));
You are pretty close.
In your code the return is being executed as soon as a match is found. You want to return after you've checked every char - so I've moved it after the foreach.
indexof has a second parm, which defines the char to start looking from. If you omit it, you will get the index of the first match every time - which is not what you want.
const subLength = (str, cha) => {
let result = [];
for(let i = 0; i < str.length; i++){
if(str[i] === cha) {
result.push(str.indexOf(cha, i));
}
}
return result;
};
console.log(subLength('Saturday', 'a'));
Room for improvement
Since you are iterating over every char anyways, you can simply store every i where str[i] matches cha.
So optimized:
const subLength = (str, cha) => {
let result = [];
for(let i = 0; i < str.length; i++){
if(str[i] === cha) {
result.push(i);
}
}
return result;
};
console.log(subLength('Saturday', 'a'));
An even simpler version using regex:
const subLength = (str, cha) => {
return [...str.matchAll(new RegExp(cha, 'g'))].map(e => e.index);
};
console.log(subLength('Saturday', 'a'));
How about putting the return result; outside of the for loop?
Something like this should work, if it is case-sensitive
const subLength = (str, cha) => {
const chaArr = str.split('');
const result = [];
chaArr.forEach((v, i) => {
if (v === cha) result.push(i);
})
return result
};
console.log(subLength('Saturday', 'a'));

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