按多个属性对对象数组进行分组

时间:2021-06-30 09:05:13

标签: javascript arrays

我有一系列通知,我想按特定条件对其进行分组(例如 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(多个)这样的case,如果其中一个有read_at = null,那么它将是一个未读通知,获取最新(最新)日期
<块引用>

id: 4id: 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 天了)

提前致谢

2 个答案:

答案 0 :(得分:2)

试试这个:

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 的对象首先出现在每个组中。然后它会跨组排序以实现预期结果中的顺序。

答案 1 :(得分:1)

sbgib 先生是绝对正确的,但我只是将 sortByDate 函数修改为短一点,如下所示,以减少重复代码,

function sortByDate(array, desc = true) {
  return array.sort((a, b) => {
     var compare = new Date(a.created_at) < new Date(b.created_at);
     return (desc == true) ? ((compare == true) ? 1 : -1) : ((compare == false) ? 1 : -1);
  })
}