Reproducing MongoDB's map/emit functionality in javascript/node.js (without MongoDB)

那年仲夏 提交于 2019-12-11 23:14:45

问题


I like the functionality that MongoDB provides for doing map/reduce tasks, specifically the emit() in the mapper function. How can I reproduce the map behavior shown below in javascript/node.js without MongoDB?

Example (from MongoDB Map-Reduce Docs):

[{ cust_id: "A123", amount: 500 }, { cust_id: "A123", amount: 250 }, { cust_id: "B212", amount: 200 }] 

Mapped to -

[{ "A123": [500, 200] }, { "B212": 200 }]

A library that makes it as simple as Mongo's one line emit() would be nice but native functions would do the job as well.


回答1:


If you just neeed to have the emit syntax, it's possible. Scan out the function body and pass in a new emit function.

function mapReduce(docs, m, r) {
  var groups = {}
  function emit(key, value) {
    if (!groups[key]) { groups[key] = [] }
    groups[key].push(value)
  }
  var fn = m.toString()
  var body = fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'))
  var map = new Function('emit', body)
  docs.forEach(function (doc) {
    map.call(doc, emit)
  })
  var outs = []
  Object.keys(groups).forEach(function (key) {
    outs.push({ _id: key, value: r(key, groups[key]) })
  })
  return outs
}

Edit, forgot example:

var docs = // from above

Array.sum = function (values) {
  return values.reduce(function (a, b) { return a + b })
}

mapReduce(docs, 
  function () {
    emit(this.cust_id, this.amount)
  },
  function (k, values) {
    return Array.sum(values)
  }
)

// [ { _id: 'A123', value: 750 }, { _id: 'B212', value: 200 } ]



回答2:


Array.reduce does what you need. here is documentation: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

I also suggest you to use undescore.js (as in first comment) which has reduce & reduceRight. http://underscorejs.org/#reduce




回答3:


I agree that there are lots of great lib ways to do this, and it's simple to do with Array methods. Here is a fiddle with my suggestion. It's pretty simple, and just uses the forEach Array method. I've done it in a single loop, but there are many other ways.

I haven't done the reduce at the end, as you didn't ask for that, but I hope this helps.

function emit (key, value, data) {
    var res = {}; out = [];
    data.forEach(function (item) {
        var k = item[key];
        var v = item[value];
        if (k !== undefined && v !== undefined) {
            if (res[k] !== undefined) {
                out[res[k]][k].push(v);
            } else {
                var obj = {};
                res[k] = out.length;
                obj[k] = [v];
                out.push(obj);
            } 
        }
    });
    return out;
}

var data = [{name: 'Steve', amount: 50},{name: 'Steve', amount: 400}, {name: 'Jim', amount: 400}];

emit('name', 'amount', data)) // returns [{"Steve":[50,400]},{"Jim":[400]}]

emit('amount', 'name', data)) // returns [{"50":["Steve"]},{"400":["Steve","Jim"]}]

I've used an object to store the array index for each unique entry. There are lots of versions of this. Probably many better than mine, but thought I'd give you a vanilla JS version.



来源:https://stackoverflow.com/questions/27535027/reproducing-mongodbs-map-emit-functionality-in-javascript-node-js-without-mong

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!