I'm trying to implement a function using JavaScript's reduce
method to process a tree-like string indented by tabs. Consider that the input is always correctly tab-indented. I know it can be done using backtrack, but I was wondering if that was possible with reduce
. My code is shown below, but I can't reach the desired output. Can anyone help me understand what I'm doing wrong and how I can fix this problem?
Given the input:
/wp-content/
/uploads
/plugins/
/akismet/
/themes/
/twentyeleven/
/colors/
I expect the output to be a list of combinations of paths, one per line, including intermediate paths (e.g. /wp-content/ and /wp-content/plugins):
/wp-content/
/wp-content/uploads
/wp-content/plugins/
/wp-content/plugins/akismet/
/wp-content/themes/
/wp-content/themes/twentyeleven/
/wp-content/themes/twentyeleven/colors
However, my code is not working as expected as I'm getting repeated output instead:
/wp-admin/wp-content/plugins/akismet/themes/twentyeleven/colors
/wp-admin/wp-content/plugins/akismet/themes/twentyeleven/images
/wp-admin/wp-content/uploads
/wp-admin/wp-content/plugins/akismet/themes/twentyeleven/colors
/wp-admin/wp-content/plugins/akismet/themes/twentyeleven/images
/wp-admin/wp-content/uploads
Here's what I've tried
rm = (s, ch='\t') => s?.replaceAll(ch,'')
count = (s, ch='\t') => s?.match(new RegExp(ch,'g'))?.length
wrapper = (arr, fun, start) => arr.reduce(fun, start)
function fn(acc, curr, i, self){
x = count(curr)
y = count(self[i+1])
curr = rm(curr)
acc = rm(acc)
selfi = rm(self[i+1])
if(!selfi) return acc
if(curr.at(-1) === '/' && acc.at(0) === '/') {
curr = curr.slice(0,-1)
} // ends and start with /
if(x===y){
console.log(acc+curr)
return acc
} else {
idx = self.indexOf(self.slice(0,i+1).filter(k => count(k) === x)[0])
if(!idx) idx = -1
wrapper(self.slice(i,idx), fn, acc)
return acc+curr
}
}
wrapper(ar, fn, '/wp-admin')
As indicated by others, this is typically solved with a stack.
But for one input line, the stack may need to get rid of more than one item, while it should always get a new entry.
I have added a few more lines to your example input to illustrate this:
function paths(input, prefix = "") {
const stack = [{length: -1, name: prefix}];
return Array.from(input.matchAll(/^(.*?)(\S.+?)(\/?)\s*$/gm),
([, {length}, name, tail]) => {
while (stack.at(-1).length >= length) stack.pop();
stack.push({ length, name });
return stack.map(({ name }) => name).join("") + tail;
}
);
}
console.log(paths(`\
/wp-content/
/uploads
/plugins/
/akismet/
/themes/
/twentyeleven/
/colors/
/images/
/thumbnails/
/other-content/`, "/wp-admin"));
.as-console-wrapper { max-height: 100% !important; top: 0; }