Here’s the problem:
Assume that the value of a = 1, b = 2, c = 3, … , z = 26. You are given a numeric string S. Write a program to return the list of all possible codes that can be generated from the given string.
So, 1123
will produce the following output:
aabc kbc alc aaw kw
So the way I’m approaching is this:
first map each alphabet to number, for e.g, "1":"a", "2":"b"...
and so on.
Then recursively divide the problem:
f("123") = {alphabetMap("1")+ f("23"), alphabetMap("12") + f("3")}
So I draw the recursive tree with this approach and able to find the solution.
But somewhere there seems to be a problem when I implement the same.
var alphabetMap = {};
for(var i = 1; i <= 26; i++){
alphabetMap[i] = String.fromCharCode(96 + i);
}
//Gives {"1": "a", "2": "b",........."26": "z"}
var res = [];
function encrypt(str, i = 0, newStr = ""){
if(i >= str.length){
res.push(newStr);
return;
}
encrypt(str, i + 1, newStr + (alphabetMap[str[i]] || ''));
encrypt(str, i + 2, newStr + (alphabetMap[str[i] + str[i + 1]] || ''));
}
encrypt("1123");
console.log(res)
Another issue I wanted to understand with this problem is how to intuitively think when there would be just one index needed to find all combination and when we’d need more.
For e.g, see this
Find all combinations of interleaving two strings. Where am I making mistake?
This problem is similar and the breakdown of the problem is similar too as with each step the problem is going to be divided into two. But we need two indices because we need two set of strings. Same argument can be applied here but here I think just once index suffices.
Appreciate insight on this.
3
Answers
Your idea with recursion is fine, and yes, this only needs one index (since there is only one string to iterate). At each stage your function will try the two possibilities. But that is where you have a bug: before looking into the case where you take two digits, you should ensure that the string still has that many characters available.
In that case
+ str[i + 1]
will be undefined, and although you have provision for that case with|| ''
this means you make the recursive call withnewStr
itself (nothing appended to it), while still skipping the last character ati
. In your example this means you output firstaabc
(correct), but then alsoaab
, skipping the input "3" because there was no second character following it.The fix is to replace this:
with this:
…so that you only apply the recursion when there are at least two digits, and that digit pair has a mapping. Now it will output the correct result. It is true you could rely on the
undefined
behaviour and leave out the length check, but the check on the existence of the mapping is a requirement for going into recursion. The call should not be made otherwise.Other remarks
Don’t use a global variable to collect the results. This is not good and would give you wrong results when you call the function multiple times from the main program.
I would not pass the partial code (
newStr
) as an argument to the recursive call. It seems more elegant to have the recursive call return the possible codes for the substring it received, and let the caller do the job of prefixing those with the current letter. That way it works from the inside-out (post order processing)Passing the start index is fine, but JavaScript is quite efficient with slicing strings (using string kerning), so you could just pass the string slice instead of the original string. This reduces the number of arguments to just one.
Instead of building the result in such a way that the function returns the list of codes (which is way better than populating a global variable), you could also yield the codes using a generator.
You don’t really need the alphabet map, as it is straightforward to do the decoding directly with
String.fromCharCode
.Here is how that would turn out:
Here’s an approach with generators. I find
yield
expressions allow me to think about the program from a higher level. Another advantage is the generator is pauseable/cancelable so that you do not need to generate the entire result set if it’s not needed.This code is nicely arranged to handle errors. Update the
combine
function to –Now when the input is invalid –
Here’s a more generic non-recursive approach (basically, BFS the tree instead of DFS).
The function expects a
Map
prefix => code
and a source string. Prefixes and codes can be whatever, not necessary digits or letters.