【问题标题】:How to get the rank for a student-score document?如何获得学生成绩文件的排名?
【发布时间】:2012-11-06 12:00:21
【问题描述】:

我在 CouchDB 中的文档结构如下所示:

{
 "_id": "0a68cdbe4a7f3abf4046bc",
 "_rev": "1-1508",
 "score": {"math":90, "physics": 88, "chemistry": 60},
 "student_name": "Mike"
}

我需要在前端显示以下统计信息来设置学生的个人资料:

  • 给定一个学生 _id,我如何检索 那个学生在每门课程中的排名;
  • 给定一个学生 _id,我如何检索 那个学生的总分(数学+物理+化学)排名。

假设我只有 2 个学生,第 2 个学生的记录如下:

{
 "_id": "0a68cdbe2344a3abf4046bc",
 "_rev": "1-1608",
 "score": {"math":80, "physics": 98, "chemistry": 90},
 "student_name": "Jane"
}

所以 Mike 的排名应该是:

math: 1
physics: 2
chemistry: 2
total: 2

简的等级应该是

math: 2
physics: 1
chemistry: 1
total: 1

如果我没有清楚地说明问题,请告诉我。

我没有弄清楚创建视图以获得排名的方法。我尝试过的:

  • 创建将分数映射到学生信息的视图。然后我可以查询一个分数范围,让学生知道他们的分数在那个范围内。

编辑:通过用户名查询和检索排名的功能不需要仅由视图实现。欢迎任何想法!

Edit2:课程数量为 1K 到 3K。学生人数为 1M 至 2M。

