Ruby on rails - Mongoid MapReduce为递归归约函数提供不规则的结果



我有一个Item模型,它有一个属性category。我希望按类别分组的项目计数。我为此功能编写了一个地图缩减。它工作正常。我最近编写了一个脚本来创建 5000 个项目。现在我意识到我的地图归约只给出了最后 80 条记录的结果。下面是 mapreduce 函数的代码。

map = %Q{
  function(){
    emit({},{category: this.category});
  }
}
reduce = %Q{
  function(key, values){
    var category_count = {};
    values.forEach(function(value){
      if(category_count.hasOwnProperty(value.category))
        category_count[value.category]++;  
      else
        category_count[value.category] = 1 
    })
    return category_count;
  }
}
Item.map_reduce(map,reduce).out(inline: true).first.try(:[],"value")

经过一番研究,我发现mongodb调用多次归约函数。如何实现我想要的功能?

在MongoDB中编写map-reduce代码时,必须遵循一条规则(实际上是一些规则)。 一个是 emit(发出键/值对)必须具有与 reduce 函数将返回的值相同的格式。

如果emit(this.key, this.value)则 reduce 必须返回与this.value完全相同的类型。 如果emit({},1)则reduce必须返回一个数字。 如果emit({},{category: this.category})则reduce必须返回格式{category:"string"}的文档(假设类别是一个字符串)。

所以

这显然不可能是你想要的,因为你想要总数,所以让我们看看减少是什么返回,并从中计算出你应该发出什么。

看起来最后您想

累积一个文档,其中每个类别都有一个键名,其值是一个表示其出现次数的数字。 像这样:

{category_name1:total, category_name2:total}

如果是这种情况,那么正确的映射函数将emit({},{"this.category":1})在这种情况下,您的reduce将需要将对应于类别的每个键的数字相加。

以下是地图的外观:

map=function (){
     category = { };
     category[this.category]=1;
     emit({},category);
}

这是正确的相应还原:

reduce=function (key,values) {
     var category_count = {};
     values.forEach(function(value){
        for (cat in value) {
           if( !category_count.hasOwnProperty(cat) ) category_count[cat]=0;
           category_count[cat] += value[cat];
        }
     });
     return category_count;
}

请注意,它满足MapReduce的另外两个要求 - 如果从不调用reduce函数,它将正常工作(如果您的集合中只有一个文档,则会出现这种情况),并且如果reduce函数被多次调用,它将正常工作(当你有超过100个文档时会发生什么)。

一种更传统的方法是将类别名称作为键,将数字作为值发出。 这简化了映射并减少了:

map=function() { 
   emit(this.category, 1);
}
reduce=function(key,values) {
    var count=0;
    values.forEach(function(val) {
        count+=val;
    }
    return count;
}

这将对每个类别出现的次数求和。 这也满足了 MapReduce 的要求 - 如果从不调用reduce函数,它就可以正常工作(对于任何只出现一次的类别都是这种情况),如果reduce函数被多次调用,它将正常工作(如果任何类别出现超过100次,就会发生这种情况)。

正如其他人所指出的,聚合框架使相同的练习变得更加简单:

db.collection.aggregate({$group:{_id:"$category",count:{$sum:1}}})

尽管这与我显示的第二个mapReduce的格式相匹配,而不是您拥有的原始格式,该格式将类别名称输出为键。 然而,聚合框架总是比MapReduce快得多。

我同意Neil Lunn的评论。

我从提供的信息中可以看到,如果您使用的是大于或等于 2.2 的 MongoDB 版本,您可以使用聚合框架而不是 map-reduce。

db.items.aggregate([
  { $group: { _id: '$category', category_count: { $sum: 1 } }
])

这要简单得多,性能也好得多(参见 映射/归化与聚合框架 )

相关内容

  • 没有找到相关文章

最新更新