Find all letter Combinations of a Phone NumberAlgorithm to Iterate All Possible Strings in ClojureLetter...
Is Dumbledore a human lie detector?
Confused with atmospheric pressure equals plastic balloon’s inner pressure
What are the unintended or dangerous consequences of allowing spells that target and damage creatures to also target and damage objects?
Can you make an identity from this product?
Why is the length of the Kelvin unit of temperature equal to that of the Celsius unit?
Rail-to-rail op-amp only reaches 90% of VCC, works sometimes, not everytime
How to get depth and other lengths of a font?
Why is long-term living in Almost-Earth causing severe health problems?
What is the reason for setting flaps 1 on the ground at high temperatures?
Should I refuse to be named as co-author of a low quality paper?
noalign caused by multirow and colors
Proving that a Russian cryptographic standard is too structured
Are the guests in Westworld forbidden to tell the hosts that they are robots?
If someone intimidates another person, does the person affected gain the Frightened condition?
Multiband vertical antenna not working as expected
Remove border lines of SRTM tiles rendered as hillshade
Find all letter Combinations of a Phone Number
Make Gimbap cutter
Three questions
Do you have to have figures when playing D&D?
Seasonality after 1st differencing
As easy as Three, Two, One... How fast can you go from Five to Four?
Grep Match and extract
ASCII Meme Arrow Generator
Find all letter Combinations of a Phone Number
Algorithm to Iterate All Possible Strings in ClojureLetter combinations of phone dial pad numberLeetcode 17. Letter Combinations of a Phone NumberFind number of combinations of stringsListing all possible names from a phone numberDecode the Morse CodeFinding all possible letter combinations from an inputted phone numberPrint out all possible letter combinations a given phone number can representFind index of the nearest larger number of a numberFind the smallest distance between any two given words in a string
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
$begingroup$
The task
is taken from LeetCode
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. A
mapping of digit to letters (just like on the telephone buttons) is
given below. Note that 1 does not map to any letters.
Example:
Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
Note:
Although the above answer is in lexicographical order, your answer could be in any order you want.
My solution
(I've been told I should provide additional information to my solution otherwise I get downvoted.)
is based on backtracking. Why I choosed that approach? Well, whenever you encounter a task where you have to combine or permutate things, then backtracking is a possible approach.
But if you know a better one, then please go ahead. Otherwise I only got generic questions to my code: Can you make it faster, cleaner, more readable, etc.?
Also I'm interested in functional programming. So, if you got a good functional approach, feel free to post it here, too. Other than that, I'm always interested in a different perspective. So, if you got a fancy or conservative approach, please feel free to post them here.
Imperative approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
if (digits.length === 1) { return [...strDigits[digits]]; }
const res = [];
const combine = (cur, n) => {
if (cur.length === digits.length) {
res.push(cur);
return;
}
[...strDigits[digits[n]]].forEach(x => {
combine(cur + x, n + 1);
});
};
combine('', 0);
return res;
};
Functional approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
const combine = (cur, n) => cur.length === digits.length
? cur
: [...strDigits[digits[n]]].flatMap(x => combine(cur + x, n + 1));
return combine('', 0);
};
javascript algorithm programming-challenge functional-programming
$endgroup$
add a comment |
$begingroup$
The task
is taken from LeetCode
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. A
mapping of digit to letters (just like on the telephone buttons) is
given below. Note that 1 does not map to any letters.
Example:
Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
Note:
Although the above answer is in lexicographical order, your answer could be in any order you want.
My solution
(I've been told I should provide additional information to my solution otherwise I get downvoted.)
is based on backtracking. Why I choosed that approach? Well, whenever you encounter a task where you have to combine or permutate things, then backtracking is a possible approach.
But if you know a better one, then please go ahead. Otherwise I only got generic questions to my code: Can you make it faster, cleaner, more readable, etc.?
Also I'm interested in functional programming. So, if you got a good functional approach, feel free to post it here, too. Other than that, I'm always interested in a different perspective. So, if you got a fancy or conservative approach, please feel free to post them here.
Imperative approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
if (digits.length === 1) { return [...strDigits[digits]]; }
const res = [];
const combine = (cur, n) => {
if (cur.length === digits.length) {
res.push(cur);
return;
}
[...strDigits[digits[n]]].forEach(x => {
combine(cur + x, n + 1);
});
};
combine('', 0);
return res;
};
Functional approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
const combine = (cur, n) => cur.length === digits.length
? cur
: [...strDigits[digits[n]]].flatMap(x => combine(cur + x, n + 1));
return combine('', 0);
};
javascript algorithm programming-challenge functional-programming
$endgroup$
add a comment |
$begingroup$
The task
is taken from LeetCode
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. A
mapping of digit to letters (just like on the telephone buttons) is
given below. Note that 1 does not map to any letters.
Example:
Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
Note:
Although the above answer is in lexicographical order, your answer could be in any order you want.
My solution
(I've been told I should provide additional information to my solution otherwise I get downvoted.)
is based on backtracking. Why I choosed that approach? Well, whenever you encounter a task where you have to combine or permutate things, then backtracking is a possible approach.
But if you know a better one, then please go ahead. Otherwise I only got generic questions to my code: Can you make it faster, cleaner, more readable, etc.?
Also I'm interested in functional programming. So, if you got a good functional approach, feel free to post it here, too. Other than that, I'm always interested in a different perspective. So, if you got a fancy or conservative approach, please feel free to post them here.
Imperative approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
if (digits.length === 1) { return [...strDigits[digits]]; }
const res = [];
const combine = (cur, n) => {
if (cur.length === digits.length) {
res.push(cur);
return;
}
[...strDigits[digits[n]]].forEach(x => {
combine(cur + x, n + 1);
});
};
combine('', 0);
return res;
};
Functional approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
const combine = (cur, n) => cur.length === digits.length
? cur
: [...strDigits[digits[n]]].flatMap(x => combine(cur + x, n + 1));
return combine('', 0);
};
javascript algorithm programming-challenge functional-programming
$endgroup$
The task
is taken from LeetCode
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. A
mapping of digit to letters (just like on the telephone buttons) is
given below. Note that 1 does not map to any letters.
Example:
Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
Note:
Although the above answer is in lexicographical order, your answer could be in any order you want.
My solution
(I've been told I should provide additional information to my solution otherwise I get downvoted.)
is based on backtracking. Why I choosed that approach? Well, whenever you encounter a task where you have to combine or permutate things, then backtracking is a possible approach.
But if you know a better one, then please go ahead. Otherwise I only got generic questions to my code: Can you make it faster, cleaner, more readable, etc.?
Also I'm interested in functional programming. So, if you got a good functional approach, feel free to post it here, too. Other than that, I'm always interested in a different perspective. So, if you got a fancy or conservative approach, please feel free to post them here.
Imperative approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
if (digits.length === 1) { return [...strDigits[digits]]; }
const res = [];
const combine = (cur, n) => {
if (cur.length === digits.length) {
res.push(cur);
return;
}
[...strDigits[digits[n]]].forEach(x => {
combine(cur + x, n + 1);
});
};
combine('', 0);
return res;
};
Functional approach
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function(digits) {
if (digits === '') { return []; }
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
};
const combine = (cur, n) => cur.length === digits.length
? cur
: [...strDigits[digits[n]]].flatMap(x => combine(cur + x, n + 1));
return combine('', 0);
};
javascript algorithm programming-challenge functional-programming
javascript algorithm programming-challenge functional-programming
edited 8 hours ago
thadeuszlay
asked 8 hours ago
thadeuszlaythadeuszlay
1,612718
1,612718
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
whenever you encounter a task where you have to combine or permute things, then backtracking is a possible approach.
True. It is not necessarily the best though (in fact it is rarely the best).
In this particular case, the problem naturally maps to finding the next combination. In turn, it is no more than an increment of a number in some fancy numbering system. In your example, the minimal possible string is ad
. Subsequent increments yield ae
, then af
, then ('f' + 1
is d
and a carry bit does carry) bd
, etc.
Consider implementing the increment/next
method directly. The space complexity will definitely benefit; and it is trivial to convert into a generator. The time complexity is likely to also benefit, depending on the use case.
PS: thank you for posting your train of thoughts.
$endgroup$
add a comment |
$begingroup$
I think your functional approach is very nice, and scores high points for readability. As long as the recursion wasn't causing performance issues, that's how I'd approach it.
However, it's not very efficient, and as vnp points out, listing permutations is really just a matter of counting from 1 to "the number of combinations" in a mixed base numbering system.
mixed base counting
These are easy implement, and it's worth going through the exercise, because assuming you have a utility that does the mixed base counting for you, the solution to the original problem will be both straightforward and efficient:
function mixedBaseCounter(bases) {
let cnt = 0
let maxCnt = bases.length ? [...bases].reduce((m, x) => m * x, 1) : 0
let digits = bases.map(() => 0)
const increment = (i = 0) => {
digits[i] = (digits[i] + 1) % bases[i]
if (digits[i] == 0) increment(i + 1)
}
return {
[Symbol.iterator]: function* () {
while (cnt++ < maxCnt) {
yield digits.join('')
increment()
}
}
}
}
This uses ECMA script's iterable interface. Note the above implementation has the least significant bit on the left (easily changed, if you need to).
Let's verify it counts correctly in binary:
[...mixedBaseCounter([2, 2, 2])]
// [ '000', '100', '010', '110', '001', '101', '011', '111' ]
And that it handles mixed bases:
console.log([...mixedBaseCounter([2, 3])])
// [ '00', '10', '01', '11', '02', '12' ]
Try it online!
applying it to the problem
Now the solutions becomes:
function letterCombinations(digits) {
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
}
const letterOptions = [...digits].map(x => strDigits[x])
const bases = [...letterOptions].map(x => x.length)
const masks = mixedBaseCounter(bases)
return [...masks].map(m =>
[...m].map((x, i) => letterOptions[i][x]).join('')
)
}
where each "mask" (or number, within the mixed base numbering system) chooses one combination.
Note also we no longer need to treat the empty string as a special case.
Try it online!
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f221976%2ffind-all-letter-combinations-of-a-phone-number%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
whenever you encounter a task where you have to combine or permute things, then backtracking is a possible approach.
True. It is not necessarily the best though (in fact it is rarely the best).
In this particular case, the problem naturally maps to finding the next combination. In turn, it is no more than an increment of a number in some fancy numbering system. In your example, the minimal possible string is ad
. Subsequent increments yield ae
, then af
, then ('f' + 1
is d
and a carry bit does carry) bd
, etc.
Consider implementing the increment/next
method directly. The space complexity will definitely benefit; and it is trivial to convert into a generator. The time complexity is likely to also benefit, depending on the use case.
PS: thank you for posting your train of thoughts.
$endgroup$
add a comment |
$begingroup$
whenever you encounter a task where you have to combine or permute things, then backtracking is a possible approach.
True. It is not necessarily the best though (in fact it is rarely the best).
In this particular case, the problem naturally maps to finding the next combination. In turn, it is no more than an increment of a number in some fancy numbering system. In your example, the minimal possible string is ad
. Subsequent increments yield ae
, then af
, then ('f' + 1
is d
and a carry bit does carry) bd
, etc.
Consider implementing the increment/next
method directly. The space complexity will definitely benefit; and it is trivial to convert into a generator. The time complexity is likely to also benefit, depending on the use case.
PS: thank you for posting your train of thoughts.
$endgroup$
add a comment |
$begingroup$
whenever you encounter a task where you have to combine or permute things, then backtracking is a possible approach.
True. It is not necessarily the best though (in fact it is rarely the best).
In this particular case, the problem naturally maps to finding the next combination. In turn, it is no more than an increment of a number in some fancy numbering system. In your example, the minimal possible string is ad
. Subsequent increments yield ae
, then af
, then ('f' + 1
is d
and a carry bit does carry) bd
, etc.
Consider implementing the increment/next
method directly. The space complexity will definitely benefit; and it is trivial to convert into a generator. The time complexity is likely to also benefit, depending on the use case.
PS: thank you for posting your train of thoughts.
$endgroup$
whenever you encounter a task where you have to combine or permute things, then backtracking is a possible approach.
True. It is not necessarily the best though (in fact it is rarely the best).
In this particular case, the problem naturally maps to finding the next combination. In turn, it is no more than an increment of a number in some fancy numbering system. In your example, the minimal possible string is ad
. Subsequent increments yield ae
, then af
, then ('f' + 1
is d
and a carry bit does carry) bd
, etc.
Consider implementing the increment/next
method directly. The space complexity will definitely benefit; and it is trivial to convert into a generator. The time complexity is likely to also benefit, depending on the use case.
PS: thank you for posting your train of thoughts.
answered 5 hours ago
vnpvnp
41.5k234106
41.5k234106
add a comment |
add a comment |
$begingroup$
I think your functional approach is very nice, and scores high points for readability. As long as the recursion wasn't causing performance issues, that's how I'd approach it.
However, it's not very efficient, and as vnp points out, listing permutations is really just a matter of counting from 1 to "the number of combinations" in a mixed base numbering system.
mixed base counting
These are easy implement, and it's worth going through the exercise, because assuming you have a utility that does the mixed base counting for you, the solution to the original problem will be both straightforward and efficient:
function mixedBaseCounter(bases) {
let cnt = 0
let maxCnt = bases.length ? [...bases].reduce((m, x) => m * x, 1) : 0
let digits = bases.map(() => 0)
const increment = (i = 0) => {
digits[i] = (digits[i] + 1) % bases[i]
if (digits[i] == 0) increment(i + 1)
}
return {
[Symbol.iterator]: function* () {
while (cnt++ < maxCnt) {
yield digits.join('')
increment()
}
}
}
}
This uses ECMA script's iterable interface. Note the above implementation has the least significant bit on the left (easily changed, if you need to).
Let's verify it counts correctly in binary:
[...mixedBaseCounter([2, 2, 2])]
// [ '000', '100', '010', '110', '001', '101', '011', '111' ]
And that it handles mixed bases:
console.log([...mixedBaseCounter([2, 3])])
// [ '00', '10', '01', '11', '02', '12' ]
Try it online!
applying it to the problem
Now the solutions becomes:
function letterCombinations(digits) {
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
}
const letterOptions = [...digits].map(x => strDigits[x])
const bases = [...letterOptions].map(x => x.length)
const masks = mixedBaseCounter(bases)
return [...masks].map(m =>
[...m].map((x, i) => letterOptions[i][x]).join('')
)
}
where each "mask" (or number, within the mixed base numbering system) chooses one combination.
Note also we no longer need to treat the empty string as a special case.
Try it online!
$endgroup$
add a comment |
$begingroup$
I think your functional approach is very nice, and scores high points for readability. As long as the recursion wasn't causing performance issues, that's how I'd approach it.
However, it's not very efficient, and as vnp points out, listing permutations is really just a matter of counting from 1 to "the number of combinations" in a mixed base numbering system.
mixed base counting
These are easy implement, and it's worth going through the exercise, because assuming you have a utility that does the mixed base counting for you, the solution to the original problem will be both straightforward and efficient:
function mixedBaseCounter(bases) {
let cnt = 0
let maxCnt = bases.length ? [...bases].reduce((m, x) => m * x, 1) : 0
let digits = bases.map(() => 0)
const increment = (i = 0) => {
digits[i] = (digits[i] + 1) % bases[i]
if (digits[i] == 0) increment(i + 1)
}
return {
[Symbol.iterator]: function* () {
while (cnt++ < maxCnt) {
yield digits.join('')
increment()
}
}
}
}
This uses ECMA script's iterable interface. Note the above implementation has the least significant bit on the left (easily changed, if you need to).
Let's verify it counts correctly in binary:
[...mixedBaseCounter([2, 2, 2])]
// [ '000', '100', '010', '110', '001', '101', '011', '111' ]
And that it handles mixed bases:
console.log([...mixedBaseCounter([2, 3])])
// [ '00', '10', '01', '11', '02', '12' ]
Try it online!
applying it to the problem
Now the solutions becomes:
function letterCombinations(digits) {
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
}
const letterOptions = [...digits].map(x => strDigits[x])
const bases = [...letterOptions].map(x => x.length)
const masks = mixedBaseCounter(bases)
return [...masks].map(m =>
[...m].map((x, i) => letterOptions[i][x]).join('')
)
}
where each "mask" (or number, within the mixed base numbering system) chooses one combination.
Note also we no longer need to treat the empty string as a special case.
Try it online!
$endgroup$
add a comment |
$begingroup$
I think your functional approach is very nice, and scores high points for readability. As long as the recursion wasn't causing performance issues, that's how I'd approach it.
However, it's not very efficient, and as vnp points out, listing permutations is really just a matter of counting from 1 to "the number of combinations" in a mixed base numbering system.
mixed base counting
These are easy implement, and it's worth going through the exercise, because assuming you have a utility that does the mixed base counting for you, the solution to the original problem will be both straightforward and efficient:
function mixedBaseCounter(bases) {
let cnt = 0
let maxCnt = bases.length ? [...bases].reduce((m, x) => m * x, 1) : 0
let digits = bases.map(() => 0)
const increment = (i = 0) => {
digits[i] = (digits[i] + 1) % bases[i]
if (digits[i] == 0) increment(i + 1)
}
return {
[Symbol.iterator]: function* () {
while (cnt++ < maxCnt) {
yield digits.join('')
increment()
}
}
}
}
This uses ECMA script's iterable interface. Note the above implementation has the least significant bit on the left (easily changed, if you need to).
Let's verify it counts correctly in binary:
[...mixedBaseCounter([2, 2, 2])]
// [ '000', '100', '010', '110', '001', '101', '011', '111' ]
And that it handles mixed bases:
console.log([...mixedBaseCounter([2, 3])])
// [ '00', '10', '01', '11', '02', '12' ]
Try it online!
applying it to the problem
Now the solutions becomes:
function letterCombinations(digits) {
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
}
const letterOptions = [...digits].map(x => strDigits[x])
const bases = [...letterOptions].map(x => x.length)
const masks = mixedBaseCounter(bases)
return [...masks].map(m =>
[...m].map((x, i) => letterOptions[i][x]).join('')
)
}
where each "mask" (or number, within the mixed base numbering system) chooses one combination.
Note also we no longer need to treat the empty string as a special case.
Try it online!
$endgroup$
I think your functional approach is very nice, and scores high points for readability. As long as the recursion wasn't causing performance issues, that's how I'd approach it.
However, it's not very efficient, and as vnp points out, listing permutations is really just a matter of counting from 1 to "the number of combinations" in a mixed base numbering system.
mixed base counting
These are easy implement, and it's worth going through the exercise, because assuming you have a utility that does the mixed base counting for you, the solution to the original problem will be both straightforward and efficient:
function mixedBaseCounter(bases) {
let cnt = 0
let maxCnt = bases.length ? [...bases].reduce((m, x) => m * x, 1) : 0
let digits = bases.map(() => 0)
const increment = (i = 0) => {
digits[i] = (digits[i] + 1) % bases[i]
if (digits[i] == 0) increment(i + 1)
}
return {
[Symbol.iterator]: function* () {
while (cnt++ < maxCnt) {
yield digits.join('')
increment()
}
}
}
}
This uses ECMA script's iterable interface. Note the above implementation has the least significant bit on the left (easily changed, if you need to).
Let's verify it counts correctly in binary:
[...mixedBaseCounter([2, 2, 2])]
// [ '000', '100', '010', '110', '001', '101', '011', '111' ]
And that it handles mixed bases:
console.log([...mixedBaseCounter([2, 3])])
// [ '00', '10', '01', '11', '02', '12' ]
Try it online!
applying it to the problem
Now the solutions becomes:
function letterCombinations(digits) {
const strDigits = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz',
}
const letterOptions = [...digits].map(x => strDigits[x])
const bases = [...letterOptions].map(x => x.length)
const masks = mixedBaseCounter(bases)
return [...masks].map(m =>
[...m].map((x, i) => letterOptions[i][x]).join('')
)
}
where each "mask" (or number, within the mixed base numbering system) chooses one combination.
Note also we no longer need to treat the empty string as a special case.
Try it online!
answered 5 mins ago
JonahJonah
3,651719
3,651719
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f221976%2ffind-all-letter-combinations-of-a-phone-number%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown