Sunday, 4 July 2021

Grouping array of objects by multiple properties

I have an array of notifications that I want to group by certain conditions (like facebook's notifications)

var data = [
    { id: 1, type: 'shop.follower', by: { id: 2, name: 'User B', }, in: null, read_at: '2021-01-03 10:15:43', created_at: '2020-08-02 05:21:20' },
    { id: 2, type: 'product.liked', by: { id: 2, name: 'User B' }, in: { id: 1, ... }, read_at: '2021-01-03 10:15:43', created_at: '2020-08-02 05:24:45' },
    { id: 3, type: 'product.commented', by: { id: 3, name: 'User C' }, in: { id: 1, ... }, read_at: '2021-02-20 20:01:39', created_at: '2021-02-19 16:21:43' },
    { id: 4, type: 'product.liked', by: { id: 4, name: 'User D' }, in: { id: 1, ... }, read_at: '2021-03-29 15:14:21', created_at: '2021-03-28 08:11:50' },
    { id: 5, type: 'product.liked', by: { id: 3, name: 'User C' }, in: { id: 1, ... }, read_at: null, created_at: '2021-03-28 08:12:24' },
    { id: 6, type: 'shop.follower', by: { id: 4, name: 'User E' }, in: null, read_at: null, created_at: '2021-05-23 10:02:21' },
    { id: 7, type: 'shop.follower', by: { id: 3, name: 'User C' }, in: null, read_at: null, created_at: '2021-07-18 10:31:12' },
    { id: 8, type: 'comment.replied', by: { id: 4, name: 'User D' }, in: { id: 6, ... }, read_at: null, created_at: '2021-07-24 08:34:25' },
]

Let's say I want to group by date in descending order

So I have this code:

function sortByDate(array, desc = true) {
    if (desc === false) {
        // Ascending order
        return array.sort((a, b) => {
            if (new Date(a.created_at) > new Date(b.created_at)) {
                return 1
            } else {
                return -1
            }

            return 0
        })
    }

    // Descending order
    return array.sort((a, b) => {
        if (new Date(a.created_at) < new Date(b.created_at)) {
            return 1
        } else {
            return -1
        }

        return 0
    })
}

So now we have array like this:

[
    { id: 8, type: 'comment.replied', by: { id: 4, name: 'User D' }, in: { id: 6, ... }, read_at: null, created_at: '2021-07-24 08:34:25' },
    { id: 7, type: 'shop.follower', by: { id: 3, name: 'User C' }, in: null, read_at: null, created_at: '2021-07-18 10:31:12' },
    { id: 6, type: 'shop.follower', by: { id: 4, name: 'User E' }, in: null, read_at: null, created_at: '2021-05-23 10:02:21' },
    { id: 5, type: 'product.liked', by: { id: 3, name: 'User C' }, in: { id: 1, ... }, read_at: null, created_at: '2021-03-28 08:12:24' },
    { id: 4, type: 'product.liked', by: { id: 4, name: 'User D' }, in: { id: 1, ... }, read_at: '2021-03-29 15:14:21', created_at: '2021-03-28 08:11:50' },
    { id: 3, type: 'product.commented', by: { id: 3, name: 'User C' }, in: { id: 1, ... }, read_at: '2021-02-20 20:01:39', created_at: '2021-02-19 16:21:43' },
    { id: 2, type: 'product.liked', by: { id: 2, name: 'User B' }, in: { id: 1, ... }, read_at: '2021-01-03 10:15:43', created_at: '2020-08-02 05:24:45' },
    { id: 1, type: 'shop.follower', by: { id: 2, name: 'User B', }, in: null, read_at: '2021-01-03 10:15:43', created_at: '2020-08-02 05:21:20' },
]

Now that our array is sorted, I created a function:

// https://www.tutorialspoint.com/most-efficient-method-to-groupby-on-an-array-of-objects-in-javascript
function groupByProperty(array, property) {
    return array.reduce((acc, object) => {
        const key = object[property]

        if (! acc[key]) {
            acc[key] = []
        }

        acc[key].push(object)

        return acc
    }, {})
}

Then, I run this code

Object.values(groupByProperty(data, 'type'))

Which return:

[
    [
        { id: 8, type: 'comment.replied', by: { id: 4, name: 'User D' }, in: { id: 6, ... }, read_at: null, created_at: '2021-07-24 08:34:25' }
    ],
    [
        { id: 7, type: 'shop.follower', by: { id: 3, name: 'User C' }, in: null, read_at: null, created_at: '2021-07-18 10:31:12' },
        { id: 6, type: 'shop.follower', by: { id: 4, name: 'User E' }, in: null, read_at: null, created_at: '2021-05-23 10:02:21' },
        { id: 1, type: 'shop.follower', by: { id: 2, name: 'User B', }, in: null, read_at: '2021-01-03 10:15:43', created_at: '2020-08-02 05:21:20' }
    ],
    [
        { id: 5, type: 'product.liked', by: { id: 3, name: 'User C' }, in: { id: 1, ... }, read_at: null, created_at: '2021-03-28 08:12:24' },
        { id: 4, type: 'product.liked', by: { id: 4, name: 'User D' }, in: { id: 1, ... }, read_at: '2021-03-29 15:14:21', created_at: '2021-03-28 08:11:50' },
        { id: 2, type: 'product.liked', by: { id: 2, name: 'User B' }, in: { id: 1, ... }, read_at: '2021-01-03 10:15:43', created_at: '2020-08-02 05:24:45' }
    ],
    [
        { id: 3, type: 'product.commented', by: { id: 3, name: 'User C' }, in: { id: 1, ... }, read_at: '2021-02-20 20:01:39', created_at: '2021-02-19 16:21:43' }
    ],
]

I don't know how to group object that has date gap interval between 10 minutes

Expected output:

  1. Same type
  2. Same in: { id: ... } (Except for type: shop.follower)
  3. If #1 and #2 true, check for similar objects with created_at: ... date gap between 10 minutes
  4. If we have a case like #3 (multiple), if one among it has read_at = null, then it unread notification, then get the latest (newest) date

In id: 4 and id: 5, the gap between timestamps is less than 10 minutes, so I want it to group as one (Again, sorry for bad english)

example:

|------------------------------------------------------------------------------|
| - (UNREAD) User D replied to your comment .....,   2021-07-24 08:34:25       |
| - (UNREAD) User C start follow your shops .....,   2021-07-18 10:31:12       |
| - (UNREAD) User E start follow your shops .....,   2021-05-23 10:02:21       |
| - (UNREAD) User C and D liked your product .....,  2021-03-28 08:12:24       | <= (Please pay attention)
| - (READ) User C commented on your product .....,   2021-02-19 16:21:43       |
| - (READ) User B liked your product .....,          2020-08-02 05:24:45       |
| - (READ) User B start follow your shops .....,     2020-08-02 05:21:20       |

This is the code I tried to find gap between 10 minutes

function inRangeBetween(val, min, max) {
    if (val >= min && val <= max) {
        return true
    }

    return false
}

var startingPoint = { min: 0, max: 0, type: null },
    newData = []

for (var i = 0; i < data.length; i++) {
    if (startingPoint.min < 1
        && startingPoint.max < 1
        && startingPoint.type === null) {
        console.log('Starting point')
        var start = new Date(data[i].created_at)
        startingPoint.min = start.getTime()
        startingPoint.max = start.getTime() + (10 * 60000)
        startingPoint.type = data[i].type
        newData[data[i].type] = []
    } else {
        // startingPoint has values

        if (inRangeBetween(new Date(data[i].created_at).getTime(), startingPoint.min, startingPoint.max
            && data[i].type === startingPoint.type) {
            console.log(`Pushing new object to key ${data[i].type}`)
            newData[data[i].type].push(data[i])
        } else {
            // Set new values for startingPoint, and start again comparing
            console.log('Starting point values changes')
            startingPoint.min = new Date(data[i]).getTime()
            startingPoint.min = new Date(data[i]).getTime() + (10 * 60000)
            startingPoint.type = data[i].type
            newData[data[i].type] = []
            newData[data[i].type].push(data[i])
        }
    }
}

// Not working

How to achieve this? (Stuck for this problem for 5 days)

Thanks in advance



from Grouping array of objects by multiple properties

No comments:

Post a Comment