Reputation: 53
I have a JSON collection as an array. I would like to group by three fields within the collection and then return the result along with the count of the matching documents. The example below will hopefully make it clearer.
The JSON document collection returned:
[
{
_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'
}
]
Should perform a grouping on browser, ipAddress and uri and then return the grouped result along with a count as per below (I checked a few times so I hope my numbers below add up to the instances of each combination above!).
[
{
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
]
I get that this should be easily doable using map / reduce but I cannot seem to get my confused brain around how to do this!
Thanks in advance.
Upvotes: 3
Views: 2806
Reputation: 1560
You can achieve this by using a generic reducer generator. This code is based on my previous answer to another question . You can give it the fields by which you want to group and it returns function that can act as reducer giving a item count.
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)
Upvotes: 1
Reputation: 17966
You can do this with vanilla JavaScript using a single reduce:
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)
Upvotes: 1
Reputation: 24945
You can try something like this:
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)
Upvotes: 1
Reputation: 9985
Another (more functional) approach using lodash:
_(array).groupBy(v => ([v.browser, v.ipAddress, v.uri]))
.map(v => _.merge(_.omit(v[0], '_id'), {count: v.length}))
.value();
Short explanation: the groupBy
uses browser, ipAddress and uri to create a grouping. In the map
statement we remove the _id field and add the count based on the number of objects in the group.
Upvotes: 1
Reputation: 3223
If you are open to use lodash
(better to use if not), you can do like the following,
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>
Upvotes: 0