count all values on object array - javascript - javascript

I need to count each value on the object array , the desired output should be like below
[{
"question": "question1",
"USA": 2
}, {
"question": "question1",
"AUS": 1
},
{
"question": "question2",
"item1": 2
},
{
"question": "question2",
"item1,item2": 1
}, {
"question": "question4",
"3": 1
}, {
"question": "question4",
"2": 1
}
]
Below is the input I need to transform in to the above output. I have no clue how to do with n no of question and also got issue when one question has 2 answers . sample input
[{"question1":"USA","question2":["item1"],"question4":2},
{"question1":"USA","question2":["item1"],"question4":3},
{"question1":"AUS","question2":["item1","item2"]}];
let arr=[{"question1":"USA","question2":["item1"],"question4":2},{"question1":"USA","question2":["item1"],"question4":3},{"question1":"AUS","question2":["item1","item2"]}];
//console.log(arr);
function solve(list){
var map = new Map();
var entry = null;
for(var item of list){
if(!map.has(item.question1))
map.set(item.question1, {question:'question1'});
entry = map.get(item.question1);
if(entry.hasOwnProperty(item.question1))
entry[item.question1] = entry[item.question1] + 1;
else
entry[item.question1] = 1;
if(!map.has(item.question2))
map.set(item.question2, {question: 'question2'});
entry = map.get(item.question2);
if(entry.hasOwnProperty(item.question2))
entry[item.question2] = entry[item.question2] + 1;
else
entry[item.question2] = 1;
}
return Array.from(map.values());
}
console.log(solve(arr))

