Find all letter Combinations of a Phone NumberAlgorithm to Iterate All Possible Strings in ClojureLetter combinations of phone dial pad numberLeetcode 17. Letter Combinations of a Phone NumberFinding all possible letter combinations from an inputted phone numberPrint out all possible letter combinations a given phone number can representGiven a set of closed intervals, find the smallest set of numbers that covers all the intervalsFind index of the nearest larger number of a numberFind the smallest distance between any two given words in a stringFind common characters (LeetCode)Find All Numbers Disappeared in an Array

Row to remove the dotted white border around focused button text

Why is the Turkish president's surname spelt in Russian as Эрдоган, with г?

One folder two different locations on ubuntu 18.04

Zombie diet, why humans?

MH370 blackbox - is it still possible to retrieve data from it?

Analog is Obtuse!

Which centaur is more 'official'?

For people who believe in Jesus and not the devil, what happend in the desert?

Wilcoxon signed rank test – critical value for n>50

Spicket or spigot?

Did Chinese school textbook maps (c. 1951) "depict China as stretching even into the central Asian republics"?

Why do I have to press the shutter button twice on my Canon 6d Mark II?

Is なきにしもあらず~なきに a set phrase?

How to assign a Python list to a vim variable and escape its strings correctly

How can I create ribbons like these in Microsoft word 2010?

How would a order of Monks that renounce their names communicate effectively?

Coefficients of the characteristic polynomial

How exactly is a normal force exerted, at the molecular level?

In native German words, is Q always followed by U, as in English?

The difference between Rad1 and Rfd1

Cross over of arrows in a complex diagram

Polish letters in ASME English template

Are there any vegetarian astronauts?

How was film developed in the late 1920s?



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 NumberFinding all possible letter combinations from an inputted phone numberPrint out all possible letter combinations a given phone number can representGiven a set of closed intervals, find the smallest set of numbers that covers all the intervalsFind index of the nearest larger number of a numberFind the smallest distance between any two given words in a stringFind common characters (LeetCode)Find All Numbers Disappeared in an Array






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








6












$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.



enter image description here



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);
;









share|improve this question











