Home > Software design >  How can I avoid duplicate objects returned from a .map()?
How can I avoid duplicate objects returned from a .map()?

Time:08-24

I am trying to boil some data down using Object.keys() and .map(). I want to get back an array of objects that contain arrays. The .map() returns the correct objects (currently two) in the array, but they are duplicated by the number of loops in the .map(). Below is a simplified example of what I am trying to do

// The data:
const theObjects = {
    object1: ['a', 'b', 'c'],
    object2: ['d', 'e', 'f'],
    object3: ['g', 'h', 'i'],
    object4: ['j', 'k', 'l'],
    object5: ['m', 'n', 'o'],
};

// Set up the keys for the object I want to get back. It looks weird here, but it is an emulation of data that is extracted from the source of my real dataset:
let season;
const seasons = [
    '2021-2022',
    '2021-2022',
    '2021-2022',
    '2022-2023',
    '2022-2023',
];

//Set up the return object. Extract the keys from the dataset object and map over them
var compiler = {};
const returnedData = Object.keys(theObjects).map((key, index) => {

    // Set up `seasons` to become the object keys of final output and initialize the objects as needed.
    var season = seasons[index];

    Array.isArray(compiler[season]) ? compiler : (compiler[season] = []);

    // `.push()` the current data into the appropriate array:
    compiler[season].push(theObjects[key]);

    // Test 1: Test the current state of the compiler object and `return` the object:
    console.log('COMPILER[', key, ']', index, ': ', compiler);
    return compiler;
});

//Test 2: The final object returned
console.log('Returned: ', returnedData);

Test 1 logs once for each loop in the .map() and we can watch the final object build:

COMPILER[ object1 ] 0 :  { '2021-2022': [ [ 'a', 'b', 'c' ] ] }
COMPILER[ object2 ] 1 :  { '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ] ] }
COMPILER[ object3 ] 2 :  {
  '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g', 'h', 'i' ] ]
}
COMPILER[ object4 ] 3 :  {
  '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g', 'h', 'i' ] ],
  '2022-2023': [ [ 'j', 'k', 'l' ] ]
}
COMPILER[ object5 ] 4 :  {
  '2021-2022': [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g', 'h', 'i' ] ],
  '2022-2023': [ [ 'j', 'k', 'l' ], [ 'm', 'n', 'o' ] ]
}

Test 2 logs the final object. The data matches the object returned in the last loop of the .map(), but it is repeated 5 times:

Returned: [
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
    {
        '2021-2022': [
            ['a', 'b', 'c'],
            ['d', 'e', 'f'],
            ['g', 'h', 'i'],
        ],
        '2022-2023': [
            ['j', 'k', 'l'],
            ['m', 'n', 'o'],
        ],
    },
];

I can make this work by simply using returnedData[0] and calling it a day. Unfortunately, my anal-retentive side just can't let this go. Is there something that I can do to just get back something that looks like the COMPILER[ object5 ]... output?

CodePudding user response:

I didn't read your whole thing because that's a lot. But if you want to dedupe an array (the result of map), use a set...

const arr = ["a", "a", "b", "b", "c", "c", "c", "c"];
const res = [...new Set(arr)];
console.log(res);  
// ["a","b","c"]

Also, checkout this other post for more info: Remove Duplicates within a map function

CodePudding user response:

Here is what it seems to me that you need. This avoids creating duplicate data. For the reasoning why your current code doesn't work I really don't have an explanation, I was actually quite baffled after seeing that the data somehow got fully looped through and set up on the first iteration. But if you find an answer why please share it in your post.

let compiledData = {};
let keys = Object.keys(theObjects);
let length = seasons.length;
for(let i = 0; i < length; i  ) {
    if(!compiledData[seasons[i]]) compiledData[seasons[i]] = [];
    compiledData[seasons[i]].push(theObjects[keys[i]]);
} 
console.log(compiledData);

CodePudding user response:

but it is repeated 5 times:

So you used .map, it always returns an array of the original size, this is the natural behavior of .map. If you need to resize an array or make an object, you need to use .reduce.

The second is that you use var compiler = {};, a global variable, and return it every time inside .map return compiler;. So you're always returning the same object - that's the problem.

Anyway, in this case it's better to use .reduce, for example:

const theObjects = {object1: ['a', 'b', 'c'],object2: ['d', 'e', 'f'],object3: ['g', 'h', 'i'],object4: ['j', 'k', 'l'],object5: ['m', 'n', 'o']};
const seasons = ['2021-2022','2021-2022','2021-2022','2022-2023','2022-2023'];

const returnedData = Object.values(theObjects)
  .reduce((acc, values, index) => {
    const seson = seasons[index];
    acc[seson] ??= [];
    acc[seson].push(values);
    return acc;
  }, {});
  
// Or the same result but in "modern" non-mutable-way  
const returnedData1 = Object.values(theObjects)
  .reduce((acc, values, index) => ({
    ...acc, 
    [seasons[index]]: [...(acc[[seasons[index]]] || []), values]
  }), {});

console.log('Returned: ', returnedData1);
.as-console-wrapper { max-height: 100% !important; top: 0 }

  • Related