【问题标题】:Javascript sort array of objects on keyJavascript对键上的对象数组进行排序
【发布时间】:2016-09-27 07:02:14
【问题描述】:

我有一个对象数组(来自 ajax/PDO 调用),如下所示:

signincounts: [{ status: 1
   count:  3
   teamid: 4
 },{
   status: 0
   count:  1
   teamid: 2
 },{
   status: 0
   count:  2
   teamid: 4
}]

这些对象表示登录特定事件的用户数:

  • 状态 = 1(已登录,将出席)
  • 状态 = 0(已退出,不会参加)
  • count = x 登录/退出人数
  • teamid = y 他们所属的团队

另外,我还有以下信息:

teamcount : {
    2: 5
    4: 6
}
  • teamid : 玩家总数

我现在想在 Javascript 中对其进行排序以显示如下所示的登录统计信息:

第 2 队:登录/退出/尚未决定

第 4 队:已登录/已退出/尚未决定

在这种情况下:

第 2 队:0 / 1 / 4

第 4 队:3 / 2 / 1

困难:

  • teamid 是唯一的,但不一定是连续的
  • 如果状态计数为零,则它不是返回的 JSON 的一部分
  • 对象不一定按顺序排列
  • 可以有两到几十个团队
  • 字符串输出应按 teamid 排序,查看者的团队在顶部(还有一个附加变量 playerteamid,可用于简单检查)

我在想我需要对其进行排序,但由于未定义的零计数和非顺序的团队ID,我不确定如何处理它。 我最初的计划是使用两个空数组(signin_count、signout_count),然后循环遍历 ajax JSON 并将计数推入其中(如果是玩家团队则取消移位)。但是,我怎样才能在其中保留 teamid 参考和未定义的值(因此索引保持“平行”)?而且我假设无论如何都有一种更清洁和更好的方法(在对象数组级别本身),这允许我将它直接排序到结果字符串中。

也尝试过搜索,但没有找到任何关于双重排序同时考虑特定键/值对问题的内容。

希望能得到一些指点。

【问题讨论】:

标签: javascript arrays sorting


【解决方案1】:

那好吧!让我们分两个阶段进行:

过滤阶段

首先,我们必须消除不应打印的条目。你提到计数等于0

data = data.filter(function(entry) { entry.count > 0 })

排序阶段

现在data 中的所有剩余条目都将被打印出来,我们可以将它们按所需的顺序排序。您提到了两个约束:

  • 应首先显示较低的团队 ID
  • 特定的团队 ID 应始终显示在顶部

Javascript Array 对象的.sort() 方法是使用具有此签名的比较函数调用的:

function compare(a, b) returns Number

返回的Number解释如下:

  • 如果compare 返回< 0,则ab 之前
  • 如果compare 返回> 0,则ba 之前
  • 如果compare 返回0,则没关系(通常尊重之前的顺序)

所以,让我们编写一个可以对数据进行排序的compare 函数:

function compare(a, b) {
  // The special USER_TEAM_ID goes before any other element:
  if (a.teamId === USER_TEAM_ID) return -1

  // For any other case:
  return a.teamId - b.teamId // invert these to reverse the sort
}

现在你可以打电话了:

data.sort(compare)

【讨论】:

  • 这听起来不错,简单的解决方案,但它没有解决登录计数稀疏的问题(没有计数为零的对象)。这些应该打印为零,因此零必须是假定的默认值。
  • 我也刚刚注意到:排序/比较功能并不总是有效:如果用户的 teamid 是数组中的最后一个,它显然不会被检查为“a”,所以 if (a.teamid === USER_TEAM_ID) 在这种情况下永远不会到达。我可以使用常规的 sort() 来捕捉这种情况吗?
【解决方案2】:

两步:

  • 首先,迭代teamcount并创建一个对象teamid => [0, 0, count]。换句话说,假设所有团队成员都没有决定

  • 然后,迭代 signincounts 并做两件事:将 c.count 添加到 result[teamid] 的相应插槽并从 result[teamid][undecided] 中减去 c.count