$endgroup$


















    6












    $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.



    enter image description here



    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);
    ;









    share|improve this question











    $endgroup$














      6












      6








      6


      2



      $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.



      enter image description here



      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);
      ;









      share|improve this question











      $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.



      enter image description here



      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 ecmascript-6






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jun 10 at 17:18









      Sᴀᴍ Onᴇᴌᴀ

      12.1k6 gold badges25 silver badges83 bronze badges




      12.1k6 gold badges25 silver badges83 bronze badges










      asked Jun 9 at 21:42









      thadeuszlaythadeuszlay

      1,7267 silver badges18 bronze badges




      1,7267 silver badges18 bronze badges




















          4 Answers
          4






          active

          oldest

          votes


















          1












          $begingroup$

          Make it faster



          This is only improvements on the algorithm you have used.



          There are a few ways to improve the performance. The majority are minor a few % points improvement but due to the complexity ~$O(3^n)$ quickly sum up to be worth the effort.



          The last step



          There is a very common mistake in recursive code that will nearly always give a huge performance gain (30-40% in this case).



          First think about why you are using a recursive function. In this case you are using it to hold the current cur combination until it is complete. In other words you are using the recursive call to place cur on a stack, the stack being the call stack. The call stack is easy to use (transparent) but comes with the cost of stacking everything within the functions context, including all that is not relevant to the next step.



          The common error in recursion is to place the exit clause at the beginning of the function.
          In the snippet below, you recurse at #A with cur + x which pushes a new context to the call stack, then at point #B you check to see if cur + x now as cur is the final length to exit.



          So at point #A you have all the information needed to know whether or not to exit, thus wasting the creation on a new context.



          Redundant recursion call



           // For seven digit number (ignoring 9) the following calls combine 3280
          const combine = (cur, n) =>
          if (cur.length === digits.length) // code point #B
          res.push(cur);
          return;

          [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1)); // code point #A
          ;


          Exit one recursion step earlier



          To avoid that extra call by testing for the length early



           // For seven digit number (ignoring 9) the following calls combine 1093 times
          const FINAL_LEN = digits.length - 1
          const combine = (cur, n) =>
          if (cur.length === FINAL_LEN) // only one char remaining to add
          [...strDigits[digits[n]]].forEach(x => res.push(cur + x));

          else [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1))
          ;


          So for a 7 digit number (ignoring "9") the complexity is $O(3^n)$ saving 2187 needless pushes to the call stack. About a 30-40% saving in performance.



          Example ~40% performance improvement



          The following example has a ~40% improvement in performance when processing a set of random number strings in the range 2 - 79999999. (I did note that as the range grew there was a small drop in the improvement with a ~37% for 10 digits, and 34% for 12 digits and is likely due to the array size growth and memory management overhead)



          Other improvements.



          • Avoid capturing (closure) of function's combine state by using for loops.

          • Using an pre processed array of digits, avoiding the need to build the array [...strDigits[digits[n]]]

          • Convert the digits string to numbers and use indexed array to lookup characters to reduce JS hashing to locate items in strDigits

          The rest of the changes are just style as performance testing iteration required many versions and my style slowly intruded into the final iteration



          I moved ALPHAS previously strDigits outside the function because that is my style (avoid repeated processing) and does not provide an improvement. You would normally capture it in a IIF closure.



          const ALPHAS = ",,abc,def,ghi,jkl,mno,pqrs,tuv,wxyz".split(",").map(a => [...a]);
          function letterCombinations(numStr)
          const alphas = ALPHAS; // local scope reference for shallower scope search
          const nums = [...numStr].map(Number);
          const res = [];
          const count = nums.length - 1;
          (function combine(n, cur)
          if (n === count)
          for (const char of alphas[nums[n]]) res.push(cur + char)
          else
          for (const char of alphas[nums[n]]) combine(n + 1, cur + char)

          )(0, "");
          return res;



          Functional



          My distaste for functional code has another example of why it is to be avoided and decried at every opportunity. You functional code a an order of magnitude slower than your imperative solution.



          Not only was it slow but the fans on the machine kicked in so I looked at the load. There was the equivalent to an extra core running near flat out (JS GC and memory management threads) and power consumption jumped by 20W!!! (thats a 3rd of a solar panel) over running the tests without the functional solution.



          The list below shows the comparative results for set of random digits to 8 digits long. (an operation is one test cycle)



          BM67....: Time: 3,875µs OPS: 258 100% Total time: 3,798ms for 980 operations
          OP imp..: Time: 6,370µs OPS: 156 60% Total time: 6,307ms for 990 operations
          OP func.: Time: 84,988µs OPS: 11 4% Total time: 82,439ms for 970 operations





          share|improve this answer









          $endgroup$












          • $begingroup$
            Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
            $endgroup$
            – thadeuszlay
            Jun 10 at 20:13










          • $begingroup$
            BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
            $endgroup$
            – thadeuszlay
            Jun 10 at 20:38










          • $begingroup$
            @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
            $endgroup$
            – Blindman67
            Jun 10 at 20:53










          • $begingroup$
            I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
            $endgroup$
            – thadeuszlay
            Jun 10 at 21:00











          • $begingroup$
            @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
            $endgroup$
            – Blindman67
            Jun 10 at 21:18


















          8












          $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.






          share|improve this answer









          $endgroup$




















            4












            $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!






            share|improve this answer









            $endgroup$




















              2












              $begingroup$

              I see a couple of simplifications that could be made:



              A feature of ecmascript-6 that could be used here is default parameters:



              const combine = (cur = '', n = 0) => {


              Then the first call to that function doesn't need to pass any parameters:



              combine();



              The arrow function in the imperative approach feels too brief to warrant the need for brackets:




              [...strDigits[digits[n]]].forEach(x => 
              combine(cur + x, n + 1);
              );



              So that could be shortened to a single line:



              [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1));


              But if that is longer than your desired maximum length of a line of code, pull out the arrow function:



              const combineNext = x => combine(cur + x, n + 1);
              [...strDigits[digits[n]]].forEach(combineNext);


              Actually, in an imperative solution, I might expect to see a for...of loop, which should be faster:



              for (const x of [...strDigits[digits[n]]]) 
              combine(cur + x, n + 1);




              I considered suggesting that the keys of strDigits be integers instead of string literals, each element in digits would be a string so the types would then be different.






              share|improve this answer











              $endgroup$















                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
                );



                );













                draft saved

                draft discarded


















                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

























                4 Answers
                4






                active

                oldest

                votes








                4 Answers
                4






                active

                oldest

                votes









                active

                oldest

                votes






                active

                oldest

                votes









                1












                $begingroup$

                Make it faster



                This is only improvements on the algorithm you have used.



                There are a few ways to improve the performance. The majority are minor a few % points improvement but due to the complexity ~$O(3^n)$ quickly sum up to be worth the effort.



                The last step



                There is a very common mistake in recursive code that will nearly always give a huge performance gain (30-40% in this case).



                First think about why you are using a recursive function. In this case you are using it to hold the current cur combination until it is complete. In other words you are using the recursive call to place cur on a stack, the stack being the call stack. The call stack is easy to use (transparent) but comes with the cost of stacking everything within the functions context, including all that is not relevant to the next step.



                The common error in recursion is to place the exit clause at the beginning of the function.
                In the snippet below, you recurse at #A with cur + x which pushes a new context to the call stack, then at point #B you check to see if cur + x now as cur is the final length to exit.



                So at point #A you have all the information needed to know whether or not to exit, thus wasting the creation on a new context.



                Redundant recursion call



                 // For seven digit number (ignoring 9) the following calls combine 3280
                const combine = (cur, n) =>
                if (cur.length === digits.length) // code point #B
                res.push(cur);
                return;

                [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1)); // code point #A
                ;


                Exit one recursion step earlier



                To avoid that extra call by testing for the length early



                 // For seven digit number (ignoring 9) the following calls combine 1093 times
                const FINAL_LEN = digits.length - 1
                const combine = (cur, n) =>
                if (cur.length === FINAL_LEN) // only one char remaining to add
                [...strDigits[digits[n]]].forEach(x => res.push(cur + x));

                else [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1))
                ;


                So for a 7 digit number (ignoring "9") the complexity is $O(3^n)$ saving 2187 needless pushes to the call stack. About a 30-40% saving in performance.



                Example ~40% performance improvement



                The following example has a ~40% improvement in performance when processing a set of random number strings in the range 2 - 79999999. (I did note that as the range grew there was a small drop in the improvement with a ~37% for 10 digits, and 34% for 12 digits and is likely due to the array size growth and memory management overhead)



                Other improvements.



                • Avoid capturing (closure) of function's combine state by using for loops.

                • Using an pre processed array of digits, avoiding the need to build the array [...strDigits[digits[n]]]

                • Convert the digits string to numbers and use indexed array to lookup characters to reduce JS hashing to locate items in strDigits

                The rest of the changes are just style as performance testing iteration required many versions and my style slowly intruded into the final iteration



                I moved ALPHAS previously strDigits outside the function because that is my style (avoid repeated processing) and does not provide an improvement. You would normally capture it in a IIF closure.



                const ALPHAS = ",,abc,def,ghi,jkl,mno,pqrs,tuv,wxyz".split(",").map(a => [...a]);
                function letterCombinations(numStr)
                const alphas = ALPHAS; // local scope reference for shallower scope search
                const nums = [...numStr].map(Number);
                const res = [];
                const count = nums.length - 1;
                (function combine(n, cur)
                if (n === count)
                for (const char of alphas[nums[n]]) res.push(cur + char)
                else
                for (const char of alphas[nums[n]]) combine(n + 1, cur + char)

                )(0, "");
                return res;



                Functional



                My distaste for functional code has another example of why it is to be avoided and decried at every opportunity. You functional code a an order of magnitude slower than your imperative solution.



                Not only was it slow but the fans on the machine kicked in so I looked at the load. There was the equivalent to an extra core running near flat out (JS GC and memory management threads) and power consumption jumped by 20W!!! (thats a 3rd of a solar panel) over running the tests without the functional solution.



                The list below shows the comparative results for set of random digits to 8 digits long. (an operation is one test cycle)



                BM67....: Time: 3,875µs OPS: 258 100% Total time: 3,798ms for 980 operations
                OP imp..: Time: 6,370µs OPS: 156 60% Total time: 6,307ms for 990 operations
                OP func.: Time: 84,988µs OPS: 11 4% Total time: 82,439ms for 970 operations





                share|improve this answer









                $endgroup$












                • $begingroup$
                  Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:13










                • $begingroup$
                  BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:38










                • $begingroup$
                  @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 20:53










                • $begingroup$
                  I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 21:00











                • $begingroup$
                  @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 21:18















                1












                $begingroup$

                Make it faster



                This is only improvements on the algorithm you have used.



                There are a few ways to improve the performance. The majority are minor a few % points improvement but due to the complexity ~$O(3^n)$ quickly sum up to be worth the effort.



                The last step



                There is a very common mistake in recursive code that will nearly always give a huge performance gain (30-40% in this case).



                First think about why you are using a recursive function. In this case you are using it to hold the current cur combination until it is complete. In other words you are using the recursive call to place cur on a stack, the stack being the call stack. The call stack is easy to use (transparent) but comes with the cost of stacking everything within the functions context, including all that is not relevant to the next step.



                The common error in recursion is to place the exit clause at the beginning of the function.
                In the snippet below, you recurse at #A with cur + x which pushes a new context to the call stack, then at point #B you check to see if cur + x now as cur is the final length to exit.



                So at point #A you have all the information needed to know whether or not to exit, thus wasting the creation on a new context.



                Redundant recursion call



                 // For seven digit number (ignoring 9) the following calls combine 3280
                const combine = (cur, n) =>
                if (cur.length === digits.length) // code point #B
                res.push(cur);
                return;

                [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1)); // code point #A
                ;


                Exit one recursion step earlier



                To avoid that extra call by testing for the length early



                 // For seven digit number (ignoring 9) the following calls combine 1093 times
                const FINAL_LEN = digits.length - 1
                const combine = (cur, n) =>
                if (cur.length === FINAL_LEN) // only one char remaining to add
                [...strDigits[digits[n]]].forEach(x => res.push(cur + x));

                else [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1))
                ;


                So for a 7 digit number (ignoring "9") the complexity is $O(3^n)$ saving 2187 needless pushes to the call stack. About a 30-40% saving in performance.



                Example ~40% performance improvement



                The following example has a ~40% improvement in performance when processing a set of random number strings in the range 2 - 79999999. (I did note that as the range grew there was a small drop in the improvement with a ~37% for 10 digits, and 34% for 12 digits and is likely due to the array size growth and memory management overhead)



                Other improvements.



                • Avoid capturing (closure) of function's combine state by using for loops.

                • Using an pre processed array of digits, avoiding the need to build the array [...strDigits[digits[n]]]

                • Convert the digits string to numbers and use indexed array to lookup characters to reduce JS hashing to locate items in strDigits

                The rest of the changes are just style as performance testing iteration required many versions and my style slowly intruded into the final iteration



                I moved ALPHAS previously strDigits outside the function because that is my style (avoid repeated processing) and does not provide an improvement. You would normally capture it in a IIF closure.



                const ALPHAS = ",,abc,def,ghi,jkl,mno,pqrs,tuv,wxyz".split(",").map(a => [...a]);
                function letterCombinations(numStr)
                const alphas = ALPHAS; // local scope reference for shallower scope search
                const nums = [...numStr].map(Number);
                const res = [];
                const count = nums.length - 1;
                (function combine(n, cur)
                if (n === count)
                for (const char of alphas[nums[n]]) res.push(cur + char)
                else
                for (const char of alphas[nums[n]]) combine(n + 1, cur + char)

                )(0, "");
                return res;



                Functional



                My distaste for functional code has another example of why it is to be avoided and decried at every opportunity. You functional code a an order of magnitude slower than your imperative solution.



                Not only was it slow but the fans on the machine kicked in so I looked at the load. There was the equivalent to an extra core running near flat out (JS GC and memory management threads) and power consumption jumped by 20W!!! (thats a 3rd of a solar panel) over running the tests without the functional solution.



                The list below shows the comparative results for set of random digits to 8 digits long. (an operation is one test cycle)



                BM67....: Time: 3,875µs OPS: 258 100% Total time: 3,798ms for 980 operations
                OP imp..: Time: 6,370µs OPS: 156 60% Total time: 6,307ms for 990 operations
                OP func.: Time: 84,988µs OPS: 11 4% Total time: 82,439ms for 970 operations





                share|improve this answer









                $endgroup$












                • $begingroup$
                  Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:13










                • $begingroup$
                  BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:38










                • $begingroup$
                  @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 20:53










                • $begingroup$
                  I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 21:00











                • $begingroup$
                  @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 21:18













                1












                1








                1





                $begingroup$

                Make it faster



                This is only improvements on the algorithm you have used.



                There are a few ways to improve the performance. The majority are minor a few % points improvement but due to the complexity ~$O(3^n)$ quickly sum up to be worth the effort.



                The last step



                There is a very common mistake in recursive code that will nearly always give a huge performance gain (30-40% in this case).



                First think about why you are using a recursive function. In this case you are using it to hold the current cur combination until it is complete. In other words you are using the recursive call to place cur on a stack, the stack being the call stack. The call stack is easy to use (transparent) but comes with the cost of stacking everything within the functions context, including all that is not relevant to the next step.



                The common error in recursion is to place the exit clause at the beginning of the function.
                In the snippet below, you recurse at #A with cur + x which pushes a new context to the call stack, then at point #B you check to see if cur + x now as cur is the final length to exit.



                So at point #A you have all the information needed to know whether or not to exit, thus wasting the creation on a new context.



                Redundant recursion call



                 // For seven digit number (ignoring 9) the following calls combine 3280
                const combine = (cur, n) =>
                if (cur.length === digits.length) // code point #B
                res.push(cur);
                return;

                [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1)); // code point #A
                ;


                Exit one recursion step earlier



                To avoid that extra call by testing for the length early



                 // For seven digit number (ignoring 9) the following calls combine 1093 times
                const FINAL_LEN = digits.length - 1
                const combine = (cur, n) =>
                if (cur.length === FINAL_LEN) // only one char remaining to add
                [...strDigits[digits[n]]].forEach(x => res.push(cur + x));

                else [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1))
                ;


                So for a 7 digit number (ignoring "9") the complexity is $O(3^n)$ saving 2187 needless pushes to the call stack. About a 30-40% saving in performance.



                Example ~40% performance improvement



                The following example has a ~40% improvement in performance when processing a set of random number strings in the range 2 - 79999999. (I did note that as the range grew there was a small drop in the improvement with a ~37% for 10 digits, and 34% for 12 digits and is likely due to the array size growth and memory management overhead)



                Other improvements.



                • Avoid capturing (closure) of function's combine state by using for loops.

                • Using an pre processed array of digits, avoiding the need to build the array [...strDigits[digits[n]]]

                • Convert the digits string to numbers and use indexed array to lookup characters to reduce JS hashing to locate items in strDigits

                The rest of the changes are just style as performance testing iteration required many versions and my style slowly intruded into the final iteration



                I moved ALPHAS previously strDigits outside the function because that is my style (avoid repeated processing) and does not provide an improvement. You would normally capture it in a IIF closure.



                const ALPHAS = ",,abc,def,ghi,jkl,mno,pqrs,tuv,wxyz".split(",").map(a => [...a]);
                function letterCombinations(numStr)
                const alphas = ALPHAS; // local scope reference for shallower scope search
                const nums = [...numStr].map(Number);
                const res = [];
                const count = nums.length - 1;
                (function combine(n, cur)
                if (n === count)
                for (const char of alphas[nums[n]]) res.push(cur + char)
                else
                for (const char of alphas[nums[n]]) combine(n + 1, cur + char)

                )(0, "");
                return res;



                Functional



                My distaste for functional code has another example of why it is to be avoided and decried at every opportunity. You functional code a an order of magnitude slower than your imperative solution.



                Not only was it slow but the fans on the machine kicked in so I looked at the load. There was the equivalent to an extra core running near flat out (JS GC and memory management threads) and power consumption jumped by 20W!!! (thats a 3rd of a solar panel) over running the tests without the functional solution.



                The list below shows the comparative results for set of random digits to 8 digits long. (an operation is one test cycle)



                BM67....: Time: 3,875µs OPS: 258 100% Total time: 3,798ms for 980 operations
                OP imp..: Time: 6,370µs OPS: 156 60% Total time: 6,307ms for 990 operations
                OP func.: Time: 84,988µs OPS: 11 4% Total time: 82,439ms for 970 operations





                share|improve this answer









                $endgroup$



                Make it faster



                This is only improvements on the algorithm you have used.



                There are a few ways to improve the performance. The majority are minor a few % points improvement but due to the complexity ~$O(3^n)$ quickly sum up to be worth the effort.



                The last step



                There is a very common mistake in recursive code that will nearly always give a huge performance gain (30-40% in this case).



                First think about why you are using a recursive function. In this case you are using it to hold the current cur combination until it is complete. In other words you are using the recursive call to place cur on a stack, the stack being the call stack. The call stack is easy to use (transparent) but comes with the cost of stacking everything within the functions context, including all that is not relevant to the next step.



                The common error in recursion is to place the exit clause at the beginning of the function.
                In the snippet below, you recurse at #A with cur + x which pushes a new context to the call stack, then at point #B you check to see if cur + x now as cur is the final length to exit.



                So at point #A you have all the information needed to know whether or not to exit, thus wasting the creation on a new context.



                Redundant recursion call



                 // For seven digit number (ignoring 9) the following calls combine 3280
                const combine = (cur, n) =>
                if (cur.length === digits.length) // code point #B
                res.push(cur);
                return;

                [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1)); // code point #A
                ;


                Exit one recursion step earlier



                To avoid that extra call by testing for the length early



                 // For seven digit number (ignoring 9) the following calls combine 1093 times
                const FINAL_LEN = digits.length - 1
                const combine = (cur, n) =>
                if (cur.length === FINAL_LEN) // only one char remaining to add
                [...strDigits[digits[n]]].forEach(x => res.push(cur + x));

                else [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1))
                ;


                So for a 7 digit number (ignoring "9") the complexity is $O(3^n)$ saving 2187 needless pushes to the call stack. About a 30-40% saving in performance.



                Example ~40% performance improvement



                The following example has a ~40% improvement in performance when processing a set of random number strings in the range 2 - 79999999. (I did note that as the range grew there was a small drop in the improvement with a ~37% for 10 digits, and 34% for 12 digits and is likely due to the array size growth and memory management overhead)



                Other improvements.



                • Avoid capturing (closure) of function's combine state by using for loops.

                • Using an pre processed array of digits, avoiding the need to build the array [...strDigits[digits[n]]]

                • Convert the digits string to numbers and use indexed array to lookup characters to reduce JS hashing to locate items in strDigits

                The rest of the changes are just style as performance testing iteration required many versions and my style slowly intruded into the final iteration



                I moved ALPHAS previously strDigits outside the function because that is my style (avoid repeated processing) and does not provide an improvement. You would normally capture it in a IIF closure.



                const ALPHAS = ",,abc,def,ghi,jkl,mno,pqrs,tuv,wxyz".split(",").map(a => [...a]);
                function letterCombinations(numStr)
                const alphas = ALPHAS; // local scope reference for shallower scope search
                const nums = [...numStr].map(Number);
                const res = [];
                const count = nums.length - 1;
                (function combine(n, cur)
                if (n === count)
                for (const char of alphas[nums[n]]) res.push(cur + char)
                else
                for (const char of alphas[nums[n]]) combine(n + 1, cur + char)

                )(0, "");
                return res;



                Functional



                My distaste for functional code has another example of why it is to be avoided and decried at every opportunity. You functional code a an order of magnitude slower than your imperative solution.



                Not only was it slow but the fans on the machine kicked in so I looked at the load. There was the equivalent to an extra core running near flat out (JS GC and memory management threads) and power consumption jumped by 20W!!! (thats a 3rd of a solar panel) over running the tests without the functional solution.



                The list below shows the comparative results for set of random digits to 8 digits long. (an operation is one test cycle)



                BM67....: Time: 3,875µs OPS: 258 100% Total time: 3,798ms for 980 operations
                OP imp..: Time: 6,370µs OPS: 156 60% Total time: 6,307ms for 990 operations
                OP func.: Time: 84,988µs OPS: 11 4% Total time: 82,439ms for 970 operations






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Jun 10 at 19:25









                Blindman67Blindman67

                11.9k1 gold badge6 silver badges25 bronze badges




                11.9k1 gold badge6 silver badges25 bronze badges











                • $begingroup$
                  Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:13










                • $begingroup$
                  BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:38










                • $begingroup$
                  @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 20:53










                • $begingroup$
                  I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 21:00











                • $begingroup$
                  @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 21:18
















                • $begingroup$
                  Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:13










                • $begingroup$
                  BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 20:38










                • $begingroup$
                  @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 20:53










                • $begingroup$
                  I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
                  $endgroup$
                  – thadeuszlay
                  Jun 10 at 21:00











                • $begingroup$
                  @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
                  $endgroup$
                  – Blindman67
                  Jun 10 at 21:18















                $begingroup$
                Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
                $endgroup$
                – thadeuszlay
                Jun 10 at 20:13




                $begingroup$
                Is there a use case where you would prefer functional over imperative (a general question and not related to this problem)?
                $endgroup$
                – thadeuszlay
                Jun 10 at 20:13












                $begingroup$
                BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
                $endgroup$
                – thadeuszlay
                Jun 10 at 20:38




                $begingroup$
                BTW: Some stuff you wrote are quite genius and i could have wrote it myself, e.g. using of IIFE, move the ALPHAS outside of the functions, ... . It's so clear once you see it, but when I wrote it I thought, my code was quite good. But it wasn't I still have to learn a lot .....and also learn to apply what I already know. Thanks again for pointing out my mistakes.
                $endgroup$
                – thadeuszlay
                Jun 10 at 20:38












                $begingroup$
                @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
                $endgroup$
                – Blindman67
                Jun 10 at 20:53




                $begingroup$
                @thadeuszlay There is no reason I would use a functional approach in javascript unless it (JS) was modified to allow function calls to be multi threaded. IE call to myFunc(...blah) is run in a new thread returning on an implied await. I do not buy the no side effects argument as it is defensive (protecting poor coding from itself) rather than encouraging good coding skills. There is good reason functional coding is a realm of academia and seldom seen in the industry sector as it provides no benefit over good traditional coding paradigms.
                $endgroup$
                – Blindman67
                Jun 10 at 20:53












                $begingroup$
                I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
                $endgroup$
                – thadeuszlay
                Jun 10 at 21:00





                $begingroup$
                I spent a few months learning FP, because I thought it would improve my coding skills, e.g. by reading books, solving puzzles using FP, etc.. I now feel it was a waste of my time. What would you recommend for someone who wants to improve his coding skills, e.g. some books, projects, ...?
                $endgroup$
                – thadeuszlay
                Jun 10 at 21:00













                $begingroup$
                @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
                $endgroup$
                – Blindman67
                Jun 10 at 21:18




                $begingroup$
                @thadeuszlay The best way to gain skill is to write, not just small solutions but big projects 10,000+ lines. Big is when bugs start to dominate and your skill to write well is paramount to keep it all working. If you are stuck for a project, write a game as they can be amongst the most difficult code challenges there are (write from the ground up, don't use frameworks or libraries). And always keep in mind segregate and ENCAPSULATE or you will spend more time hunting bugs than writing code.
                $endgroup$
                – Blindman67
                Jun 10 at 21:18













                8












                $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.






                share|improve this answer









                $endgroup$

















                  8












                  $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.






                  share|improve this answer









                  $endgroup$















                    8












                    8








                    8





                    $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.






                    share|improve this answer









                    $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.







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Jun 10 at 0:20









                    vnpvnp

                    41.9k2 gold badges35 silver badges107 bronze badges




                    41.9k2 gold badges35 silver badges107 bronze badges





















                        4












                        $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!






                        share|improve this answer









                        $endgroup$

















                          4












                          $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!






                          share|improve this answer









                          $endgroup$















                            4












                            4








                            4





                            $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!






                            share|improve this answer









                            $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!







                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Jun 10 at 5:52









                            JonahJonah

                            3,7117 silver badges19 bronze badges




                            3,7117 silver badges19 bronze badges





















                                2












                                $begingroup$

                                I see a couple of simplifications that could be made:



                                A feature of ecmascript-6 that could be used here is default parameters:



                                const combine = (cur = '', n = 0) => {


                                Then the first call to that function doesn't need to pass any parameters:



                                combine();



                                The arrow function in the imperative approach feels too brief to warrant the need for brackets:




                                [...strDigits[digits[n]]].forEach(x => 
                                combine(cur + x, n + 1);
                                );



                                So that could be shortened to a single line:



                                [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1));


                                But if that is longer than your desired maximum length of a line of code, pull out the arrow function:



                                const combineNext = x => combine(cur + x, n + 1);
                                [...strDigits[digits[n]]].forEach(combineNext);


                                Actually, in an imperative solution, I might expect to see a for...of loop, which should be faster:



                                for (const x of [...strDigits[digits[n]]]) 
                                combine(cur + x, n + 1);




                                I considered suggesting that the keys of strDigits be integers instead of string literals, each element in digits would be a string so the types would then be different.






                                share|improve this answer











                                $endgroup$

















                                  2












                                  $begingroup$

                                  I see a couple of simplifications that could be made:



                                  A feature of ecmascript-6 that could be used here is default parameters:



                                  const combine = (cur = '', n = 0) => {


                                  Then the first call to that function doesn't need to pass any parameters:



                                  combine();



                                  The arrow function in the imperative approach feels too brief to warrant the need for brackets:




                                  [...strDigits[digits[n]]].forEach(x => 
                                  combine(cur + x, n + 1);
                                  );



                                  So that could be shortened to a single line:



                                  [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1));


                                  But if that is longer than your desired maximum length of a line of code, pull out the arrow function:



                                  const combineNext = x => combine(cur + x, n + 1);
                                  [...strDigits[digits[n]]].forEach(combineNext);


                                  Actually, in an imperative solution, I might expect to see a for...of loop, which should be faster:



                                  for (const x of [...strDigits[digits[n]]]) 
                                  combine(cur + x, n + 1);




                                  I considered suggesting that the keys of strDigits be integers instead of string literals, each element in digits would be a string so the types would then be different.






                                  share|improve this answer











                                  $endgroup$















                                    2












                                    2








                                    2





                                    $begingroup$

                                    I see a couple of simplifications that could be made:



                                    A feature of ecmascript-6 that could be used here is default parameters:



                                    const combine = (cur = '', n = 0) => {


                                    Then the first call to that function doesn't need to pass any parameters:



                                    combine();



                                    The arrow function in the imperative approach feels too brief to warrant the need for brackets:




                                    [...strDigits[digits[n]]].forEach(x => 
                                    combine(cur + x, n + 1);
                                    );



                                    So that could be shortened to a single line:



                                    [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1));


                                    But if that is longer than your desired maximum length of a line of code, pull out the arrow function:



                                    const combineNext = x => combine(cur + x, n + 1);
                                    [...strDigits[digits[n]]].forEach(combineNext);


                                    Actually, in an imperative solution, I might expect to see a for...of loop, which should be faster:



                                    for (const x of [...strDigits[digits[n]]]) 
                                    combine(cur + x, n + 1);




                                    I considered suggesting that the keys of strDigits be integers instead of string literals, each element in digits would be a string so the types would then be different.






                                    share|improve this answer











                                    $endgroup$



                                    I see a couple of simplifications that could be made:



                                    A feature of ecmascript-6 that could be used here is default parameters:



                                    const combine = (cur = '', n = 0) => {


                                    Then the first call to that function doesn't need to pass any parameters:



                                    combine();



                                    The arrow function in the imperative approach feels too brief to warrant the need for brackets:




                                    [...strDigits[digits[n]]].forEach(x => 
                                    combine(cur + x, n + 1);
                                    );



                                    So that could be shortened to a single line:



                                    [...strDigits[digits[n]]].forEach(x => combine(cur + x, n + 1));


                                    But if that is longer than your desired maximum length of a line of code, pull out the arrow function:



                                    const combineNext = x => combine(cur + x, n + 1);
                                    [...strDigits[digits[n]]].forEach(combineNext);


                                    Actually, in an imperative solution, I might expect to see a for...of loop, which should be faster:



                                    for (const x of [...strDigits[digits[n]]]) 
                                    combine(cur + x, n + 1);




                                    I considered suggesting that the keys of strDigits be integers instead of string literals, each element in digits would be a string so the types would then be different.







                                    share|improve this answer














                                    share|improve this answer



                                    share|improve this answer








                                    edited Jun 10 at 18:58

























                                    answered Jun 10 at 17:18









                                    Sᴀᴍ OnᴇᴌᴀSᴀᴍ Onᴇᴌᴀ

                                    12.1k6 gold badges25 silver badges83 bronze badges




                                    12.1k6 gold badges25 silver badges83 bronze badges



























                                        draft saved

                                        draft discarded
















































                                        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.




                                        draft saved


                                        draft discarded














                                        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





















































                                        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







                                        Popular posts from this blog

                                        Wikipedia:Vital articles Мазмуну Biography - Өмүр баян Philosophy and psychology - Философия жана психология Religion - Дин Social sciences - Коомдук илимдер Language and literature - Тил жана адабият Science - Илим Technology - Технология Arts and recreation - Искусство жана эс алуу History and geography - Тарых жана география Навигация менюсу

                                        Bruxelas-Capital Índice Historia | Composición | Situación lingüística | Clima | Cidades irmandadas | Notas | Véxase tamén | Menú de navegacióneO uso das linguas en Bruxelas e a situación do neerlandés"Rexión de Bruxelas Capital"o orixinalSitio da rexiónPáxina de Bruselas no sitio da Oficina de Promoción Turística de Valonia e BruxelasMapa Interactivo da Rexión de Bruxelas-CapitaleeWorldCat332144929079854441105155190212ID28008674080552-90000 0001 0666 3698n94104302ID540940339365017018237

                                        What should I write in an apology letter, since I have decided not to join a company after accepting an offer letterShould I keep looking after accepting a job offer?What should I do when I've been verbally told I would get an offer letter, but still haven't gotten one after 4 weeks?Do I accept an offer from a company that I am not likely to join?New job hasn't confirmed starting date and I want to give current employer as much notice as possibleHow should I address my manager in my resignation letter?HR delayed background verification, now jobless as resignedNo email communication after accepting a formal written offer. How should I phrase the call?What should I do if after receiving a verbal offer letter I am informed that my written job offer is put on hold due to some internal issues?Should I inform the current employer that I am about to resign within 1-2 weeks since I have signed the offer letter and waiting for visa?What company will do, if I send their offer letter to another company