【问题讨论】:

    标签: algorithm mapreduce couchdb ranking


    【解决方案1】:

    我认为仅仅通过一个视图来做你想做的事情是不可能的。 会尝试这样的地图功能:

    function(doc) {
      emit (["math", doc.score.math], doc.student_name);
      emit (["physics", doc.score.physics], doc.student_name);
      emit (["chemistry",doc.score.chemistry], doc.student_name);
      emit (["total",doc.score.math+doc.score.physics+doc.score.chemistry], doc.student_name);
    }
    

    然后我会按课程查询。这将返回按分数排序的学生列表。在那之后,我认为你必须以编程方式在你的软件中选择排名。

    我认为 reduce 函数没有用,因为该函数不会缩小结果集,还因为我无法想出一种方法来通过学生姓名进行查询并同时拥有整个学生列表时间。我也认为一个列表是没有用的,因为我再一次看不到如何让列表知道学生的姓名,同时在结果中包含整个学生集。

    【讨论】:

    • 感谢您的回答。这与我现在的情况相似。如果排名必须通过编程来选择,那么运行时会很慢,对吧?
    • 根据学生和课程的数量,它会很慢,但沙发已经为你整理了数据,这应该可以减轻很多 cpu 负载。
    • 是的,这是我关心的问题。我将拥有数千门课程和数百万学生(这是一门在线课程)。此外,如果有任何新学生完成课程并获得分数,则应更新在他之后的所有其他学生的排名。这不是我所期望的。
    • 那么这两种策略中的一种呢: 1- 使用脚本来查阅您的视图并将更新后的排名保存在单独的数据库中。您可以经常运行此脚本以提供几乎实时的效果。 2- 使用 Cloudant 的视图链接功能:support.cloudant.com/customer/portal/articles/…
    • 另外,如果您使用脚本,您可以依赖 _changes API 在每次文档更改时更新第二个数据库,这将是实时的。
    【解决方案2】:

    也许排序视图和列表的组合会起作用。

    您的 resultByChemistryScore 视图如下所示

    function(doc) {
        emit(doc.score.chemistry, [doc._id, doc.student_name]);
    }
    

    那么您的 GET 请求将类似于 http://localhost:5984/results/_design/results/_view/resultByChemistryScore?descending=true 您可以随时使用 offsetlimit GET 查询参数来实现分页。

    从此时起,list 可以计数,直到它到达您指定的学生。

    你的列表函数“rank”看起来像这样

    function(head, req) {
        start({ "headers": { "content-type": "application/json" } } );
        var row, rank = 0; 
        while ( row = getRow() ) {
            if ( row.id == req.query.id ) break;
            // increment rank if not a tie
            if ( old_row != null && old_row.key != row.key ) 
               rank++;
            old_row = row;
        }; 
        send( JSON.stringify( { "rank" : rank } ) );
    }
    

    您的请求基本上是http://localhost:5984/results/_design/results/_list/rank/resultByChemistryScore?id=fet&descending=true

    这不是很漂亮,我会给你。如果您说...第 1,000,000 名最好的化学学生,服务器可能需要一段时间才能浏览整个列表。但是服务器肯定比客户端更容易做到。

    编辑 增加了领带处理案例

    【讨论】:

    • 感谢您的回答。这种方法与我与@joscas 讨论的约束相同。我知道我所期望的可能无法仅用 couchdb 完成。还是谢谢!
    • 对这个答案的改进将处理关系。一个班级中两个分数相同的学生排名相同。例如,如果两个学生的得分最高,他们都应该排在第一位。下一位跟随他们的学生排名第三。
    • 你是对的@greeness,除了这有点快,因为我们 1) 依靠服务器来完成繁重的工作,2) 繁重的工作不需要任何额外的内存,只有一个计数器。但你是对的,这并不理想。如果您想要一个常数因子时间查找,我可能会建议使用类似我的 resultByChemistryScore 视图,然后只使用排名数据更新另一个数据库。然后,您可以在排名数据库上使用具有反向键/值的视图。发出(sudentId,排名)。但是您必须使用 cron 更新该数据库。
    • @lambmj,是的,你是对的。我认为,如果我们将当前的行与前一行进行比较,并且只有当它是一个不同的分数时才增加计数器,这将确保具有以下分数的学生:[100,90,90,80] 将具有排名 [1, 2、2、3]。该算法对您有用吗?
    • 是的,没错。例如,请参阅我刚刚发布的答案。
    【解决方案3】:

    所以我不认为有一个解决方案可以完全在 CouchDB 中完成,它会为学生、科目配对检索单个值。但是,可以创建一个 map/reduce 视图,它几乎可以生成您正在寻找的内容。然后可以使用该视图的结果来查找学生、科目对的排名。

    我们首先使用与 joscas 建议的地图非常相似的地图构建视图。唯一的区别是主题名称不是硬编码的:

    map.js

    function(doc) {
        var total = 0;
        for (var subject in doc.score)  {
            var score = doc.score[subject];
            emit([subject, score], doc.student_name);
            total += score;
        }
        emit(["total", total], doc.student_name);
    }
    

    我们将它与一个 reduce 函数配对,该函数将为给定 group=truegrouping_level=1 的每个主题生成一个排名

    reduce.js

    function(keys, values) {
        var rankings = {};              // In order to return ties, a simple array can't be used.
        var rank = 0;
        var place = 0;
        var last_score = -1;
        for (var i = 0; i < values.length; i++) {
            var name = values[i];
            var score = keys[i][0][1];  // The 0th element of the key is the [subject, score] array.
            if (score == last_score) {
                // Tie, add another student to this rank.
                place++;
            } else {
                // Not a tie, create a new rank.
                rank += (place + 1);
                rankings[rank] = new Array();
                place = 0;
                last_score = score;
            }
            rankings[rank][place] = name;
        }
        return rankings;
    }
    

    数据

    我在数据集中添加了第三个学生并创建了一些联系以使其变得有趣。以下是使用的数据:

    {
        "_id": "ce6b2cd97e73258014679ab7bb9e7cdc",
        "_rev": "2-b62581d22c186bfc8ebe1703a2dfb506",
        "score": {
            "chemistry": 60,
            "math": 90,
            "physics": 88
        },
        "student_name": "Mike"
    }
    
    {
        "_id": "ce6b2cd97e73258014679ab7bb9e8ada",
        "_rev": "5-94d6cfbd3cf22f903ebc306570d1f1af",
        "score": {
            "chemistry": 90,
            "math": 90,
            "physics": 98
        },
        "student_name": "Jane"
    }
    
    {
        "_id": "ce6b2cd97e73258014679ab7bb9e960b",
        "_rev": "1-d8c7fe88de63cf3d6e9743696f96aad0",
        "score": {
            "chemistry": 61,
            "math": 89,
            "physics": 88
        },
        "student_name":
        "Charlie"
    }
    

    结果

    视图保存为排名,可以这样查询:

    http://127.0.0.1:5984/atest/_design/atest/_view/rank?group=true&group_level=1
    

    产生这个结果:

    {
        "rows":[
            {"key":["chemistry"],"value":{"1":["Jane"],"2":["Charlie"],"3":["Mike"]}},
            {"key":["math"],"value":{"1":["Jane","Mike"],"3":["Charlie"]}},
            {"key":["physics"],"value":{"1":["Jane"],"2":["Charlie","Mike"]}},
            {"key":["total"],"value":{"1":["Jane"],"2":["Charlie","Mike"]}}
        ]
    }
    

    可以这样按主题查询视图(假设最低分是0,最高分是100):

    http://127.0.0.1:5984/atest/_design/atest/_view/rank?group=true&group_level=1&startkey=%5B%22math%22,0%5D&endkey=%5B%22math%22,100%5D
    

    (不带url编码):

    http://127.0.0.1:5984/atest/_design/atest/_view/rank?group=true&group_level=1&startkey=["math",0]&endkey=["math",100]
    

    产生这个结果:

    {
        "rows":[
            {"key":["math"],"value":{"1":["Jane","Mike"],"3":["Charlie"]}}
        ]
    }
    

    可以使用 Javascript(或其他客户端技术)搜索生成的词典,以确定学生在单个(或所有)科目中的排名。

    【讨论】:

    • 感谢@lambmj 考虑tie 案例。尽管不能完全解决我的问题,但这很有帮助。我现在正在考虑使用外部存储进行用户排名查询(例如,redis,它从您创建的视图中获取排名)。对此有什么想法吗?
    • 我认为这是个好主意,因为在 CouchDB 中没有直接的方法可以做你想做的事情。
    【解决方案4】:

    我有一个基于@joscas 回答的想法。 您可以像这样创建视图:

    key         -> value
    ---------------------
    ("math", 0) -> 2
    ("math", 1) -> 3
    ("math", 2) -> 5
    ....
    ("math", 100) -> 50
    

    我假设每门课程的分数范围是从 0 到 100。想法是:

    • 对于每门课程,您可以汇总分数落在该分数桶中的学生数量(每门课程有 101 个桶,即从 bucket0bucket100)。
    • 然后您可以使用 @joscas 指出的链式视图,或者使用另一个外部程序来获取得分直方图的累积分布,如下所示。

    key         -> accumulative value
    ------------------------------------
    ("math", 0) -> 2
    ("math", 1) -> 5
    ("math", 2) -> 10
    ....
    ("math", 99) -> 32324
    ("math", 100)-> 32374
    

    给定课程名称c 和您的分数s,第二个视图告诉您how many students play not as good as you in this course,您可以使用n-#s 从中得出排名,其中n 是注册的学生总数c#s 是分数低于s 的学生人数。例如,"math", 99 的查询将返回 32374-32324 = 50,这是在“数学”中获得 99 的学生的排名。

    对于你的问题的总分部分,你可以使用类似的想法,但改变桶的大小和数量。

    【讨论】:

      猜你喜欢
      • 2017-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多