Search code examples
javascriptarraysstringpattern-matchingcartesian-product

Find all possible combinations of strings that match a given pattern in JS


So I have a dictionary where each key is mapped to an array of letters:

tCategories = { "T": ["t","d","th"],
                "P": ["p","t","k","q"],
                "N": ["m","n"] };

And an input string that contains a handful of patterns delimited by commas, e.g. "aT,Ps,eNe,NP", where a substring that is a valid key of tCategories acts a stand-in for any of the letters in tCategories[key].

What I'm trying to figure out is how to find every combination of each pattern listed in the input string and put them all in an array. So e.g. the expected output for foo("aT,Ps,eNe,NP") would be ["at","ad","ath","ps","ts","ks","qs","eme","ene","mp","mt","mk","mq","np","nt","nk","nq"].

My first instinct would either be to call String.split(",") on the input string to deal with each substring separately, or else iterate via for (var key in tCategories) { input.replace(new RegExp(key, "g"), "["+tCategories[key].join("|")+"]" }, or something... but I just can't seem to find a useful pathway between those and the expected output. It would involve... what, basically implementing the distributive property but for letters instead of numbers? How do I do this?


Solution

  • Original Question:

       const tCategories = {
          "T": ["t","d","th"],
          "P": ["p","t","k","q"],
          "N": ["m","n"],
        };
        
        // Think matrix like multiplication
        function multiply(twoDArray1, twoDArray2) {
          const product = [];
          for (let i = 0; i < twoDArray1.length; i++) {
            for (let j = 0; j < twoDArray2.length; j++) {
              product.push([...twoDArray1[i], twoDArray2[j]]);
            }
          }
          return product;
        }
        
        function stringHasCategories(inputString) {
          for (let i = 0, ch = inputString.charAt(0); i < inputString.length; i++, ch = inputString.charAt(i)) {
            if (tCategories[ch]) {
              return true;
            }
          }
          return false;
        }
                            
        function expandCategories(inputString) {
          if (!stringHasCategories(inputString)) {
            return inputString;
          }
          let output = [[]];
          for (let i = 0, ch = inputString.charAt(0); i < inputString.length; i++, ch = inputString.charAt(i)) {
             if (tCategories[ch]) {
               output = multiply(output, tCategories[ch]);
             } 
             else {
               output.forEach((op) => op.push(ch));
             }
          }
          output.forEach((op, i) => { output[i] = op.join(''); });
          return output;
        }
                            
        function foo(inputString = "aT,Ps,eNe,NP") {
          return inputString
            .split(',')
            .map(expandCategories)
            .flat();
        }
        
        console.log(foo());
    

    For Updated Question:

    https://gist.github.com/EarthyOrange/1f9ca9ae606b61d435fef484bbf96945