You could take an object or what ever data structure you like which supports a key/value structure in a nested style and collect first all items and then reder the collected tree.
This approach uses objects, because the keys are strings, this is important for an array as key. This is joint with a comma which is sufficient for this use case.
var data = [{ question1: "USA", question2: ["item1"], question4: 2 }, { question1: "USA", question2: ["item1"], question4: 3 }, { question1: "AUS", question2: ["item1", "item2"] }],
hash = data.reduce((hash, o) => {
Object.entries(o).forEach(([question, value]) => {
var sub = hash[question] = hash[question] || Object.create(null);
sub[value] = sub[value] || { question, [value]: 0 };
sub[value][value]++;
});
return hash;
}, Object.create(null)),
result = Object.values(hash).reduce((r, sub) => [...r, ...Object.values(sub)], []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

First, obtain the countries by using reduce. Then use some nested forEach loops for the rest:
const input = [{"question1":"USA","question2":["item1"],"question4":2},
{"question1":"USA","question2":["item1"],"question4":3},
{"question1":"AUS","question2":["item1","item2"]}];
const countriesOutput = input.reduce((acc, curr) => {
if (!acc.some(e => e[curr.question1])) {
acc.push({ question: "question1", [curr.question1]: 1 });
} else {
acc.find(e => e[curr.question1])[curr.question1]++;
}
return acc;
}, []);
let questionsOutput = [];
input.forEach(item => {
Object.keys(item).forEach(key => {
if (key != "question1") {
if (Array.isArray(item[key])) {
questionsOutput.push({ question: key, [item[key].join(",")]: 1 });
} else {
questionsOutput.push({ question: key, [item[key]]: 1 });
}
}
});
});
const finalOutput = [...countriesOutput, ...questionsOutput];
console.log(finalOutput);
.as-console-wrapper { max-height: 100% !important; top: auto; }

Its a matter of summarizing the input using a dictionary (like Object) and track the duplicates. The "name" of the name/value pair can be uniquely identified by combining the question and answer with some delimiter.
const input = [{
"question1": "USA",
"question2": ["item1"],
"question4": 2
},
{
"question1": "USA",
"question2": ["item1"],
"question4": 3
},
{
"question1": "AUS",
"question2": ["item1", "item2"]
}
];
//Sum the input to an array which we can easily search for duplciates
var repeatCounter = {};
input.forEach(objItem => {
Object.keys(objItem).forEach(propItem => {
//Get the counter and the string
var s = `${propItem}-${objItem[propItem]}`;
var c = repeatCounter[s] || 0;
//Modify it or introduce it if absent
repeatCounter[s] = c + 1;
})
})
var output = Object.keys(repeatCounter).map(element => {
var ret = {'question': element.split('-')[0]}
ret[element.split('-')[1]] = repeatCounter[element];
return ret;
})
console.log(output);
.as-console-wrapper {
max-height: 100% !important;
}
Subtle adjustments such as fortifying the delimiter, converting multiple strings in to array items(as shown in the question) needs to be done on practical grounds.

Related

Push items in an array in Javascript?

I'm working on school-app. person enter students marks from frontend and I've to store it in my backend. I know my data-structure is quite bad. but this is only way I can comfortly use and fit it in my front end application/website.
codeSandbox link
Full Code:
//This data is already set need to push information in this array.
let student = [{
"detail": {
"name": "Mark",
"surname":"widen"
},
}];
//formatting the query in json.
const keys = Object.keys(query)[0].split(",")
const values = Object.values(query)[0].split(",")
const newObj = {}
for (let i = 0; i < keys.length; i++) {
newObj[keys[i]] = values[i]
}
// I've to push it along with "academic-year". so,
for (let a = 0; a < newObj.length; a++) {
const year = a + "st-Year"
console.log(year) // Expected Output: 1st-Year and 2nd-Year
}
// How to run this both for-loop synchronously way ?? AND
//pushing with "ObtainedMarks" and "year" (Error over here)
student.push({
ObtainedMarks: {
year : [
{ physics: newObj }
],
year : [
{ physics: newObj }
]
}
})
console.log(student) //Here's I want expected Output
Expected Output:
let student = [{
"detail": {
"name": "Mark",
"surname":"widen"
},
ObtainedMarks: {
"1st-Year": [
{ physics: { "marks": "500" } } //Physics subject is default.
],
"2nd-Year": [
{ physics: { "mark": "200" } } //Physics subject is default.
]
}
}];
I want to push returned data in student array. with 1st-Year
and 2nd-Year's for-loop.
You can do the conversion in your for-loop
let student = [{
"detail": {
"name": "Mark",
"surname": "widen"
},
}];
let query = {
"marks,mark": "500,200"
}
const keys = Object.keys(query)[0].split(",");
const values = Object.values(query)[0].split(",");
const marks = {}
for (let i = 0; i < keys.length; i++) {
marks[i === 0 ? `${i+1}st-year` : `${i+1}nd-year`] = [{
physics: {
[keys[i]]: values[i]
}
}];
}
student.push({
obtainedMarks: marks
});
console.log(student);
Alternative way: map through the keys and create an object from entries after manipulating the data.
let student = [{
"detail": {
"name": "Mark",
"surname": "widen"
},
}];
let query = {
"marks,mark": "500,200"
}
const keys = Object.keys(query)[0].split(",");
const values = Object.values(query)[0].split(",");
const marks = Object.fromEntries(keys.map((k, i) => {
return [
i === 0 ? `${i+1}st-year`: `${i+1}nd-year`,
[{ physics: { [k]: values[i] }}]
];
}));
student.push({
obtainedMarks: marks
});
console.log(student);

Convert an array to json object by javascript

I am stuck to solve this problem.
Convert an array below
var input = [
'animal/mammal/dog',
'animal/mammal/cat/tiger',
'animal/mammal/cat/lion',
'animal/mammal/elephant',
'animal/reptile',
'plant/sunflower'
]
to json Object
var expectedResult = {
"animal": {
"mammal": {
"dog": true,
"cat": {
"tiger": true,
"lion": true
},
"elephant": true
},
"reptile": true
},
"plant": {
"sunflower": true
}
}
Which data structure and algorithm can I apply for it?
Thanks
You need to first split each element to convert to array
using reverse reduce method you can convert them to object.
And your last step is merge this objects.
Lodash.js merge method is an one way to merge them.
var input = ['animal/mammal/dog','animal/mammal/cat/tiger','animal/mammal/cat/lion', 'animal/mammal/elephant','animal/reptile', 'plant/sunflower']
var finalbyLodash={}
input.forEach(x=>{
const keys = x.split("/");
const result = keys.reverse().reduce((res, key) => ({[key]: res}), true);
finalbyLodash = _.merge({}, finalbyLodash, result);
});
console.log(finalbyLodash);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>
To make the process more understandable, break the problem down into pieces.
The first step is convert each string into something we can use, converting this:
"animal/mammal/dog"
into this:
[ "animal", "mammal", "dog" ]
That's an array of property names needed to build the final object.
Two functions will accomplish this for you, String.prototype.split() to split the string into an array, and Array.prototype.map() to transform each of the array elements:
let splitIntoNames = input.map(str => str.split('/'));
The intermediate result is this:
[
[ "animal", "mammal", "dog" ],
[ "animal", "mammal", "cat", "tiger" ],
[ "animal", "mammal", "cat", "lion" ],
[ "animal", "mammal", "elephant" ],
[ "animal", "reptile" ],
[ "plant", "sunflower" ]
]
Next step is to iterate over each array, using Array.prototype.forEach() to add properties to the object. You could add properties to the object with a for loop, but let's do that with a recursive function addName():
function addName(element, list, index) {
if (index >= list.length) {
return;
}
let name = list[index];
let isEndOfList = index === list.length - 1;
element[name] = element[name] || (isEndOfList ? true : {});
addName(element[name], list, index + 1);
}
let result = {};
splitIntoNames.forEach((list) => {
addName(result, list, 0);
});
The result:
result: {
"animal": {
"mammal": {
"dog": true,
"cat": {
"tiger": true,
"lion": true
},
"elephant": true
},
"reptile": true
},
"plant": {
"sunflower": true
}
}
const input = [
"animal/mammal/dog",
"animal/mammal/cat/tiger",
"animal/mammal/cat/lion",
"animal/mammal/elephant",
"animal/reptile",
"plant/sunflower",
];
let splitIntoNames = input.map((str) => str.split("/"));
console.log("splitIntoNames:", JSON.stringify(splitIntoNames, null, 2));
function addName(element, list, index) {
if (index >= list.length) {
return;
}
let name = list[index];
let isEndOfList = index === list.length - 1;
element[name] = element[name] || (isEndOfList ? true : {});
addName(element[name], list, index + 1);
}
let result = {};
splitIntoNames.forEach((list) => {
addName(result, list, 0);
});
console.log("result:", JSON.stringify(result, null, 2));
You can create a function that will slice every element from the array by "/" than you put the results into a variable and than just mount the Json. I mean something like that below:
window.onload = function() {
var expectedResult;
var input = [
'animal/mammal/dog',
'animal/mammal/cat/tiger',
'animal/mammal/cat/lion',
'animal/mammal/elephant',
'animal/reptile',
'plant/sunflower'
]
input.forEach(element => {
var data = element.split('/');
var dog = data[2] === 'dog' ? true : false
var tiger = data[2] === 'cat' && data[3] === 'tiger' ? true : false
var lion = data[2] === 'cat' && data[3] === 'lion' ? true : false
expectedResult = {
data[0]: {
data[1]: {
"dog": dog,
"cat": {
"tiger": tiger,
"lion": lion
}
}
}
}
})
}
Late to the party, here is my try. I'm implmenting recursive approach:
var input = ['animal/mammal/dog', 'animal/mammal/cat/tiger', 'animal/mammal/cat/lion', 'animal/mammal/elephant', 'animal/reptile', 'plant/sunflower'];
result = (buildObj = (array, Obj = {}) => {
array.forEach((val) => {
keys = val.split('/');
(nestedFn = (object) => {
outKey = keys.shift();
object[outKey] = object[outKey] || {};
if (keys.length == 0) object[outKey] = true;
if (keys.length > 0) nestedFn(object[outKey]);
})(Obj)
})
return Obj;
})(input);
console.log(result);
I try with array reduce, hope it help
let input = [
"animal/mammal/dog",
"animal/mammal/cat/tiger",
"animal/mammal/cat/lion",
"animal/elephant",
"animal/reptile",
"plant/sunflower",
];
let convertInput = (i = []) =>
i.reduce((prev, currItem = "") => {
let pointer = prev;
currItem.split("/").reduce((prevPre, currPre, preIdx, arrPre) => {
if (!pointer[currPre]) {
pointer[currPre] = preIdx === arrPre.length - 1 ? true : {};
}
pointer = pointer[currPre];
}, {});
return prev;
}, {});
console.log(JSON.stringify(convertInput(input), null, 4));

JS Want to Iterate and set value of each key in a nested object to empty "", and create array of such objects

Base Object :
obj = {
"place": "{{base_gplaceId}}",
"feedInputs": [
{
"subCategoryQuestion": "{{base_gquestionId}}",
"context": "other",
"image": "abc.jpg",
"mediaMetadata": {
"stickerList": [
{
"id": "someid2",
"sticker": "delish",
"weight": 3
}
],
"textList": [
{
"text": "What an evening!!!"
}
]
}
}
]
};
more keys can have more nesting,
want to set the values of keys = "", one by one and push the updated object to an array
Expected OP :
[
{"place":"","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"","mediaMetadata":{"stickerList":[{"id":"someid2","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]},
{"place":"{{base_gplaceId}}","feedInputs":[{"subCategoryQuestion":"{{base_gquestionId}}","context":"other","image":"abc.jpg","mediaMetadata":{"stickerList":[{"id":"","sticker":"delish","weight":3}],"textList":[{"text":"Whatanevening!!!"}]}}]}
,...........]
tried couple of recursions, but not able to break after update inside the nested objects,
any simplistic approach ?
You could iterate the properties and change the values who are not objects. For having access to the complete object store the root as well and take a copy of the object with stringify and parse for the result set.
function visitAll(object, root = object) {
return Object
.keys(object)
.flatMap(k => {
if (object[k] && typeof object[k] === 'object') return visitAll(object[k], root);
const value = object[k];
object[k] = '';
const result = JSON.parse(JSON.stringify(root));
object[k] = value;
return result;
});
}
var object = { place: "{{base_gplaceId}}", feedInputs: [{ subCategoryQuestion: "{{base_gquestionId}}", context: "other", image: "abc.jpg", mediaMetadata: { stickerList: [{ id: "someid2", sticker: "delish", weight: 3 }], textList: [{ text: "What an evening!!!" }] } }] },
result = visitAll(object);
result.forEach(o => console.log(JSON.stringify(o)));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Merge arrays inside an array in javascript

