There are at least two good ways to analyze this question. The first is to fire up a debugger.1 This is often the best way. But you can also add logging to track what's happening. Here are the results of adding logging 2, using the shorter input "abc"
, first for
i + 1
allSubseq ([], "abc", 0)
allSubseq ([a], "abc", 1)
> console .log ("a")
allSubseq ([a,b], "abc", 2)
> console .log ("ab")
allSubseq ([a,b,c], "abc", 3)
> console .log ("abc")
allSubseq ([a,c], "abc", 3)
> console .log ("ac")
allSubseq ([b], "abc", 2)
> console .log ("b")
allSubseq ([b,c], "abc", 3)
> console .log ("bc")
allSubseq ([c], "abc", 3)
> console .log ("c")
and then for
index + 1
allSubseq ([], "abc", 0)
allSubseq ([a], "abc", 1)
> console .log ("a")
allSubseq ([a,b], "abc", 2)
> console .log ("ab")
allSubseq ([a,b,c], "abc", 3)
> console .log ("abc")
allSubseq ([a,c], "abc", 2)
> console .log ("ac")
allSubseq ([a,c,c], "abc", 3)
> console .log ("acc")
allSubseq ([b], "abc", 1)
> console .log ("b")
allSubseq ([b,b], "abc", 2)
> console .log ("bb")
allSubseq ([b,b,c], "abc", 3)
> console .log ("bbc")
allSubseq ([b,c], "abc", 2)
> console .log ("bc")
allSubseq ([b,c,c], "abc", 3)
> console .log ("bcc")
allSubseq ([c], "abc", 1)
> console .log ("c")
allSubseq ([c,b], "abc", 2)
> console .log ("cb")
allSubseq ([c,b,c], "abc", 3)
> console .log ("cbc")
allSubseq ([c,c], "abc", 2)
> console .log ("cc")
allSubseq ([c,c,c], "abc", 3)
> console .log ("ccc")
These should help make it clear what's happening, and why you don't want to keep passing the fixed index + 1
.
But I also want to point out a much simpler implementation:
const call = (fn, ...args) =>
fn (...args)
const subseqs = ([c, ...cs]) =>
c == undefined
? ['']
: call ((ss = subseqs (cs)) => ss .flatMap (s => c + s) .concat (ss))
const allSubseq = (s) =>
subseqs (s) .filter (Boolean) // remove empty string
console .log (allSubseq ('abcd'))
.as-console-wrapper {max-height: 100% !important; top: 0}
Here the main recursive function is subseqs
. We wrap it in allSubseq
to remove the empty string that is generated in subseqs
. If you want to keep that empty string, then it's simpler still.
We use call
as a way to define, calculate, then use and reuse the variable ss
in a function body that contains only expressions and not statements. If this is confusing, we can skip the call and achieve the same thing with statements and a local variable like this:
const subseqs = ([c, ...cs]) => {
if (c == undefined) return ['']
const ss = subseqs (cs)
return ss .flatMap (s => c + s) .concat (ss)
}
In either case, our base case is when the input string is empty, and we return an array containing only the empty string. If it's not, we calculate the subsequences for the tail of the string (everything but the first character) and return first those new subsequences prefixed by the first character and then those subsequences directly.
Note that this function does not print your results to the console, just returns them. This is clearly more flexible. If you want to print them individually, you can do something like console .log (allSubseq ('abcd') .join ('\n'))
or allSubseq ('abcd') .forEach (console .log)
.
1 Please see How to debug small programs and What is a debugger and how can it help me diagnose problems?
2You can view the tweaked source code for i + 1
and index + 1