问题
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