1

So I have this kind of data (simplified version):

const itemList = [
{itemDetail: {date: '2020-12-30' , quantity: 5, productType: 'shirt'  }},
{itemDetail: {date: '2021-01-05' , quantity: 4, productType: 'trouser'  }},
{itemDetail: {date: '2020-12-30', quantity: 1, productType: 'shirt'  }},
{itemDetail: {date: '2021-01-05', quantity: 2, productType: 'jacket'}}
]

The desired output:

const itemListFinalForm = [
{
   date: '2020-12-30',
   items: [{productType: 'shirt', quantity: 6}]
},
{
   date: '2021-01-05',
   items: [{productType: 'trouser', quantity: 4}, {productType: 'hat', quantity: 2}]
}
]

I've tried writing the pseudocode, but cant wrap my head around to actually implement it. Any kind of answer would be much appreciated. Thank you

  • Please visit the [help], take the [tour] to see what and [ask]. Do some research, search for related topics on SO; if you get stuck, post a [mcve] of your attempt, noting input and expected output using the `[<>]` snippet editor. – mplungjan Jan 14 '21 at 13:00
  • 1
    You have three separate problems here. 1. Grouping entities where productType+date is equal. 2. Parsing actual date from your date string. 3. sorting an array of objects by custom propery. Take them on, one by one. Sorting array by date for example is explained here: https://stackoverflow.com/questions/10123953/how-to-sort-an-array-by-a-date-property – Tarmo Jan 14 '21 at 13:10

5 Answers5

1

const itemList = [
  { itemDetail: { date: '2020-12-30', quantity: 5, productType: 'shirt' } },
  { itemDetail: { date: '2021-01-05', quantity: 4, productType: 'trouser' } },
  { itemDetail: { date: '2020-12-30', quantity: 1, productType: 'shirt' } },
  { itemDetail: { date: '2021-01-05', quantity: 2, productType: 'jacket' } },
  { itemDetail: { date: '2021-01-06', quantity: 3, productType: 'jacket' } },
]

const itemsByType = {};
const itemsByDate = {};

itemList.forEach(({ itemDetail }) => {
  const { productType, date, quantity } = itemDetail;
  if (itemsByType[productType]?.[date]) {
    itemsByType[productType][date] = itemsByType[productType][date] + quantity;
  } else {
    itemsByType[productType] = { [date]: quantity };
  }
});

Object.entries(itemsByType).forEach(([productType, info]) => {
  Object.entries(info).forEach(([date, quantity]) => {
    if (itemsByDate[date]) {
      itemsByDate[date].push({ quantity, productType });
    } else {
      itemsByDate[date] = [{ quantity, productType }];
    }
  });
});

const result = Object.entries(itemsByDate).map(([date, items]) => ({ date, items }));

console.log(result);
0

Here i made a simple example below

const itemList = [{
    itemDetail: {
      date: '2020-12-30',
      quantity: 5,
      productType: 'shirt'
    }
  },
  {
    itemDetail: {
      date: '2021-01-05',
      quantity: 4,
      productType: 'trouser'
    }
  },
  {
    itemDetail: {
      date: '2020-12-30',
      quantity: 1,
      productType: 'shirt'
    }
  },
  {
    itemDetail: {
      date: '2021-01-05',
      quantity: 2,
      productType: 'jacket'
    }
  }
]


var data = itemList.reduce((acc, item) => {
  var d = acc.find(x => x.date == item.itemDetail.date);
  if (!d) {
    acc.push({
      date: item.itemDetail.date,
      items: [{
        quantity: item.itemDetail.quantity,
        productType: item.itemDetail.productType
      }]
    });
  } else {
    var type = d.items.find(x => x.productType == item.itemDetail.productType);
    if (!type) {
      d.items.push({
        quantity: item.itemDetail.quantity,
        productType: item.itemDetail.productType
      })
    } else type.quantity += item.itemDetail.quantity
  }
  return acc;
}, [])

console.log(data)
Alen.Toma
  • 4,792
  • 2
  • 11
  • 24
  • thank you so much, i really like your approach. Quick question, why it is possible to update accumulator value without touching it directly? like on this line `type.quantity += item.itemDetail.quantity` and `d.items.push` ? isnt it should only update the `d` and `type` array on the current iteration? – Muhamad Hafiz Jan 14 '21 at 16:42
  • `find` method dose not clone the item in `acc` so d and type is still belong to the acc. I really don't know how i could explain this. – Alen.Toma Jan 15 '21 at 00:14
0

First, you will have to reduce your items by grouping by productType. Next, you can accumulate the quantities and find the minimum date. Once you have done that, you can group by date. Then, you can transformed your grouped data into the final structure. Finally, you can sort it by date.

If you think about each discrete step, it makes the entire algorithm easy to follow.

