简体   繁体   中英

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, eg "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 eg 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?

You could split the string by comma, replace the groups with their arrays and replace a single character with the characters in an array, get the cartesian product, join the inner arrays and get the array with the result.

Finally flat the array.

 const cartesian = (a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []), foo = string => string.split(',').map(s => Array.from(s, c => tCategories[c] || [c])).map(a => a.reduce(cartesian).map(a => a.join(''))).flat(), tCategories = { T: ["t", "d", "th"], P: ["p", "t", "k", "q"], N: ["m", "n"] }; console.log(...foo("aT,Ps,eNe,NP"));

This is an update regarding the bounty by @Arcaeca who asked for 3 things:

1- The line .map(s => Array.from(s, c => tCategories[c] || [c])) does not replace a key of tCategories with its corresponding value when key.length > 1.

2- Passing an input string with an empty subpattern (ie substring delimited by ","), eg "aT,Ps,eNe,,NP" , causes the function to throw: TypeError .

3- It's a new feature, I tried to add was the ability to define "nonce" categories on the spot by enclosing them in square brackets [ ], eg the input string "a[Ps,T]" should yield the same output as "aPs,aT"

My Answer (From @Nina Scholz Answer)

I will start with the third requirement since it's completely new, so to make it easy I will make another function to parse the given string and check if it has square brackets multiplication, then resolve it, eg the input "a[Ps,T]" , the output would be "aPs,aT" i will call this clean()`. You can enhance this function as you want.

const clean = string => {
    while(true){
        const match = /(\w+)\[([\w+\,]*)\]/.exec(string)
        if(!match){
            break
        }
        const newString = [[match[1]],match[2].split(',').map(v => v.replace(',', ''))].reduce(cartesian).map(a => a.join('')).join(',')
        string = string.replace(match[0], newString)
    }
    return string
};

Backing to the first two requirements, I made this modification

const foo = string => Object.keys(tCategories)
    .reduce((a, b) => a.replaceAll(b, `?${b}?`), string)
    .split(',')
    .map(v => v.split('?').map(t => tCategories[t] || [[t]]))
    .map(a => a.reduce(cartesian).map(a => a.join('')))
    .flat()

What I did is, I went through each key of tCategories then checked if my string contains that key, if yes then put a placeholder around it to make it easy to identify it, in my example, I chose ? , and got rid of Array.from method. now our function supports keys whose length > 1, and also empty subpatterns.

Full Example

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

const cartesian = (a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []);

const clean = string => {
    while(true){
        const match = /(\w+)\[([\w+\,]*)\]/.exec(string)
        if(!match){
            break
        }
        const newString = [[match[1]],match[2].split(',').map(v => v.replace(',', ''))].reduce(cartesian).map(a => a.join('')).join(',')
        string = string.replace(match[0], newString)
    }
    return string
};

const foo = string => Object.keys(tCategories)
    .reduce((a, b) => a.replaceAll(b, `?${b}?`), string)
    .split(',')
    .map(v => v.split('?').map(t => tCategories[t] || [[t]]))
    .map(a => a.reduce(cartesian).map(a => a.join('')))
    .flat()

console.log(...foo(clean('aT,Ps,eNe,NP,,KK[z,c,f]')))

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:

For edge case 1, I want to know what the expectation is if one key is a substring of another.

For the rest the solution is at https://gist.github.com/EarthyOrange/1f9ca9ae606b61d435fef484bbf96945

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM