Воспроизведение функциональности карты/выдачи MongoDB в javascript/node.js (без MongoDB)

Мне нравится функциональность, которую MongoDB предоставляет для выполнения задач map/reduce, в частности, emit() в функции mapper. Как я могу воспроизвести поведение карты, показанное ниже, в javascript/node.js без MongoDB?

Пример (из документации MongoDB Map-Reduce):

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

Сопоставлено с -

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

Библиотека, которая делает ее такой же простой, как однострочная функция emit() в Mongo, была бы хороша, но собственные функции также справились бы с этой задачей.


person Fuzzifized    schedule 17.12.2014    source источник
comment
Вы проверили Underscore.JS?   -  person Chris Franklin    schedule 18.12.2014
comment
У меня есть, но я не смог понять, как использовать _.map для получения тех же результатов.   -  person Fuzzifized    schedule 18.12.2014
comment
С [].map, [].reduce и [].forEach в качестве встроенных методов в наши дни (и еще целая куча других, на которые ссылается первый ответ), вам действительно не нужны библиотеки. Для совместимости удобно использовать Underscore, потому что он предоставляет их, когда они отсутствуют, но дополнительные зависимости — это хорошо только в случае необходимости.   -  person SamMorrowDrums    schedule 18.12.2014


Ответы (3)


Если вам просто нужен синтаксис emit, это возможно. Отсканируйте тело функции и передайте новую функцию emit.

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
}

Изменить, забыл пример:

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 } ]
person AJcodez    schedule 17.12.2014
comment
Для этого я добавлю JSFiddle. Спасибо. Это была та функциональность, которую я искал. - person Fuzzifized; 18.12.2014
comment
Хороший пример, хотя и содержит некоторые странности: зачем преобразовывать функцию map в строку и возиться с телом функции только для того, чтобы внедрить emit? Почему Array.sum? Если вы хотите добавить новый метод в каждый массив, вы должны использовать Array.prototype.sum. - person fbuchinger; 24.01.2017

Array.reduce делает то, что вам нужно. вот документация: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

Я также предлагаю вам использовать undescore.js (как в первом комментарии), который имеет право на уменьшение и уменьшение. http://underscorejs.org/#reduce

person risyasin    schedule 17.12.2014

Я согласен, что есть много отличных способов сделать это, и это просто сделать с помощью методов Array. Вот пример с моим предложением. Это довольно просто и использует метод forEach Array. Я сделал это в одном цикле, но есть много других способов.

Я не сделал сокращение в конце, так как вы не просили об этом, но я надеюсь, что это поможет.

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"]}]

Я использовал объект для хранения индекса массива для каждой уникальной записи. Версий на этот счет множество. Вероятно, намного лучше, чем у меня, но я подумал, что дам вам ванильную версию JS.

person SamMorrowDrums    schedule 17.12.2014