Home > Software design >  forEach/functional recursion and returning values
forEach/functional recursion and returning values

Time:09-17

So I have an array which looks like this:

['id1/id2', 'id3', 'id4/id5/id6']

Although it may have more than 3 entries, or more split values (/ things) - these may change depending on the state of the app but this is a standard example.

I am trying to output:

[
    ['id1', 'id3', 'id4'],
    ['id1', 'id3', 'id5'],
    ['id1', 'id3', 'id6'],
    ['id2', 'id3', 'id4'],
    ['id2', 'id3', 'id5'],
    ['id2', 'id3', 'id6']
]

I think a forEach loop can be used to recurse through but I got stuck on the syntax, and so tried a standard functional recursive tpye. I also was unsure about the return procedure to generate the output. What I have so far is:

singleOptionStemGenerator(route: string[]): [string[]] {

    let returnArray: [string[]];

    route.forEach((options: string) => {
        // split into the various options
        let splitOption: string[] = options.split('/');

        splitOption.forEach((str: string) => {
            if(route.length > 1) {
                this.singleOptionStemGenerator(route.splice(0, 1));
            } else {
                return str;
            }
        })
    });

    return returnArray;
}

But I am not sure how to amalgamate the ID values into a new array, and then add that to a new array overall.

EDIT: The solution given below for myself was a little dense, so I went through and made it a function and fully annotated it. I also made it typescript (which I use). The solution is all what the answer this, this just feel sanitzed for easier reading and learning: https://codepen.io/bogomip/pen/LYLjrxa

CodePudding user response:

What you're looking for is usually called the Cartesian Product. With a helper function that computes those, this becomes almost trivial.

Here is one version:

const cartesian = ([xs, ...xss]) =>
  xs = undefined
    ? []
  : xss.length == 0
    ? xs .map (x => [x])
  : xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))

const separate = (xs) => 
  cartesian (xs .map (x => x .split ('/')))

console .log (separate (['id1/id2', 'id3', 'id4/id5/id6']))
.as-console-wrapper {max-height: 100% !important; top: 0}

Update

A comment made it clear that this code is somewhat dense. Part of that is my penchant for expression-only coding. For those more used to expression-and-statement-style Javascript, this version might be more familiar:

const cartesian = (xss) => {
  if (xss .length == 0) {
    return []
  }
  const first = xss [0] 
  const rest = xss .slice (1)
  if (rest .length == 0) {
    return first .map (x => [x])
  }
  const cartesianEnd = cartesian (rest)
  return first .flatMap (
    x => cartesianEnd .map (ys => [x, ...ys])
  )
}

If that style is more to your liking, then that may also help explain the code as I wrote it. Except that... typing that out made me realize that there was a horrible inefficiency in my first approach. Unfortunately the fix adds some complexity, but it is clearly worthwhile:

const cartesian = ([xs, ...xss]) =>
  xs = undefined
    ? []
  : xss.length == 0
    ? xs .map (x => [x])
  : ((yss = cartesian (xss)) => xs .flatMap (x => yss .map (ys => [x, ...ys]))) ()

The problem was that I was recalculating the product of the remaining arrays for each element of my current array. These don't change, and we should do this only once. Ahh for real let-bindings in JS! We could do this in many different manners, but this one is not too bad.

  • Related