Search code examples
javascriptnode.jsarraysreactjsfunctional-programming

Javascript Function to flatten multiple input arrays


I am dealing with a couple of arrays of objects which will get rendered into the UI using React. So here's the context of what I am doing. I get different data sets from different APIs. These datasets are arrays of arrays of objects. E.g

[
 [{age: 23, name: john}],
 [{age: 24, name: jane}],
 [{age: 25, name: sam}],
 [{age: 26, name: smith}],
]

With this format, it will be hard to render the result in the UI. So a simple fix will be to flatten the array which returns a single array of objects which then I can map through and display the data on the UI. Now, I have this kind of operation for each of the datasets returned from the API.

  • First, I flatten each array of arrays, using array.flat()
  • Second, I filter duplicated items using array.filter()
  • Third, I sort these items using array.sort()
  • Fourth, I merge the arrays together using array.concat()
  • Fifth, I map through the final array produced from the 4 previous steps.

Following this style I listed above seems to be imperative to me, I wanted to use a more functional approach where I can pipe functions together, which will make it more declarative rather than imperative. Currently, I am stuck on writing a function that will accept arrays as inputs, then flatten the arrays into a single final array. Now I know I can flatten an array using the reduce() method and. I came across a solution on StackOverflow which looks like this and can be seen from this link Flatten Array

const flatten = (arr) => {
    return arr.reduce((flat, toFlatten) => {
        return flat.concat(
            Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten
        )
    }, [])
}

I was thinking maybe I could spread the input arr parameter but that seems to throw this error

RangeError: Maximum call stack size exceeded

I would appreciate it if I could be guided in the right way and also shown an efficient way to achieve all these operations I listed out. Thank you


Solution

  • This solution should do what you're looking for. It would help to have a few samples of different return data to merge together, but I tried to reproduce what you explained in my solution below.

    First I combine all the different source arrays into one single array and then flatten that to any depth any of the source arrays have. In case this depth is variable, I used flat(Infinity) but you could use a finite depth as well (flat(2) or flat(3)). Once I've flattened the array, I briefly convert it to a Set to remove any duplicates and then convert it back to an array.

    const source1 = [
      [{age: 23, name: 'john'}],
      [{age: 24, name: 'jane'}],
      [{age: 25, name: 'sam'}],
      [{age: 26, name: 'smith'}]
    ];
    
    const source2 = [
      [{age: 27, name: 'mike'}],
      [{age: 28, name: 'joanne'}],
      [{age: 29, name: 'lois'}],
      [{age: 30, name: 'paul'}]
    ];
    
    const allSources = [...new Set([source1,source2].flat(Infinity))];
    
    console.log(allSources);

    ** Important note here: Because arrays and objects are reference types, even if two source arrays have the same object by key-value pairs, it won't actually be seen as a duplicate. In order to truly filter out any duplicate objects, you'll need to loop through the keys for both objects and compare their associated values, like this:

    const source1 = [
      [{age: 23, name: 'john'}],
      [{age: 24, name: 'jane'}],
      [{age: 25, name: 'sam'}],
      [{age: 26, name: 'smith'}]
    ];
    
    const source2 = [
      [{age: 23, name: 'john'}],
      [{age: 24, name: 'jane'}],
      [{age: 25, name: 'sam'}],
      [{age: 26, name: 'smith'}],
      [{age: 27, name: 'mike'}],
      [{age: 28, name: 'joanne'}],
      [{age: 29, name: 'lois'}],
      [{age: 30, name: 'paul'}]
    ];
    
    const allSources = [source1,source2].flat(Infinity).filter((obj,_,a) => a.find(o => Object.keys(obj).length === Object.keys(o).length && Object.keys(obj).every(objKey => Object.keys(o).includes(objKey) && obj[objKey] === o[objKey])) === obj);
    
    console.log(allSources);