我正在使用Firebase构建排行榜。使用Firebase的优先系统跟踪玩家在排行榜中的排名。
在我的程序执行的某个时刻,我需要知道给定用户在排行榜中的位置。我可能有成千上万的用户,因此遍历所有用户以找到具有相同ID的对象(从而给我索引)并不是真正的选择。
是否有更高效的方法来确定Firebase中有序列表中对象的索引?
修改:我试图找出以下内容:
/
---- leaderboard
--------user4 {...}
--------user1 {...}
--------user3 {...} <- what is the index of user3, given a snapshot of user3?
--------...
答案 0 :(得分:6)
如果您正在处理数十或数百个元素并且不介意带宽受到影响,请参阅Kato
的答案。
如果您正在处理记录,那么您需要遵循pperrin
答案中原则上概述的方法。以下答案详细说明了这一点。
Flashlight是一个方便的节点脚本,可以将elasticsearch与Firebase数据同步。
Read about how to set it up here.
在撰写本文时,Flashlight无法告诉ElasticSearch您只对匹配的文档的 number 感兴趣,不文档本身。
I've submitted this pull request使用简单的单行修复来添加此功能。如果您在阅读此答案时没有关闭,只需手动更改手电筒的副本/分支。
这是我通过Firebase发送的查询:
{
index: 'firebase',
type: 'allTime',
query: {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"range": {
"points": {
"gte": minPoints
}
}
}
}
},
options: {
"search_type": "count"
}
};
将points
替换为您的用户的字段跟踪点的名称,将minPoints
替换为您感兴趣的排名的用户的点数。
响应将类似于:
{
max_score: 0,
total: 2
}
total
是具有相同或更多分数的用户数 - 换句话说,就是用户的排名!
答案 1 :(得分:1)
由于Firebase存储对象而不是数组,因此元素没有&#34;索引&#34;在列表中 - JavaScript和扩展JSON对象本质上是无序的。正如Ordered Docs中所述,并在leaderboard example中进行了演示,您可以使用优先级来完成排序。
设定操作:
var ref = new Firebase('URL/leaderboard');
ref.child('user1').setPriority( newPosition /*score?*/ );
读取操作:
var ref = new Firebase('URL/leaderboard');
ref.child('user1').once('value', function(snap) {
console.log('user1 is at position', snap.getPriority());
});
答案 2 :(得分:1)
要获得所需的信息,某个过程必须枚举节点以对其进行计数。那么问题就在于何时/什么时候进行兜售。
在客户端使用.count()意味着每次需要它时都会完成,它会非常准确,但处理/流量很大。
如果您保留一个单独的计数索引,则需要定期刷新或不断更新(每次插入都会导致其余条目混乱)。
根据数据的分布和数量,我很想采用后台进程,只需每次(比如说)十次或二十次添加即可更新(/重建)索引。并为每个(比如说)10个位置编制索引。
"Leaderboard",$UserId = priority=$score
...
"Rank",'10' = $UserId,priority=$score
"Rank",'20' = $UserId,priority=$score
...
从得分中你得到十分之一,然后在你的“排行榜”上使用startat / endat / count将其归结为单位。
如果您的后台进程正在监控排行榜的更新,那么它对索引的更新可能会更加智能,或者只是根据需要进行更新。
答案 3 :(得分:0)
我知道这是一个老问题,但是我只想分享我的解决方案以供将来参考。首先,Firebase生态系统已经发生了很大变化,我假设了当前的最佳做法(即Firestore和无服务器功能)。我在构建实际应用程序时亲自考虑了这些解决方案,最后选择了计划的近似排名。
在准备用户排行榜时,我需要做一些假设:
每当用户增加得分时,Firebase函数都会通过查询所有超过的用户(其得分> =用户的旧分数,但<用户的新分数)来响应此更改,并且其排名为decreased乘以1。根据前面提到的查询的大小,用户自己的排名为increased。
该排名现在可以在客户阅读中立即获得。但是,建议功能内部的排名更新相当繁重。确切的操作次数在很大程度上取决于您的应用程序,但是对于我个人的应用程序而言,得分变化的频率很高,并且得分的相对接近度使这种方法效率太低。我很好奇是否有人找到了更有效的(实时)替代方案。
安排一个Firebase函数,以通过分数简单地对整个用户集合进行排序,并写回每个用户的排名(在批处理更新中)。此过程可以每天重复一次,或更频繁/不频繁,具体取决于您的应用程序。对于N个用户,该功能始终进行N次读取和N次写入。
作为“计划的排名”选项的替代方法,我建议采用一种近似技术:不是将每个用户的确切排名写在每个计划的更新上,而是将用户集合(仍然像以前一样排序)简单地分成M个块大小相等且绑定这些块的分数将写入单独的“统计”集合。
因此,例如:如果为了简单起见,使用M = 3,并且读取了60个按升序排序的用户,则我们有20个用户中的三个大块。对于每个(仍排序的块),我们获得最后一个(块的最低分数)和第一个用户(块的最高分数)的分数(即包含该块的所有分数的范围)。假设得分最低的块得分在20-120之间,第二块得分在130-180之间,得分最高的块在200-350之间。现在,我们只需将这些范围写入“统计”集合(无论有多少用户,写入计数都将减少为1!)。
在排名检索中,用户只需阅读最新的“统计”文档,并通过将范围与自己的得分进行比较来近似其百分位排名。当然,用户的得分可能高于先前“统计”更新中的最高得分,也可能低于最低得分,但是我只认为它们分别属于最高得分组和最低得分组。
在我自己的应用程序中,我使用M = 20,因此可以以5%的精度显示用户百分位等级,甚至可以使用线性插值法在该范围内进行估算(例如,如果用户得分为450,并且落入40% -45%的块级介于439-474之间,我们估计用户的百分等级为40 + (450 - 439) / (474 - 439) * 5 = 41.57...%
。
如果您想得到真实的幻想,还可以通过将预期的分数分布(例如正态分布)拟合到测量范围来估算确切的百分位排名。
注意:所有用户都需要阅读“统计”文档以近似其排名。但是,在大多数应用程序中,并非所有用户实际上都查看统计信息(因为他们不是每天都处于活动状态,或者只是对统计信息不感兴趣)。就个人而言,我还使用了“ stats”文档(命名不同)来存储用户之间共享的其他DB值,因此无论如何该文档都已被检索到。除此之外,读取比写入便宜3倍。最坏的情况是2N次读取和1次写入。