Map Reduce Mongo DB:带元素的ODD和EVEN数的总和

时间:2014-09-11 08:40:27

标签: mongodb mapreduce mongodb-query aggregation-framework

我正在尝试处理一个数字系列(集合),分别得到奇数/偶数的总和以及计算每个数字的元素。

数字文档结构如下:

{
_id: <Autogenerated>,
number: <any number, it can repeat. Even if it repeats, it should be added each time. >
} 

输出类似于下面的内容(不完全一般)

{ 
..
{
"odd":<result>, elements:{n1,n3,n5}
},
{
"even":<result>, elements:{n2,n4,n6}
}
..
}

地图功能:

mapf = function(){

    var value = { sum : 0, elements :[] };

    value.sum = this.number;
    value.elements.push(this.number);

    print(tojson(value));

    if( this.number % 2 != 0 ){
    emit( "odd", value );   
    }

    if( this.number % 2 == 0 ){
    emit( "even", value );  
    }
}

减少Values参数:

值是从map:

发出的JSON数组
   [{
        "sum": 1,
        "elements": [1]
    }, {
        "sum": 3,
        "elements": [3]
    } ... ]

减少功能:

  reducef = function(key, values){

    var result = { sum : 0 , elements:[] };
    print("K " + key +"Values array " + tojson(values) );

    for(var i = 0; i<values.length;i++ ){

        v = values[i];

        print("Key "+key+"V.JSON"+tojson(v)+" V.SUM -> "+v.sum);

        result.sum += v.sum;
        result.elements.push(v.elements[0]);

        print(tojson(result));

    }
    return result;  
}

我正确地得到了总和,但是元素数组没有正确填充。它只包含一些考虑用于计算的元素。

<小时/> 的更新

根据Neil给出的答案,我进一步验证了我的代码。我发现我的代码没有任何修改,适用于小型数据集,但不适用于大型数据集。

以下是我已经验证过的点,我发现我的代码是正确的。

print("K " + key +"Values array " + tojson(values) );

reduce函数的上一行导致打印出values个对象。

[{
    "sum": 1,
    "elements": [1]
}, {
    "sum": 3,
    "elements": [3]
}, {
    "sum": 5,
    "elements": [5]
}, {
    "sum": 7,
    "elements": [7]
}, {
    "sum": 9,
    "elements": [9]
}, {
    "sum": 11,
    "elements": [11]
}, {
    "sum": 13,
    "elements": [13]
}, {
    "sum": 15,
    "elements": [15]
}, {
    "sum": 17,
    "elements": [17]
}, {
    "sum": 19,
    "elements": [19]
}]

因此,在最终结果result.elements.push(v.elements[0]);中将元素推送到数组的行应该是正确的。

在map函数中,在发出之前,我正在修改value.sum,如下所示

value.sum = this.number;

这样可以确保sum 不为零,并且由于此原因正确添加了数字。

  • 当我用20条记录,40条记录,100条记录测试此代码时,它运行良好。
  • 当我使用20000条记录测试此代码时,总和值是正确的但元素数组 每个不包含10000个元素(奇数和偶数在集合中均匀分布)。

在后面的情况下,我得到以下信息:

查询未录制(太大)

1 个答案:

答案 0 :(得分:0)

好的,有一个明显的原因,你似乎已经阅读了一些the documentation并且至少应用了这条规则:

  
      
  • “返回对象的类型必须与map函数发出的值的类型相同......”
  •   

这意味着map函数和reduce函数基本上具有相同的输出,你做了:

{ sum : 0, elements :[] };

但有一段文件尚未被理解:

  
      
  • “MongoDB可以为同一个键多次调用reduce函数。在这种情况下,该键的reduce函数的前一个输出将成为下一个reduce函数调用的输入值之一键“。
  •   

所以整个事情出错的地方在于你假设因为你的“map”函数只发出一个元素,那么“elements”数组中只有一个元素。仔细阅读上述内容后说这不是真的。事实上,“减少”的输出很可能会再次反馈到“减少”功能。这确实是mapReduce如何处理“values”数组的大量值。

要解决此问题,请在“减少”功能中更改此项:

result.elements.push(v.elements[0]);

对此:

v.elements.forEach(function(element) {
    result.elements.push(element);
}

以这种方式,当“reduce”函数返回已经总结了几个“元素”并将它们推送到列表的结果时,那个“输入”将被正确处理并与任何其他“值”合并“随之而来。

顺便说一句。我认为你在映射器中实际意味着这个:

var value = { sum : 1, elements :[] };

否则此处的代码只是汇总0

result.sum += v.sum;

但聚合做得更好

所有这些都说下面的聚合框架语句做同样的事情,但在本机代码中实现更好更快:

db.collection.aggregate([
    { "$project": {
        "type": { "$cond": [
            { "$eq": [ { "$mod": [ "$number", 2 ] }, 0 ] },
            "even",
            "odd"
        ]},
        "number": 1
    }},
    { "$group": {
        "_id": "$type",
        "sum": { "$sum": 1 },
        "elements": { "$push": "$number" }
    }}
])

并且还要注意,在这两种情况下,你并不是真正“总结元素”,而是“计算”它们。因此,如果您想要总和,则mapReduce部分变为:

//result.sum += v.sum;
v.elements.forEach(function(element) {
    result.sum += element;
    result.elements.push(element);
}

聚合部分变为:

    { "$group": {
        "_id": "$type",
        "sum": { "$sum": "$number" },
        "elements": { "$push": "$number" }
    }}

真正总结了您的收藏中的“奇数”或“偶数”数字。