我知道我在MongoDB中遗漏了一些MapReduce。我正在尝试构建一个标记频率集合,即使看起来map
和reduce
函数“相同”,我也会得到不同的结果。
示例文档(忘记值100,45 ......我没有使用它们):
{
...
tags: [['Rock', 100], ['Indie Pop', 45], ...]
}
发出标量值1
:
var map = function () {
if (this.tags) {
this.tags.forEach(function (tag) {
emit(tag[0], 1); // Emit just 1
});
}
};
var reduce = function (key, vals) { // Vals should be [1, ...]
return vals.length; // Count the length of the array
};
db.tracks.mapReduce(map, reduce, { out: 'mapreduce_out' });
db.mapreduce_out.find().sort({ value: -1 }).limit(3);
输出是:
{ "_id" : "rubyrigby1", "value" : 9 }
{ "_id" : "Dom", "value" : 7 }
{ "_id" : "Feel Better", "value" : 7 }
发出一个对象{ count: 1 }
:
var map = function () {
if (this.tags) {
this.tags.forEach(function (tag) {
emit(tag[0], { count: 1 }); // Emit an object
});
}
};
var reduce = function (key, vals) { // vals should be [{ count: 1 }, ...]
var count = 0;
vals.forEach(function (val) {
count += val.count; // Accumul
});
return { count: count };
};
db.tracks.mapReduce(map, reduce, { out: 'mapreduce_out' });
db.mapreduce_out.find().sort({ 'value.count': -1 }).limit(3);
结果不同,似乎是“正确的”:
{ "_id" : "rock", "value" : { "count" : 9472 } }
{ "_id" : "pop", "value" : { "count" : 7103 } }
{ "_id" : "electronic", "value" : { "count" : 5727 } }
第一种方法出了什么问题?
答案 0 :(得分:4)
考虑一千个文件的集合,所有文件都带有'tagname'标签:
for (var i = 0; i < 1000; i++) {
db.collection.insert({tags: [['tagname']]});
}
如果我写了一个合适的mapReduce,我应该得到输出{"_id": "tagname", "count": 1000}
。但是如果我使用你的地图并减少功能,我将得到101而不是1000。
原因是,MongoDB使用中间结果重复调用reduce函数,以避免在内存中保留过多的结果。您可以通过在reduce:
中放置一个print语句来实际看到这一点var reduce = function (key, vals) {
print(vals);
return vals.length; // Count the length of the array
};
打印输出显示在服务器日志中。使用前100个1调用reduce函数,它返回100.到目前为止一直很好。然后MongoDB再次使用第一个reduce的输出加上下一个100的1来调用它:
reduce([100, 1, 1, ..., 1]) // 100 plus 100 more 1's
所以现在它返回101,因为这是数组的长度。但显然它应该返回200,数组的总和。因此,要获得正确的结果,请更改reduce函数:
reduce = function (key, vals) {
var sum = 0;
vals.forEach(function(val) { sum += val; });
return sum;
}