首页 > 解决方案 > 按多个属性对对象数组进行分组

问题描述

我有一系列通知,我想按特定条件进行分组(如 Facebook 的通知)

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: 5, 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' },
]

假设我想按日期降序分组

所以我有这个代码:

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
    })
}

所以现在我们有这样的数组:

[
    { 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: 5, 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' },
]

现在我们的数组已经排序,我创建了一个函数:

// 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
    }, {})
}

然后,我运行这段代码

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

哪个回报:

[
    [
        { 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: 5, 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' }
    ],
]

我想按以下类别对这些通知进行分组:

  1. 相同type(我已经在我的函数中介绍了这个groupByProperty()
  2. 一样in: { id: ... }除了type: shop.follower
  3. 如果 #1 和 #2 为真,请检查created_at: ...日期间隔在 10 分钟之间的类似对象
  4. 如果我们有像#3(多个)这样的情况,如果其中一个有read_at= null,那么它将是未读通知,获取最新(最新)日期

id: 4andid: 5中,时间戳之间的间隔小于 10 分钟,所以我希望它组合为一个

示例预期输出

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

浏览器中的示例

|------------------------------------------------------------------------------|
| - (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       |

这是我尝试的代码find interval 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

如何做到这一点?(卡在这个问题上5天)

提前致谢

标签: javascriptarrays

解决方案


试试这样:

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-01-03 10:15:43', 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: 5, 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' }
]

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
    })
}

function groupByProperties(array, properties) {
    return Object.values(array.reduce((acc, object) => {
        const key = properties.reduce((acc, property) => {
          return acc + (object[property] ? JSON.stringify(object[property]) : '')
        }, '')

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

        acc[key].push(object)

        return acc
    }, {}))
}

function groupByInterval(data, interval) {
  var group;
  
  for(var i = 0; i < data.length; i++) {
    if(1 < data[i].length) {
      
      var max_date = new Date(data[i][0].created_at);
      
      for(var j = data[i].length - 1; 0 < j; j--) {
        var next_date = new Date(data[i][j].created_at)
        
        if(interval < max_date - next_date) {
           if(!group) {
             group = i + 1;
             data.splice(group, 0, [])
           }
           
           data[group].splice(0, 0, data[i][j])
           data[i].splice(j, 1)
        };
      };
      
      if(group) {
         return groupByInterval(data, interval)
      };
    }
    
    data[i].sort((a, b) => {
      if(!a.read_at) {
        return -1
      }
      
      if(!b.read_at) {
        return 1
      }
      
      return 0
    })
  }
  
  data.sort((a, b) => new Date(b[0].created_at) - new Date(a[0].created_at))

  return data
}


sortByDate(data)

//1. Same type
//2. Same in: { id: ... } (Except for type: shop.follower)
data = groupByProperties(data, ['type', 'in'])

//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
data = groupByInterval(data, 1000 * 60 * 10) //10min

console.log(data)

  • groupByProperties()基于groupByProperty(),但接受多个属性进行分组(类别 1 和 2)。它检查属性的值是否为假(例如null),如果是,则将其从分组标准中排除。
  • groupByInterval()根据指定的时间间隔(以毫秒为单位)添加到单独的组(类别 3 和 4)。然后它根据是否虚假对组进行read_at排序,以便对象read_at == null首先出现在每个组中。然后它对组进行排序以达到预期结果中的顺序。

推荐阅读