const itemList = [
  { itemDetail: { date: '2020-12-30', quantity: 5, productType: 'shirt' }},
  { itemDetail: { date: '2021-01-05', quantity: 4, productType: 'trouser' }},
  { itemDetail: { date: '2020-12-30', quantity: 1, productType: 'shirt' }},
  { itemDetail: { date: '2021-01-05', quantity: 2, productType: 'jacket' }}
];

const groupedByProductType = itemList.reduce((acc, item) => {
  const { itemDetail: { productType } } = item;
  const prev = acc[productType] || [];
  return { ...acc, [productType]: [ ...prev, item ] };
}, {});

const rollup = Object.entries(groupedByProductType).map(([productType, arr]) => {
  const minDate = Math.min(...arr.map(({ itemDetail: { date } }) => new Date(date)));
  const quantity = arr.reduce((acc, { itemDetail: { quantity } }) => acc + quantity, 0);
  const date = arr.find(({ itemDetail: { date } }) =>
    new Date(date).getTime() === minDate).itemDetail.date;
  return {
    itemDetail: {
      date,
      quantity,
      productType
    }
  };
});

const groupedByDate = rollup.reduce((acc, item) => {
  const { itemDetail: { date } } = item;
  const prev = acc[date] || [];
  return { ...acc, [date]: [ ...prev, item ] };
}, {});

const result = Object.entries(groupedByDate).map(([date, items]) => ({
  date,
  items: items.map(({ itemDetail: { productType, quantity } }) =>
    ({ productType, quantity }))
}));

result.sort(({ date: a }, { date: b }) => new Date(a) - new Date(b));

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

Result

[
  {
    "date": "2020-12-30",
    "items": [
      {
        "productType": "shirt",
        "quantity": 6
      }
    ]
  },
  {
    "date": "2021-01-05",
    "items": [
      {
        "productType": "trouser",
        "quantity": 4
      },
      {
        "productType": "jacket",
        "quantity": 2
      }
    ]
  }
]
Mr. Polywhirl
  • 31,606
  • 11
  • 65
  • 114
0

This data transformation can be separated into 3 (base) steps.

  1. Group the items by date.
  2. Sort the groups by date.
  3. Transform each group into desired result.
    1. Group all items in the date group by product type.
    2. Sum the quantity of each product type group.
    3. Combine the results into the desired result.

const itemList = [
  { itemDetail: { date: '2020-12-30', quantity: 5, productType: 'shirt'   } },
  { itemDetail: { date: '2021-01-05', quantity: 4, productType: 'trouser' } },
  { itemDetail: { date: '2020-12-30', quantity: 1, productType: 'shirt'   } },
  { itemDetail: { date: '2021-01-05', quantity: 2, productType: 'jacket'  } },
];

const itemListFinalForm =
  Array.from(groupBy(
    // map first since we don't need the itemDetail wrapper
    itemList.map(item => item.itemDetail),
    item => item.date,
  ))
  .sort(asc(([date]) => Date.parse(date)))
  .map(([date, items]) => ({
    date: date,
    items: Array.from(groupBy(items, item => item.productType))
      .map(([type, items]) => ({
        productType: type,
        quantity: sumBy(items, item => item.quantity),
      }))
  }));

console.log(itemListFinalForm);


// helpers
function groupBy(iterable, fn) {
  const groups = new Map();
  for (const item of iterable) {
    const key = fn(item);
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(item);
  }
  return groups;
}

function asc(fn) {
  const cache = new Map();
  
  function fromCache(item) {
    if (!cache.has(item)) cache.set(item, fn(item));
    return cache.get(item);
  }
  
  return function (a, b) {
    a = fromCache(a);
    b = fromCache(b);
    return -(a < b) || +(a > b);
  }
}

function sumBy(iterable, fn) {
  let sum = 0;
  for (const item of iterable) sum += fn(item);
  return sum;
}
3limin4t0r
  • 13,832
  • 1
  • 17
  • 33
0

Driven to the extreme you can do everything in a "single line", see here:

const itemList = [
{itemDetail: {date: '2020-12-30' , quantity: 5, productType: 'shirt'  }},
{itemDetail: {date: '2021-01-05' , quantity: 4, productType: 'trouser'  }},
{itemDetail: {date: '2020-12-30', quantity: 1, productType: 'shirt'  }},
{itemDetail: {date: '2021-01-05', quantity: 2, productType: 'jacket'}}
];

const res=Object.entries
(itemList.reduce
  ((a,{itemDetail},i)=>
     (i=a[itemDetail.date]=a[itemDetail.date]||{},
      i[itemDetail.productType]=+itemDetail.quantity+(i[itemDetail.productType]||0),
      a
     ), {} 
  )
).map(([k,o])=>({date: k, items: Object.entries(o).map(([p,n])=>({productType: p,quantity: n }) )}));

console.log(res)
Carsten Massmann
  • 16,701
  • 2
  • 16
  • 39