Going through the link Merge/flatten an array of arrays in JavaScript? is somewhat what i need. But with that link and many other links shows merging of arrays of two arrays. What I have is as follows
[
{
"setter":[
{
"keyname":"Sample Size",
"cond":"=",
"value":1
}
]
},
{
"setter":[
{
"joinedcond":"and"
},
{
"keyname":"Sample Size",
"cond":"=",
"value":2
}
]
}
]
That is I have an array and inside that array I have an array "setter".
What I want is actually merging all setter array as a single array. Having said the merge should produce below output
[
{
"setter":[
{
"keyname":"Sample Size",
"cond":"=",
"value":1
},
{
"joinedcond":"and"
},
{
"keyname":"Sample Size",
"cond":"=",
"value":2
}
]
}
]
Help would be appreciated. Thanks
You can do it by using Array#reduce
var arr = [{"setter":[{"keyname":"Sample Size","cond":"=","value":1}]},{"setter":[{"joinedcond":"and"},{"keyname":"Sample Size","cond":"=","value":2}]}];
var finalArr = arr.reduce((a,x)=>{
(a[0].setter = a[0].setter || []).push(...x.setter);
return a;
},[{}]);
console.log(finalArr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use a hash table for the outer keys and concat the inner values with a dynamic approach for the outer keys, like setter.
var data = [{ setter: [{ keyname: "Sample Size", cond: "=", value: 1 }] }, { setter: [{ joinedcond: "and" }, { keyname: "Sample Size", cond: "=", value: 2 }] }],
result = data.reduce(function (hash) {
return function (r, o) {
Object.keys(o).forEach(function (k) {
if (!hash[k]) {
hash[k] = {};
r.push(hash[k]);
}
hash[k][k] = (hash[k][k] || []).concat(o[k]);
});
return r;
};
}(Object.create(null)), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using ES6 Rest and Spread operators we can achieve the same with a recursive function call:
let cdata= data.slice();//Copy the data
let condensedArray=[];
function flatten(data, ...rest){
let [{ setter }] = data;
condensedArray = [...condensedArray, ...setter];
data.splice(0,1);
data.length>0?flatten(data, ...data):console.log('Complete');
}
flatten(cdata, ...cdata);
console.log(condensedArray);

Summarize count of occurrences in an array of objects with Array#reduce

I want to summarize an array of objects and return the number of object occurrences in another array of objects. What is the best way to do this?
From this
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
To this
var newArrayOfSongs = [
{"title": "Blue", "playCount": 3 },
{"title": "Green", "playCount": 1}
]
I have tried
arrayOfSongs.reduce(function(acc, cv) {
acc[cv.title] = (acc[cv.title] || 0) + 1;
return acc;
}, {});
}
But it returns an object:
{ "Blue": 3, "Green": 1};
You should pass the initial argument to the reduce function as an array instead of object and filter array for the existing value as below,
Working snippet:
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
var newArrayOfSongs = arrayOfSongs.reduce(function(acc, cv) {
var arr = acc.filter(function(obj) {
return obj.title === cv.title;
});
if(arr.length === 0) {
acc.push({title: cv.title, playCount: 1});
} else {
arr[0].playCount += 1;
}
return acc;
}, []);
console.log(newArrayOfSongs);
To build on what you already have done, the next step is to "convert" the object to an array
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
var obj = arrayOfSongs.reduce(function(acc, cv) {
acc[cv.title] = (acc[cv.title] || 0) + 1;
return acc;
}, {});
// *** added code starts here ***
var newArrayOfSongs = Object.keys(obj).map(function(title) {
return {
title: title,
playCount:obj[title]
};
});
console.log(newArrayOfSongs);
I recommend doing this in two stages. First, chunk the array by title, then map the chunks into the output you want. This will really help you in future changes. Doing this all in one pass is highly complex and will increase the chance of messing up in the future.
var arrayOfSongs = [
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
{"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];
function chunkByAttribute(arr, attr) {
return arr.reduce(function(acc, e) {
acc[e[attr]] = acc[e[attr]] || [];
acc[e[attr]].push(e);
return acc;
}, {});
}
var songsByTitle = chunkByAttribute(arrayOfSongs, 'title');
var formattedOutput = Object.keys(songsByTitle).map(function (title) {
return {
title: title,
playCount: songsByTitle[title].length
};
});
There, now everything is named according to what it does, everything does just one thing, and is a bit easier to follow.
https://jsfiddle.net/93e35wcq/
I used a set object to get the unique track titles, then used Array.map to splice those and return a song object that contains play count inside the track title.
The Data:
var arrayOfSongs = [{
"title": "Blue",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}, {
"title": "Blue",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}, {
"title": "Blue",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}, {
"title": "Green",
"duration": 161.71,
"audioUrl": "/assets/music/blue",
"playing": false,
"playedAt": "2016-12-21T22:58:55.203Z"
}];
The Function:
function getPlayCount(arrayOfSongs) {
let songObj = {};
let SongSet = new Set();
arrayOfSongs.map(obj => (SongSet.has(obj.title)) ? true : SongSet.add(obj.title));
for (let songTitle of SongSet.values()) {
songObj[songTitle] = {
playCount: 0
};
arrayOfSongs.map(obj => (obj.title === songTitle) ? songObj[songTitle].playCount++ : false)
}
return songObj;
}
console.log(getPlayCount(arrayOfSongs));
Which isn't exactly what you wanted formatting wise, but if you're married to it, this will do the trick:
function getPlayCount(arrayOfSongs) {
let songObj = {};
let SongSet = new Set();
arrayOfSongs.map(obj => (SongSet.has(obj.title)) ? true : SongSet.add(obj.title));
for (let songTitle of SongSet.values()) {
songObj[songTitle] = 0;
arrayOfSongs.map(obj => (obj.title === songTitle) ? songObj[songTitle]++ : false)
}
return songObj;
}
console.log(getPlayCount(arrayOfSongs));
https://jsfiddle.net/93e35wcq/1/

Categories

Resources