JSON array count in JavaScript - javascript

I have the array below:
[{"__metadata":{"id":"bba6f593-167d-4f14-85cf-3b69288f5434","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"},{"__metadata":{"id":"925dceaf-250f-43c1-be73-9972b0a34750","etag":"\"2\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option4"},{"__metadata":{"id":"85c73abb-a565-4e2c-b74c-1883c15e2eb6","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"}]
How can I get a count by group as below:
[
{"Answer":"Option3","Count":2},
{"Answer":"Option4","Count":1}
]
I my previous array there is no Count key. I tried below but it is giving an error. Can somebody help me?
const data = [{"__metadata":{"id":"bba6f593-167d-4f14-85cf-3b69288f5434","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"},{"__metadata":{"id":"925dceaf-250f-43c1-be73-9972b0a34750","etag":"\"2\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option4"},{"__metadata":{"id":"85c73abb-a565-4e2c-b74c-1883c15e2eb6","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"}];
function getCount(pollLog) {
counts = pollLog.reduce(function(r, o) {
if (r[o.Answer]) { //!(o.Answer in r)
r.push(r[o.Answer] = o);
r[o.Answer].Count = 1;
} else {
r[o.Answer].Count += 1;
}
}, {})
return counts;
}
getCount(data)

I'd use a Map for this (or an object created with Object.create(null) if you have to support obsolete environments without a polyfill for Map):
const answers = new Map();
for (const {Answer: answer} of data) {
if (answers.has(answer)) {
answers.set(answer, answers.get(answer) + 1);
} else {
answers.set(answer, 1);
}
}
Then if you really want that array of objects instead of the Map:
const array = [...answers.entries()].map(([Answer, Count]) => ({Answer, Count}));
Live Example:
const data = [{"__metadata":{"id":"bba6f593-167d-4f14-85cf-3b69288f5434","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"},{"__metadata":{"id":"925dceaf-250f-43c1-be73-9972b0a34750","etag":"\"2\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option4"},{"__metadata":{"id":"85c73abb-a565-4e2c-b74c-1883c15e2eb6","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"}];
const answers = new Map();
for (const {Answer: answer} of data) {
if (answers.has(answer)) {
answers.set(answer, answers.get(answer) + 1);
} else {
answers.set(answer, 1);
}
}
console.log(...answers);
// If you want the array of objects:
const array = [...answers.entries()].map(([Answer, Count]) => ({Answer, Count}));
console.log(array);
.as-console-wrapper {
max-height: 100% !important;
}

Your reduce() logic isn't right and you aren't returning the accumulator.
The accumulator is an object so you can't push() to it. When r[o.Answer] doesn't exist you want to create a new object for that property value.
Finally instead of returning the reduce object , return it's values as array
function getCount(pollLog) {
var counts = pollLog.reduce(function(r, o) {
if (!r[o.Answer]) {
r[o.Answer] = {Answer: o.Answer, Count:0}
}
r[o.Answer].Count ++;
// must return the accumulator
return r;
}, {})
return Object.values(counts);
}
console.log(getCount(data))
<script>
var data = [{"__metadata":{"id":"bba6f593-167d-4f14-85cf-3b69288f5434","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"},{"__metadata":{"id":"925dceaf-250f-43c1-be73-9972b0a34750","etag":"\"2\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option4"},{"__metadata":{"id":"85c73abb-a565-4e2c-b74c-1883c15e2eb6","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"}]
</script>

Here is an ES2017+ way to get the counts for all your array items in O(N):
const arr = [...yourArray];
const counts = {};
arr.forEach((el) => {
const answer = el['Answer']
counts[answer] = counts[answer] ? (counts[answer] += 1) : 1;
});
const countsSorted = Object.entries(counts).sort(([_, a], [__, b]) => a - b);
const result = countsSorted.map([a, c] => ({Answer: a, Count: c}))
console.log(result) for your example array:
[ { Answer: 'Option3', Count: 2 }, { Answer: 'Option4', Count: 1 } ]

Your reduce callback function is incorrect, if you need to use this code in browser or in some es5 environment, then here is one of solutions.
var data = [{"__metadata":{"id":"bba6f593-167d-4f14-85cf-3b69288f5434","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"},{"__metadata":{"id":"925dceaf-250f-43c1-be73-9972b0a34750","etag":"\"2\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option4"},{"__metadata":{"id":"85c73abb-a565-4e2c-b74c-1883c15e2eb6","etag":"\"1\"","type":"SP.Data.PollLogListItem"},"PollId":1,"Answer":"Option3"}];
function getCount(pollLog){
const answersCount = pollLog.reduce(function (r, o) {
var answer = o["Answer"];
if (!r[answer]) {
r[answer] = 1
} else {
r[answer] += 1;
}
return r;
}, {});
return Object.keys(answersCount).map(function(key) {
return {"Answer":key, "Count": answersCount[key]+""}
});
}
alert(JSON.stringify(getCount(data)));
Or here is a jsfiddle https://jsfiddle.net/hgen4Lzp/
If you can use ecma2017 (no need for IE support), go with the solution provided by #charlietfl

Related

Return all possible combinations of array with optional strings

Lets say I have an array keys = ["the?", "orange", "van", "s?"], with '?' at the end of strings to represent that it is optional.
I want a function in javascript generateCombinations(keys) that returns the possible combinations such as :
[["orange","van"],["the","orange","van"],["orange","van","s"],["the","orange","van","s"]]
One possible way of removing '?' is to simply do a replace("?',"").
I have a feeling it might require a recursive function, which I am not yet quite strong in. Help is appreciated!
So far I've tried this:
function isOptionalKey(key) {
return key.endsWith('?');
}
function hasOptionalKey(keys) {
return keys.some(isOptionalKey);
}
function stripOptionalSyntax(key) {
return key.endsWith('?') ? key.slice(0, -1) : key;
}
function generateCombinations(keys) {
if (keys.length === 1) {
return keys;
}
const combinations = [];
const startKey = keys[0];
const restKeys = keys.slice(1);
if (hasOptionalKey(restKeys)) {
const restCombinations = isOptionalKey(startKey)
? generateCombinations(restKeys)
: restKeys;
if (isOptionalKey(startKey)) {
combinations.push(restCombinations);
}
combinations.push(
restCombinations.map((c) => [stripOptionalSyntax(startKey), ...c])
);
} else {
if (isOptionalKey(startKey)) {
combinations.push(restKeys);
}
combinations.push([stripOptionalSyntax(startKey), ...restKeys]);
}
return combinations;
}
You could take a recursive approach by using only the first item of the array and stop if the array is empty.
const
getCombinations = array => {
if (!array.length) return [[]];
const
sub = getCombinations(array.slice(1)),
optional = array[0].endsWith('?'),
raw = optional ? array[0].slice(0, -1) : array[0],
temp = sub.map(a => [raw, ...a]);
return optional
? [...temp, ...sub]
: temp;
};
keys = ["the?", "orange", "van", "s?"],
result = getCombinations(keys);
console.log(result.map(a => a.join(' ')));

Get access to the output array of map inside of map's callback - JS

I have a following function that iterates over array and dose some work on the item related to the array that I want to fill like this:
function fileNaming(names) {
const result = [];
names.forEach(name => {
let count = 0;
const curr = name;
if (result.includes(name)) {
while (result.includes(name)) {
count++;
name = `${curr}(${count})`;
}
}
result.push(name);
})
return result;
}
I want to get rid of the result array and use only map for it like this:
function fileNaming(names) {
return names.map(name => {
let count = 0;
const curr = name;
// In the following 2 lines we will get error because we don't have result array anymore
if (result.includes(name)) {
while (result.includes(name)) {
count++;
name = `${curr}(${count})`;
}
}
return name;
})
}
But the problem is that I need to check something inside the outputed array but I don't know how to do it. I tried to find similar problems like this but didn't found anything, also I tried to dig up in the pollifils of map method, but everywhere was just a loop with some result array that I could get access.
You can use the accumulator in Array#reduce instead.
function fileNaming(names){
return names.reduce((result, name) => {
let count = 0;
const curr = name;
while(result.includes(name)){
++count;
name = `${curr}${count}`;
}
result.push(name);
return result;
}, []);
}
Alternatively you can use reduce & filter
function fileNamingDup(names) {
return names.reduce((acc, curr) => {
if (acc.includes(curr)) {
acc.push(`${curr}(${acc.filter(elem=>elem===curr).length+1})`)
} else {
acc.push(curr);
}
return acc;
}, []);
}
console.log(fileNamingDup(['a', 'b', 'a', 'a']));

How to make the grouping fastest, provided that the data in the array also show the object nodeJs

Hello this is my array;
arr =[{cNo:1,buyOrSel:'A',ip:192.168.1.1},{cNo:1,buyOrSel:'S',ip:192.168.1.1},{cNo:2,buyOrSel:'S',ip:192.168.1.2},{cNo:3,buyOrSel:'A',ip:192.168.1.1},{cNo:4,buyOrSel:'S',ip:192.168.1.3},{cNo:5,buyOrSel:'S',ip:192.168.1.2}]
I want to group in Object like this;
[{cNo:'1,3',ip:192.168.1.1},{cNo:'2,5',ip:192.168.1.2}]
I don't want to use nested For loop.What is the best way for this ?
let arr = [{cNo:1,buyOrSel:'A',ip:"192.168.1.1"},{cNo:1,buyOrSel:'S',ip:"192.168.1.1"},{cNo:2,buyOrSel:'S',ip:"192.168.1.2"},{cNo:3,buyOrSel:'A',ip:"192.168.1.1"},{cNo:4,buyOrSel:'S',ip:"192.168.1.3"},{cNo:5,buyOrSel:'S',ip:"192.168.1.2"}];
arr = arr.reduce((prev, a) => {
let cNo = prev[a.ip] = prev[a.ip] || [];
if (cNo.indexOf(a.cNo) < 0) {
prev[a.ip].push(a.cNo);
};
return prev
}, {});
arr = Object.keys(arr).map(key => {
return {
cNo: arr[key].join(),
ip: key
}
});
console.log(arr);
you can use object for better grouping.
and instead of pushing only cNo, you can push whole object too.
let arr = [{cNo:1,buyOrSel:'A',ip:"192.168.1.1"},{cNo:1,buyOrSel:'S',ip:"192.168.1.1"},{cNo:2,buyOrSel:'S',ip:"192.168.1.2"},{cNo:3,buyOrSel:'A',ip:"192.168.1.1"},{cNo:4,buyOrSel:'S',ip:"192.168.1.3"},{cNo:5,buyOrSel:'S',ip:"192.168.1.2"}];
let objectByIP = {};
arr.forEach(element => {
if (!objectByIP[element.ip]) {
objectByIP[element.ip] = [element.cNo];
} else {
objectByIP[element.ip].push(element.cNo);
}
});
console.log(objectByIP);
console.log(Object.keys(objectByIP));
you can use map():
let a = arr.map(a=>{ return {cNo: a.cNo, ip: a.ip}})

JavaScript ES6 - count duplicates to an Array of objects

I'm creating for my list of products a filter to count all producers and display like this:
Apple (3)
I eliminated the duplicates from array: ["Apple","Apple","Apple"] I used this link:
Get all non-unique values (i.e.: duplicate/more than one occurrence) in an array
But my problem is that I want to count these elements from array and display them in an Array of Objects cause i need to iterate it later.
From this Array of Apples above i need result: [{"Apple": 3},{...},{...}]
I was trying to do this but it returns me object and I can't iterate after it:
How to count duplicate value in an array in javascript
I need an Array of Objects it's not duplicated
I'm using Angular 4.
My code:
component.ts
async ngOnInit() {
this.cart$ = await this.cartService.getCart();
this.subscription = this.productService.getAll().subscribe(products => {
this.category = products.filter(
products => products.category == this.name
);
this.filters();
});
}
filters() {
this.category2 = this.category.map(value => value.producer);
this.filteredArray = this.eliminateDuplicates(this.category2);
console.log(this.filteredArray);
}
eliminateDuplicates(arr) {
let i,
len = arr.length,
out = [],
obj = {};
for (i = 0; i < len; i++) {
obj[arr[i]] = 0;
}
for (i in obj) {
out.push(i);
}
return out;
}
component.html
<div *ngFor="let f of filteredArray">
{{f}}
</div>
You can use reduce to summarize the array and map for form the desired output
let obj = ["Apple", "Apple", "Apple", "Orange"];
let result = Object.values(obj.reduce((c, v) => {
c[v] = c[v] || [v, 0];
c[v][1]++;
return c;
},{})).map(o=>({[o[0]] : o[1]}));
console.log(result);
Here:
const array = ["a", "a", "b"]
const result = { }
for (let i = 0; i < array.length; i++) {
result[array[i]] = (result[array[i]] || 0) + 1
}
Object.keys(result).map(key => ({ [key]: result[key] }))
That last line is the key for
I was trying to do this but it returns me object
you can simply do it by using Lodash countBy function
filters() {
this.category2 = this.category.map(value => value.producer);
this.filteredArray = _.countBy(this.category2);
console.log(this.filteredArray);
// Object {Apple: 3, Orange: 1}
}
You can simply do it by using array.reduce() method
const votes = ['Yes', 'Yes', 'Yes', 'No', 'No', 'Absent'];
const result = votes.reduce((prevValue, vote) => {
if (prevValue[vote]) {
prevValue[vote]++;
} else {
prevValue[vote] = 1;
}
return prevValue;
}, {});
console.log(result);
Output : { Yes: 3, No: 2, Absent: 1 }

Group and aggregate an array of javascript objects

I am having some trouble rationalising and aggregating an array of objects in javascript.
Given the array
[{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}]
I am trying to convert it to an array in the format (actual values may be wrong here).
[{"descriptions":"Shiny, Bright, Dull","sizeRange":"S - L","price":8.99},{"descriptions":"Shiny, Bright, Dull","sizes":"XL - 2XL","price":9.99},{"descriptions":"Dark","sizes":"S - 2XL","price":10.99}]
That is - I wish to group each set of items by price, showing the descriptions and size ranges for them.
So far this is what I have, and it seems to be working, but it just seems very cumbersome. Really I'd be more than happy to use something like lodash or underscore if it would help to rationalise the code a bit rather than using native JS.
function groupBy (array, key) {
return array.reduce(function(value, property) {
(value[property[key]] = value[property[key]] || []).push(property);
return value;
}, {});
};
function unique(array) {
return Array.from(new Set(array));
};
function getRanges(data)
{
var result = [];
// simple map of sizes from smallest to largest to use for sorting
var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};
// group the remaining variants by price
var group = groupBy(data, 'price');
// for each variant price group
for(var price in group) {
var item = {};
item.price = price;
// get the range of sizes sorted smallest to largest
var sizes = unique(group[price].map(function(i) {
return i.size;
})).sort(function(a, b) {
return sizeSort[a] - sizeSort[b];
});
// Add single size, or first and last size.
item.sizes = (sizes.length === 1) ?
sizes.shift() :
sizes.shift() + ' - ' + sizes.pop();
// Add the descriptions as alphabetically sorted CSV
item.description = unique(group[price].map(function(i) {
return i.description;
})).sort().join(", ");
result.push(item);
}
return result;
}
Here is a version using lodash..
I think it looks more rational..
function calc(data) {
var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5,
'3XL':6, '4XL':7, '5XL':8};
return _.chain(data).
groupBy('price').
map(function(f){
var sizes = _.chain(f).map('size').uniq().
sortBy(function (a) { return sizeSort[a] }).value();
return {
price: _.head(f).price,
description: _.chain(f).map('description').uniq().join(',').value(),
size: sizes.length === 1 ? _.first(sizes) : _.join([_.first(sizes),_.last(sizes)], ' - ')
}
}).
sortBy(['price']).
value();
}
//put data at end, so not having to scroll down to see code
var data = [{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}];
console.log(calc(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.3/lodash.js"></script>
A vanilla JavaScript solution (with ES6 template strings)
/*
Some boilerplate functions. Listed underscore/lodash functions that
could replace them above
*/
// _.mapObject(object, reducer)
function reduceValues(object, reducer) {
let newObject = {}
for (var property in object) {
if (object.hasOwnProperty(property)) {
newObject[property] = reducer(object[property])
}
}
return newObject
}
// _.groupBy
function groupBy(arr, key) {
let reducer = (grouped, item) => {
let group_value = item[key]
if (!grouped[group_value]) {
grouped[group_value] = []
}
grouped[group_value].push(item)
return grouped
}
return arr.reduce(reducer, {})
}
// _.values
function objectValues(object) {
let values = []
for (var property in object) {
if (object.hasOwnProperty(property)) {
values.push(object[property])
}
}
return values
}
/*
Shirt specific functions and data
*/
// Mapping of shirts to their order.
let sizesToNumbers = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};
// Create an intermediate summary with data instead of strings.
// This makes processing easier to write and reason about
function reduceShirtsToSummary(shirts) {
let reducer = (summary, shirt) => {
summary['descriptions'].add(shirt['description'])
let shirtSize = shirt['size']
if (!summary['smallestSize'] || sizesToNumbers[shirtSize] < sizesToNumbers[summary['smallestSize']]) {
summary['smallestSize'] = shirtSize
}
if (!summary['largestSize'] || sizesToNumbers[shirtSize] > sizesToNumbers[summary['largestSize']]) {
summary['largestSize'] = shirtSize
}
summary['prices'].push(shirt['price'])
return summary
}
return shirts.reduce(reducer, {'descriptions': new Set(), 'prices': []})
}
// Convert the shirt summary data into the "labelized" version with strings in the example
function labelizeShirtSummary(shirtSummary) {
let labelizedShirtSummary = {}
labelizedShirtSummary['descriptions'] = Array.from(shirtSummary['descriptions']).join(', ')
labelizedShirtSummary['price'] = shirtSummary['prices'][0]
labelizedShirtSummary['sizes'] = `${shirtSummary['smallestSize']} - ${shirtSummary['largestSize']}`
return labelizedShirtSummary
}
let grouped = groupBy(shirts, 'price')
let groupedAndSummarized = reduceValues(grouped, reduceShirtsToSummary)
let labelizedSummaries = objectValues(groupedAndSummarized).map(labelizeShirtSummary)
// Gives desired output
console.log(labelizedSummaries)

Categories