2

I want to reduce this nested array:

const list = ['Map<%s,%s>', ['string', 'Map<%s,%s>', ['string', 'boolean']]];

so that list becomes:

'Map<string,Map<string,boolean>>'

Here is a start, but the recusion is really confusing to me:

const util = require('util');

const reduceToString = function(l){
  return l.reduceRight((a,b) => {
    if(Array.isArray(a)){
      return reduceToString(a);
    }

    return util.format(b, a);

  });
};


console.log(reduce(list));

For better understanding to see how this needs to work generically, this input:

const list = ['Map<%s,%s,%s>', ['string', 'Map<%s,%s>', ['string', 'boolean'], 'number']];

should yield:

'Map<string,Map<string,boolean>,number>'

The general rule is: any array to the right of a string, should be interpolated into the string, and the reduceToString function should always return a string.

5
  • The nested array seems off; the outermost array's first element has two %s, so it should either have three elements total, where the 2nd to last are inserted into the first, or it should have two elements, where the 2nd is an array that has in turn two elements, reflecting the two %s. The nested array you have doesn't seem to have a consistent grammar. Commented Nov 21, 2018 at 23:21
  • The rule is any array that is to the right of a string, gets interpolated into the string. I double-checked and the example seems fine. Commented Nov 21, 2018 at 23:23
  • Are there only Map types, or the type is dynamic (could be others)? Commented Nov 21, 2018 at 23:27
  • You could do this list.join('').replace(/%s,%s>/g, '').replace(/<,/g, '<') + '>>' if your Array format is guaranteed to be always the same. Commented Nov 21, 2018 at 23:28
  • @ibrahimmahrir there could many types besides Map, it has to be generic etc Commented Nov 21, 2018 at 23:31

3 Answers 3

2

One approach can be to "reduce" any element that is an array (going down to the deepest level) and then do your replacements as you keep returning from the stack for any element whose "next element" is an array:

const list = ['Map<%s,%s,%s>', ['string', 'Map<%s,%s>', ['string', 'boolean'], 'Number']];

function merge(list) {
  function reduce(arr) {
    arr = arr.map(e => Array.isArray(e) ? reduce(e) : e);
    return arr
      .map((e, i) => Array.isArray(arr[i + 1])
        ? arr[i + 1].reduce((a, c) => a.replace('%s', c), e)
        : e)
      .filter(e => !Array.isArray(e));
  }
  return reduce(list)[0];
}


console.log(merge(list));

Sign up to request clarification or add additional context in comments.

2 Comments

One thing to check is if this works for arrays with length 0 and 1, instead of only length greater than 1
@MrCholo With length 0, this returns undefined and with length 1 it returns the first string in the array (which I think can be considered acceptable behavior).
2

You can do this recursively with replace():

const list = ['Map<%s,%s,%s>', ['string', 'Map<%s,%s>', ['string', 'boolean'], 'Number']];

function replaceMatch(arr, i=0){
    let str = arr[0]
    return str.replace(/%s/g, () => {
        let next = arr[1]
        if (Array.isArray(next[i+1])) {
            i+=2
            return replaceMatch(next.slice(i-2))       
        }
        else return next[i++]
    })
}
str = replaceMatch(list)
console.log(str)

Here's a longer, but probably easier-to-read recursive version. It returns an array rather than a string to allow non-mappings to end up in the result (for example the first and last elements. It will add a ? if the mapping has more slots than array values and ignore extra array values that don't have corresponding %s:

const list = ["Name", 'Map<%s,%s,%s>', ['string', 'Map<%s,%s,%s>', ['string', 'boolean'], 'Number'], 'Set<%s,%s>', ['val_1', 'val_2']];

function replaceArr(str, arr){ // helper function for replace
    let i = 0
    return str.replace(/\%s/g, () => arr[i++] || '?')
}

function replaceMatch(arr){
  let res = []
  for (let i = 0; i < arr.length ; i++){
      if (Array.isArray(arr[i])) continue
      res.push(Array.isArray(arr[i+1])
          ? replaceArr(arr[i], replaceMatch(arr[i+1]))
          : arr[i]
        )
  }
  return res
}
str = replaceMatch(list)
console.log(str)

10 Comments

Also, good call with your data input, that is more general and accurate than the original input in the OP, thank you (even though no Map I know of has 3 params, maybe change the OP to Map<?,<?,?>>, idk
Yeah, I would like to make the magic number go away…when you are replacing you need to skip over the arrays in the list. That's why I added the extra %s because it's easy to miss. If I think of a purer recursive way, I'll edit.
sure I updated my comment above as well, yeah any number besides 0 or 1 in code is magic right haha, although 3 is even scarier than 2 lulz
One thing to check is if this works for arrays with length 0 and 1, instead of only length greater than 1.. I checked for length 1, and it returns undefined instead of a placeholder. How about return a question mark ?, if there is no corresponding value in the array?
That looks pretty nice @MrCholo -- popping from the array is a nice way to handle this.
|
2

This was much harder than I thought it would be. This solution returns an array, and is more generic than other solutions and what the OP originally asked for. Since it can handle two strings in a row.

const util = require('util');
const list = ['Array','Map<%s,%s, %s>', ['xxx','Map<%s,%s>', ['string', 'boolean'], 'number']];

const reduce = function(list){
  return list.slice(1).reduce((a,b) => {

    if(Array.isArray(b)){
      const pop = a.pop();
      const format = util.format(pop,...reduce(b));
      return a.concat(format);
    }

    return (a.push(b), a);  // comma operator

  },
    [list[0]]
  );
};


console.log(reduce(list));

The above will yield:

[ 'Array', 'Map<xxx,Map<string,boolean>, number>' ]

2 Comments

This is equivalent to the inner reduce helper in the solution I posted. If your final desired output is an array, you can use that directly.
the best way to understand how this works is to put console.log({a,b}) inside the Array.prototype reduce callback.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.