获取Firebase中有序列表中对象的索引

时间:2014-04-24 03:02:54

标签: javascript firebase

我正在使用Firebase构建排行榜。使用Firebase的优先系统跟踪玩家在排行榜中的排名。

在我的程序执行的某个时刻,我需要知道给定用户在排行榜中的位置。我可能有成千上万的用户,因此遍历所有用户以找到具有相同ID的对象(从而给我索引)并不是真正的选择。

是否有更高效的方法来确定Firebase中有序列表中对象的索引?

修改:我试图找出以下内容:

/
---- leaderboard
--------user4 {...}
--------user1 {...}
--------user3 {...} <- what is the index of user3, given a snapshot of user3?
--------...

4 个答案:

答案 0 :(得分:6)

如果您正在处理数十或数百个元素并且不介意带宽受到影响,请参阅Kato的答案。

如果您正在处理记录,那么您需要遵循pperrin答案中原则上概述的方法。以下答案详细说明了这一点。

步骤1:设置Flashlight以使用ElasticSearch

索引排行榜

Flashlight是一个方便的节点脚本,可以将elasticsearch与Firebase数据同步。

Read about how to set it up here.

步骤2:修改Flashlight以允许您将查询选项传递给ElasticSearch

在撰写本文时,Flashlight无法告诉ElasticSearch您只对匹配的文档的 number 感兴趣,文档本身。

I've submitted this pull request使用简单的单行修复来添加此功能。如果您在阅读此答案时没有关闭,只需手动更改手电筒的副本/分支。

第3步:执行查询!

这是我通过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功能设置排名,但还会将“总用户”计数器增加1)。 / li>
  • 分数只能增加(通过一些调整,分数也可以得到支持)。
  • 已删除的用户在排行榜上保留了一个“幽灵”点。

每当用户增加得分时,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次写入。