如何获得学生成绩单的排名?

时间:2012-11-18 09:35:15

标签: algorithm mapreduce couchdb ranking

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

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

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

  • 鉴于学生_id,如何在每门课程中检索 学生的等级;
  • 鉴于学生_id,如何检索 学生的总分(数学+物理+化学)等级。

假设我只有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

和Jane的排名应该是

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

如果我没有明确说明问题,请告诉我。

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

  • 创建将分数映射到学生信息的视图。然后我可以查询分数范围,让学生得分在该范围内。

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

Edit2 :课程数量为1K到3K。学生人数将在1M到2M之间。

4 个答案:

答案 0 :(得分:6)

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

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函数没有用,因为函数不会缩小结果集,也因为我无法想出一种按学生姓名查询的方法,并且整个学生列表也是如此时间。我认为列表没有用,因为我再也看不出如何让列表知道学生的名字,同时让整个学生在结果中。

答案 1 :(得分: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可以计算,直到它到达您指定的学生。

您的列表功能“排名”看起来像这样

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名最优秀的化学学生,服务器可能需要一段时间才能完成整个列表。但服务器执行此操作肯定比客户端更容易。

修改 添加了领带处理案例

答案 2 :(得分:2)

所以我认为没有一个解决方案可以完全在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

(没有网址编码):

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(或其他客户端技术)搜索生成的词典,以确定学生在单个(或所有)科目中的排名。

答案 3 :(得分:1)

我有一个基于@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 1}}是#s中注册的学生总数,s是得分低于"math", 99的学生人数。例如,{{1}}的查询将返回32374-32324 = 50,这是在“数学”中得到99的学生的等级。

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