【问题标题】:Map/Reduce gives me NaN or wrong result (due to null)Map/Reduce 给我 NaN 或错误的结果(由于 null)
【发布时间】:2012-08-02 13:30:53
【问题描述】:

我还没有提出另一个 Map/Reduce 问题。

我有一个集合“example”,如下所示:

{
"userid" : "somehash",
"channel" : "Channel 1"
}

我的 Map/Reduce 函数如下所示:

var map = function () {
    emit(this.channel, {user:this.userid, count: 1});
}

var reduce = function (key, values) {
    var result = {total:0, unique:0};
    var temp = [];
    values.forEach(function (value) {
        result.total += value.count;

        if (temp.indexOf(value.user) == -1) {
            temp.push(value.user);
        }
    });

    result.unique += temp.length;

    return result;
}

不幸的是,它给了我一些非常奇怪的结果:

{ "_id" : "Channel 1", "value" : { "total" : NaN, "unique" : 47 } }
{ "_id" : "Channel 2", "value" : { "total" : NaN, "unique" : 12 } }
{ "_id" : "Channel 3", "value" : { "total" : 6, "unique" : 6 } }

似乎value.count 解析为null,似乎“唯一”也不是正确的值。我想要做的是计算每个通道的所有值,并以我可以看到每个用户的唯一值的方式计算它。这意味着,此集合中的文档example 可能会出现多次。我想知道所有时间和独特的时间。

我遵循了本指南:http://www.mongodb.org/display/DOCS/MapReduce#MapReduce-ReduceFunction,但我不知道为什么我的脸上会出现null?很奇怪,关于这个主题有什么好的想法吗?

感谢您的建议和更好的智慧。

【问题讨论】:

  • 什么values 传递给reduce?它们是字符串吗?对象? value.count 是什么?
  • 我通过了这个:{ "userid" : "somehash", "channel" : "Channel 1" },如顶部所述。从代码中可以看出,Userid 是一个字符串,value.count 是 1(整数)。
  • 其实@jbabey询问的values{'user' : 'userid', 'count' : 1},渠道是关键。您可以将您的输入发布到地图中,或者至少是其中的一个示例吗?
  • 你能在对象上使用.forEach吗?
  • reduce 阶段从具有相同键的映射中收集所有结果,所以我认为值应该是上面发布形式的对象数组

标签: javascript mongodb mapreduce


【解决方案1】:

发生这种情况的原因是因为 map/reduce 有时会触发自身,即 reduce 会触发 reduce 的结果。但是reduce的结果没有count字段。您必须始终确保 map 发出和 reduce 结果具有相同格式。在文档中阅读有关此内容的更多信息。

编辑这是一个简单的演示如何解决这个问题:

var map = function () {
    emit(this.channel, { user: [this.userid], count: 1 });
}

var reduce = function (key, values) {
    var result = { user: [], count: 0 };
    values.forEach(function (value) {
        result.count += value.count;

        value.user.forEach(function(usr) {
            if (result.user.indexOf( usr ) == -1) {
                result.user.push( usr );
            }
        });
    });

    return result;
}

现在result.user.length 应该为您提供唯一用户。没有测试,但应该可以。

EDIT 2 不过应该很慢,.indexOf 是一个相当昂贵的函数。您可以通过制作两个 map/reduce 作业来加快速度。首先,您像这样映射/减少集合:

var map = function() {
    // make a key unique per channel and userid
    emit( this.channel + '_' + this.userid,
        { count: 1, channel: this.channel }
    );
}

var reduce = function(key, values) {
    var result = { count: 0, channel: null };
    values.forEach(function( value ) {
        result.count += value.count;
        // Don't worry about these substitutions,
        // these values can't change anyway per key.
        result.channel = value.channel;
    });
    return result;
}

现在count 在这个集合上会给你一些独特的条目。要获得总数,您可以对这样的结果进行第二次 map/reduce:

var map = function() {
    // Note the key!!!
    emit( this.value.channel, { count: this.value.count } );
}

var reduce = function(key, values) {
    var result = { count: 0 };
    values.forEach(function( value ) {
        result.count += value.count;
    });
    return result;
}

这应该会快很多。

【讨论】:

  • 这是否意味着命名必须相同或只是类型或两者兼而有之? :)
  • @cubsink 我已经更新了我的答案。 map中的emit和reduce的整个结构必须相同。
  • 它运作良好,你的回答,但它非常慢。我使用 cron 作业来聚合 2 个这样的集合,即使我在 50 分钟前开始它,它也没有完成。是否有可能以任何方式使其更快,并且仍然具有独特的价值?
  • @cubsink 我已经更新了答案。如果没有双重 map/reduce,这将无法有效完成。
  • 非常感谢,我明天第一时间试试。我希望我能十次投票赞成你的答案。 :)
【解决方案2】:

来自the docs

因为reduce函数可能对同一个key被多次调用,reduce函数返回的对象的结构必须和map函数发出的值的结构相同

你不这样做,你的reduce返回一个带有totalunique的对象,而不是countuser。要么将格式更改为代表按用户分组的格式,要么使用finalize function

【讨论】:

    猜你喜欢
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-09
    • 2011-09-22
    • 1970-01-01
    相关资源
    最近更新 更多