这会给你你想要的统计数据,最终的排序和输出留给读者。

代码:

data = {
    signincounts: [{
        status: 1,
        count: 3,
        teamid: 4
    }, {
        status: 0,
        count: 1,
        teamid: 2
    }, {
        status: 0,
        count: 2,
        teamid: 4
    }]
    ,
    teamcount: {
        2: 5,
        4: 6
    }
};

res = {}

Object.keys(data.teamcount).forEach(teamid => 
    res[teamid] = [0, 0, data.teamcount[teamid]]);

data.signincounts.forEach(c => {
    res[c.teamid][c.status] = c.count;
    res[c.teamid][2] -= c.count;
});

console.log(res)

【讨论】:

  • 我非常喜欢这个解决方案。我有类似的想法,但我不知道结合 foreach 最好地迭代数组 object.keys() 的必要函数这听起来像是将来会派上用场的东西:)。跨度>
【解决方案3】:

我认为lodash 在这里会为您服务。

var signincounts = [{
   status: 1,
   count:  3,
   teamid: 4
 },{
   status: 0,
   count:  1,
   teamid: 2
 },{
   status: 0,
   count:  2,
   teamid: 4
}],

teamcount = {
    2: 5,
    4: 6
};

var result = _.chain(signincounts)
              .groupBy('teamid')
              .mapValues(mapTeams).value();

console.log(result);

// helper function for mapping from grouped data
function mapTeams(teamData, teamid) {
  var team = {};
  team.undecided = teamcount[teamid];

  var signed_in = _.find(teamData, {status: 1}), // gets the object that tells us how many are signed in
      signed_out = _.find(teamData, {status: 0}); // gets the object that tells us how many are signed out

  team.signed_in = (signed_in && signed_in.count) ? signed_in.count : 0; // guard against undefined
  team.signed_out = (signed_out && signed_out.count) ? signed_out.count : 0; // guard against undefined
  team.undecided -= (team.signed_in + team.signed_out);

  return team;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>

所以这就是正在发生的事情。首先我们调用_.chain(signincounts)。这意味着我们将获取我们的登录计数并将其传递给函数管道,每个函数都会修改前一个函数的结果。因此,在发出要链接的信号后,我们将数组中的所有对象按teamid 分组。我们得到以下对象:

{ '2': [ { status: 0, count: 1, teamid: 2 } ],
  '4':
   [ { status: 1, count: 3, teamid: 4 },
     { status: 0, count: 2, teamid: 4 } ] }

因为我们正在链接,所以这是传递给mapValues 的内容。 mapValues 根据传入的回调函数创建一个与传入的对象具有相同键的新对象,但具有不同的值。所以我们的结果将是一个以teamids为键的对象,这我相信你想。辅助函数定义将映射到每个 teamid 的内容。这个辅助函数针对每个键值对执行。

在辅助函数中,我们创建了一个对象,该对象将作为我们新的 teamid 映射。因此,对于每个 teamid,我们将返回一个看起来像 { signed_in: <Number>, signed_out: <Number>, undecided: <Number> } 的对象。我们设置未定为团队的总人数。然后我们从前面的结果中找到数组中的对象,告诉我们有多少人参加了该团队,然后我们找出有多少人没有参加。我们相应地分配signed_insigned_out 字段。然后我们从我们的总数中减去有多少已签入和签出,得到我们未定的计数。

【讨论】:

  • 感谢您的建议。虽然我不急于使用额外的库(尤其是对于这个单一的案例),但这是我第一次听说这个特定的库。而且里面的很多功能看起来都非常强大。也感谢您对发生的事情的详细解释。
猜你喜欢
  • 1970-01-01
  • 2016-12-23
  • 1970-01-01
  • 2021-10-30
  • 2016-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多