Search code examples
javascriptarraysgroupingreducekey-value

How to group an array of objects by same id and date values?


I have an array of objects; I have to group objects by same ids, startDates and endDates. If ids are the same but startdate or endDate are NOT the same - do not group there. If startDates or endDates are the same but ids are NOT - do not group there. I've tries to do it with reduce but can't figure out how.

How to do it ?

My code:

const newDates: any[] = [];
    response.forEach((elem, index) => {
        if(newDates.length === 0) newDates.push([elem])

        if(newDates.length > 0) {
            if(
                newDates[index][0].startDate === elem.startDate
                && newDates[index][0].endDate === elem.endDate
                && newDates[index][0].id === elem.id
            ) {
                newDates[index].push(elem)
            } else {
                newDates.push([elem])
            }
        }
    })

Example:

    const response = [
        {
            name: 'John',
            id: 1,
            startDate:'2022-10-31T00:00:00',
            endDate: '2022-12-01T00:00:00'
        },
        {
            name: 'Alisha',
            id: 1,
            startDate:'2022-10-31T00:00:00',
            endDate: '2022-12-01T00:00:00'
        },
        {
            name: 'Andrew',
            id: 4,
            startDate:'2022-10-05T00:00:00',
            endDate: '2022-11-01T00:00:00'
        },
        {
            name: 'James',
            id: 4,
            startDate:'2022-10-05T00:00:00',
            endDate: '2022-11-01T00:00:00'
        },
        {
            name: 'Kamilla',
            id: 4,
            startDate:'2022-11-14T00:00:00',
            endDate: '2022-11-18T00:00:00'
        },
        {
            name: 'Oliver',
            id: 4,
            startDate:'2022-11-13T00:00:00',
            endDate: '2022-11-18T00:00:00'
        }
    ]

And result should be

  const result = [
        [
            {
                name: 'John',
                id: 1,
                startDate:'2022-10-31T00:00:00',
                endDate: '2022-12-01T00:00:00'
            },
            {
                name: 'Alisha',
                id: 1,
                startDate:'2022-10-31T00:00:00',
                endDate: '2022-12-01T00:00:00'
            },
        ],
        [
            {
                name: 'Andrew',
                id: 4,
                startDate:'2022-10-05T00:00:00',
                endDate: '2022-11-01T00:00:00'
            },
            {
                name: 'James',
                id: 4,
                startDate:'2022-10-05T00:00:00',
                endDate: '2022-11-01T00:00:00'
            },
        ],
        [
            {
                name: 'Kamilla',
                id: 4,
                startDate:'2022-11-14T00:00:00',
                endDate: '2022-11-18T00:00:00'
            }
        ],
        [
            {
                name: 'Oliver',
                id: 4,
                startDate:'2022-11-13T00:00:00',
                endDate: '2022-11-18T00:00:00'
            }
        ]
    ]

Solution

  • The OP's task belongs to the "group and collect" category.

    The approach there is always the same

    1. create an object (index/map) ...
    2. where a group is presented by an entry with a key which is unique to the group it presents, and where the value is an array of the (to be) grouped and collected items.

    Thus, as for solving the OP's problem

    1. reduce the array of items with an initial value which serves as such an index.
    2. for each iteration step retrieve the data that makes the item belong to a unique group ... here ... const { id, startDate, endDate } = item;
    3. create a unique key which does refer either an existing or a to be created group ... here ... const groupKey = [id, startDate, endDate].join('_');
    4. create and/or access the group via the created key by using the nullish coalescing assignment (x ??= y) operator
    5. ... and push the item into the group array.
    6. pass the programmatically built index into the next iteration step or return it as final result ... return index;

    Since the reducer function's result is an object which features the OP's grouped arrays as values of its key-value pairs, this object has to be processed by Object.values in order to exactly meet the OP's expected data structure (array of arrays of item(s)).

    function groupAndCollectItemBySameIdAndDate(index, item) {
      const { id, startDate, endDate } = item;
      const groupKey = [id, startDate, endDate].join('_');
    
      (index[groupKey] ??= []).push(item);
    
      return index;
    }
    
    const sampleData =   [{
      name: 'John',
      id: 1,
      startDate:'2022-10-31T00:00:00',
      endDate: '2022-12-01T00:00:00'
    }, {
      name: 'Alisha',
      id: 1,
      startDate:'2022-10-31T00:00:00',
      endDate: '2022-12-01T00:00:00'
    }, {
      name: 'Andrew',
      id: 4,
      startDate:'2022-10-05T00:00:00',
      endDate: '2022-11-01T00:00:00'
    }, {
      name: 'James',
      id: 4,
      startDate:'2022-10-05T00:00:00',
      endDate: '2022-11-01T00:00:00'
    }, {
      name: 'Kamilla',
      id: 4,
      startDate:'2022-11-14T00:00:00',
      endDate: '2022-11-18T00:00:00'
    }, {
      name: 'Oliver',
      id: 4,
      startDate:'2022-11-13T00:00:00',
      endDate: '2022-11-18T00:00:00'
    }];
    
    const groupedData = sampleData
      .reduce(groupAndCollectItemBySameIdAndDate, {});
    
    const expectedResult = Object.values(groupedData);
    
    console.log({
      groupedData: structuredClone?.(groupedData) ?? groupedData,
      expectedResult: structuredClone?.(expectedResult) ?? expectedResult,
    });
    .as-console-wrapper { min-height: 100%!important; top: 0; }