Карта JavaScript / Уменьшить, чтобы вернуть сгруппированные по количеству

У меня есть коллекция JSON в виде массива. Я хотел бы сгруппировать по трем полям в коллекции, а затем вернуть результат вместе с количеством соответствующих документов. Пример ниже, надеюсь, прояснит ситуацию.

Коллекция документов JSON возвратила:

[
    {
        _id: 1,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 2,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 3,
       browser: 'opera',
       ipAddress: '222.0.888.0',
       uri: 'example1.com'
    },
    {
       _id: 4,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 5,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 6,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 7,
       browser: 'opera',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    }
]

Следует выполнить группировку в браузере, ipAddress и uri, а затем вернуть сгруппированный результат вместе со счетчиком, как показано ниже (я проверял несколько раз, поэтому надеюсь, что мои числа ниже суммируются с экземплярами каждой комбинации выше!).

[
    {
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com',
       count: 2
    },
    {
       browser: 'opera',
       ipAddress: '222.0.888.0',
       uri: 'example1.com',
       count: 1
    },
    {
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com',
       count: 3
    },
       browser: 'opera',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com',
       count: 1
]

Я понимаю, что это должно быть легко выполнимо с помощью map/reduce, но я не могу понять, как это сделать!

Заранее спасибо.


person Dave    schedule 16.03.2017    source источник


Ответы (5)


Другой (более функциональный) подход с использованием lodash:

_(array).groupBy(v => ([v.browser, v.ipAddress, v.uri]))
        .map(v => _.merge(_.omit(v[0], '_id'), {count: v.length}))
        .value();

Краткое пояснение: groupBy использует браузер, ipAddress и uri для создания группы. В операторе map мы удаляем поле _id и добавляем счетчик на основе количества объектов в группе.

person Maurits Rijk    schedule 16.03.2017
comment
Спасибо Мориц. В соответствии с RaR, не против библиотек, но в этом случае я хотел бы выяснить, как запустить это, используя только JS. - person Dave; 19.03.2017
comment
Как и в случае с ответом от Раджеша, он дает мне правильный результат с точки зрения объединения одинаковых записей, но включает в себя количество для каждой. - person Dave; 29.03.2017

Если вы открыты для использования lodash (лучше использовать, если нет), вы можете сделать следующее:

var array = [
    {
        _id: 1,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 2,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 3,
       browser: 'opera',
       ipAddress: '222.0.888.0',
       uri: 'example1.com'
    },
    {
       _id: 4,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 5,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 6,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 7,
       browser: 'opera',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    }
]

var res = _.reduce(array, function(acc, elem){
  delete elem._id;
  var obj = _.find(acc, elem)
  if(obj){
    obj.count++;
  }
  else{
    elem.count = 1;
    acc.push(elem);
  }
  return acc;
}, [])

console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

person RaR    schedule 16.03.2017
comment
Спасибо РаР. Я не против использования библиотеки, и я знаю, что lodash, подчеркивание и т. д. являются хорошими вариантами. Я просто знаю, что это то, что должно быть довольно легко выполнено, поэтому я очень хочу знать, как именно. - person Dave; 16.03.2017

Вы можете попробовать что-то вроде этого:

var data=[{_id:1,browser:"chrome",ipAddress:"222.111.111.0",uri:"example1.com"},{_id:2,browser:"chrome",ipAddress:"222.111.111.0",uri:"example1.com"},{_id:3,browser:"opera",ipAddress:"222.0.888.0",uri:"example1.com"},{_id:4,browser:"chrome",ipAddress:"222.111.222.0",uri:"sample1.com"},{_id:5,browser:"chrome",ipAddress:"222.111.222.0",uri:"sample1.com"},{_id:6,browser:"chrome",ipAddress:"222.111.222.0",uri:"sample1.com"},{_id:7,browser:"opera",ipAddress:"222.111.222.0",uri:"sample1.com"}];

function groupBy(array, keys) {
  var groups = array.reduce(function(p, c) {
    var hash = keys.map(function(k){ return c[k]; }).join("|")
    p[hash] = p[hash] || c;
    p[hash]["count"] = (p[hash]["count"] || 0) + 1
    delete p[hash]["_id"];
    return p;
  }, {});
  var result = Object.keys(groups).map(function(x){return groups[x] })
  console.log(result);
  return result
}

var keys = ["browser", "ipAddress", "uri"]
groupBy(data, keys)

person Rajesh    schedule 16.03.2017
comment
Спасибо Раджеш. Когда я запускаю это, он группируется, но я вообще не получаю счет. - person Dave; 16.03.2017
comment
@Dave Дэйв, я использовал код ES6. Это могло вызвать проблемы. Обновил мой ответ, чтобы использовать синтаксис ES5. Надеюсь, поможет - person Rajesh; 16.03.2017
comment
Дело было не столько в ES6/ES5, сколько в том, что результаты фактически не возвращают счет. Я получаю только группы, но также и поле _id. - person Dave; 19.03.2017

Вы можете сделать это с помощью ванильного JavaScript, используя один уменьшить:

let arr = [
    {
        _id: 1,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 2,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 3,
       browser: 'opera',
       ipAddress: '222.0.888.0',
       uri: 'example1.com'
    },
    {
       _id: 4,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 5,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 6,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 7,
       browser: 'opera',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    }
]

let result = arr.reduce((_, x) => {
  for(let i = 0; i < _.length; i++) {
    if(_[i].browser === x.browser && _[i].ipAddress === x.ipAddress && _[i].uri === x.uri) {
      _[i].count++
      return _
    }
  }
  let { _id, ...rest } = x
  return [ ..._, { ...rest, count: 1 } ]
}, [])

console.log(result)

person cchamberlain    schedule 16.03.2017
comment
Спасибо, cchamberlain, я предполагаю, что вы используете для этого подчеркивание? - person Dave; 19.03.2017
comment
@ Дэйв, нет, это ванильный JavaScript. Подчеркивание здесь — это просто переменная, которая довольно часто используется для редуктора-аккумулятора. Если у вас в проекте было подчеркивание или lodash, вам лучше выбрать другое имя переменной для аккумулятора. - person cchamberlain; 19.03.2017
comment
@Dave ответы были обновлены ссылкой на документацию по сокращению на MDN. - person cchamberlain; 19.03.2017
comment
К сожалению, я не смог запустить это. Мое приложение продолжает сходить с ума из-за .... Я понимаю, что это ES6, а мой NodeJS - последний, поэтому он должен запускать это, но продолжает выдавать мне ошибку об атрибуте распространения. - person Dave; 29.03.2017

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

let arr = Object.freeze([
    {
        _id: 1,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 2,
       browser: 'chrome',
       ipAddress: '222.111.111.0',
       uri: 'example1.com'
    },
    {
       _id: 3,
       browser: 'opera',
       ipAddress: '222.0.888.0',
       uri: 'example1.com'
    },
    {
       _id: 4,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 5,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 6,
       browser: 'chrome',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    },
    {
       _id: 7,
       browser: 'opera',
       ipAddress: '222.111.222.0',
       uri: 'sample1.com'
    }
]);

const groupByReducerCount = (group) =>
  (result, row) => {
    const keygroup = group.map((v) => row[v]);
    const key = keygroup.join(':');
    if (result[key])
      result[key] ++;
    else
      result[key] = 1;
    return result;
  };


const result = arr.reduce(groupByReducerCount(['uri','browser','ipAddress']),{});

console.log(result)

person David Lemon    schedule 11.04.2018