Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

group by array of objects with condition and custom key

I have below array of objects,

[{

    a: 1,
    created_on: '2021-04-23 10:00:01',
}, {
    b: 1,
    created_on: '2021-04-24 09:03:01',
}, {
    b: 1,
    created_on: '2021-04-24 13:03:01',
}]

First, I need to group by with date ignoring times.

So,

{
 "2021-04-23": [{
    a: 1,
    created_on: '2021-04-23 10:00:01',
  }],
....
}

If it were a date like created_on: '2021-04-23', I could use lodash/groupBy.

I still could use it but now it involves some condition to be applied to convert those created_on into date format.

Further, I also need to group by those each grouped by date result as such that their created_on falls within a time interval.

"SALES_SLOTS": [
    {
      "slot": "00:00-04:00"
    },
    {
      "slot": "04:00-08:00"
    }, 
    {
      "slot":"08:00-12:00"
    }, 
    {
      "slot":"12:00-16:00"
    },
    {
      "slot":"16:00-20:00"
    },
    {
      "slot":"20:00-24:00"
    }
  ],

So, if created_on: '2021-04-23 10:00:01', it should come in the group,

{
  "08:00-12:00": [{
     a: 1,
     created_on: '2021-04-23 10:00:01',
  }]
}

So, first results should be grouped by date from created on and then elements in each groups should be further group by defined time slots.

I suppose, if I could use group by condition, I could achieve the desired result.

like image 379
8bitIcon Avatar asked Apr 26 '21 05:04

8bitIcon


People also ask

How do I Group products by category in an array?

Here's how you would use array.groupBy () to create the same grouping by category: Try the demo. products.groupBy (product => {...}) returns an object where each property has the key as category name and value as an array with the products from the corresponding category.

How to display objects in groups based on specified property values?

The Group-Object cmdlet displays objects in groups based on the value of a specified property. Group-Object returns a table with one row for each property value and a column that displays the number of items with that value. If you specify more than one property, Group-Object first groups them by the values of the first property, and then, ...

How to group the items of an array in Python?

If you want to easily group the items of an array (similarly to GROUP BY in SQL), then welcome the new methods array.groupBy () and array.groupByToMap (). Both functions accept a callback that should return the key of the group where the current items must be inserted.

How does group-object group objects of different types?

When grouping objects of different .NET Core types, Group-Object uses the following rules: Same Property Names and Types. If the objects have a property with the specified name, and the property values have the same .NET Core type, the property values are grouped by using the same rules that would be used for objects of the same type.


Video Answer


2 Answers

Vanilla JS


Using Array#reduce with Map.

  1. Grouping by date is fairly simple, you can group the data by created_on.slice(0, 10) using a Map.

  2. Then I've created a helper function getSlot that returns a slot based on the time passed. Now, since your slots are fairly wide, testing just the hour part would be sufficient.

  3. Next, for every group of date, again group it by time using getSlot helper and Map and using Object.fromEntries you can get the desired object.

NOTE: I've used expressions instead of statements inside my code (using commas, defaulted parameters) which is just a preferential choice.

const 
  arr = [{ a: 1, created_on: "2021-04-23 10:00:01" }, { b: 1, created_on: "2021-04-24 09:03:01" }, { b: 1, created_on: "2021-04-24 13:03:01" }],
  
  slots = [{ slot: "00:00-04:00" }, { slot: "04:00-08:00" }, { slot: "08:00-12:00" }, { slot: "12:00-16:00" }, { slot: "16:00-20:00" }, { slot: "20:00-24:00" }],
      
  grpDate = Array.from(
    arr.reduce(
      (m, o, _i, _arr, d = o.created_on.slice(0, 10)) => (
        m.has(d) ? m.get(d).push(o) : m.set(d, [o]), m
      ),
      new Map()
    )
  ),
  
  getSlot = (time) =>
    slots
      .find(({ slot }) => time >= slot.slice(0, 2) && time < slot.slice(6, 8))
      .slot,
      
  grpSlot = grpDate.map(([k, v]) =>
    [k, Object.fromEntries(v.reduce(
      (m, o, _i, _arr, slot = getSlot(o.created_on.slice(11, 13))) => (
        m.has(slot) ? m.get(slot).push(o) : m.set(slot, [o]), m
      ),
      new Map()
    ))]
  );

console.log(Object.fromEntries(grpSlot));

Screenshot of the output


enter image description here

like image 128
Anonymous Panda Avatar answered Oct 24 '22 11:10

Anonymous Panda


This is what I tried using momentjs and lodash.

const bookings = [{
  a: 1,
  created_on: '2021-04-23 10:00:01',
}, {
  b: 1,
  created_on: '2021-04-24 09:03:01',
}, {
  b: 1,
  created_on: '2021-04-24 13:03:01',
}];


const groupedByDate = bookings.reduce((acc, e) => {
  const eDate = moment(e.created_on).format('YYYY-MM-DD');
  if (acc[eDate]) {
    acc[eDate].push(e)
  } else {
    acc[eDate] = [e];
  }
  return acc;
}, { })
const allSlots = ['00:00-04:00', '04:00-08:00', '08:00-12:00', '12:00-16:00','16:00-20:00','20:00-24:00'];
const grupedByDateAndSlots = _.mapValues(groupedByDate, (ele) => {
  return ele.reduce((acc, e) => {
    const createdOnDate = moment(e.created_on).format('YYYY-MM-DD');
    const foundSlot = allSlots.find((slot) => moment(e.created_on).isBetween(moment(`${createdOnDate} ${slot.split('-')[0]}`), moment(`${createdOnDate} ${slot.split('-')[1]}`)))
    if (acc[foundSlot]) {
      acc[foundSlot].push(e)
    } else {
      acc[foundSlot] = [e];
    }
    return acc;
  }, { })
})

console.log(grupedByDateAndSlots)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
like image 25
8bitIcon Avatar answered Oct 24 '22 11:10

8bitIcon