Populate dynamic values in JSON in Javascript - javascript

I have a an array containing some values. I need to populate values dynamically using the array elements.
Below is the master array.
list = [{name: 'm1'}, {name: 'm2'},{name: 'm3'},{name: 'm4'},{name: 'm5'},]
I have JSON called demo.json
demo: {
listCard = {
'x': 0,
'y': 1,
'name': ''
},
layout: [
{
'col': '3',
'row': '3'
'grids': []
}
]
};
The result should be -
demo: {
listCard = {
'x': 0,
'y': 1,
'name': ''
},
layout: [
{
'col': '3',
'row': '3'
'grids': [
{
'x': 0,
'y': 1,
'name': 'm1'
},
{
'x': 0,
'y': 1,
'name': 'm2'
}
]
},
{
'col': '3',
'row': '3'
'grids': [
{
'x': 0,
'y': 1,
'name': 'm3'
},
{
'x': 0,
'y': 1,
'name': 'm4'
}
]
},
{
'col': '3',
'row': '3'
'grids': [
{
'x': 0,
'y': 1,
'name': 'm5'
}
]
}
]
};
Basically the result should be like demo.listcard.name should iterate list.length times & get each value. After that layout.grid should be assigned with the whole object of listcard with 2 objects.
Below is the method I used but I am constantly failing.
let listcard = demo.listcard; //here I get listcard object
const layout = demo.layout; // layout object
const noOfScreens = (arr, size) => arr.reduce((acc, e, i) => (i % size ? acc[acc.length - 1].push(e) : acc.push([e]), acc), []);
const screens = noOfScreens(list, 2); // master array is split into arrays of size 2.
for (let i = 0; i < screens.length; i++) {
layout[0].grids.push(layout);
}
I am stuck here. Please help me out

Here is what you want:
const demo = {
listCard: {
'x': 0,
'y': 1,
'name': ''
},
layout: []
};
const list = [{ name: 'm1' }, { name: 'm2' }, { name: 'm3' }, { name: 'm4' }, { name: 'm5' }]
for (var i = 0; i < list.length; i++) {
if (i % 2 == 0) {
demo.layout.push({
'col': '3',
'row': '3',
'grids': [{
'x': 0,
'y': 1,
'name': list[i].name
}]
})
} else {
demo.layout[Math.floor(i / 2)].grids.push({
'x': 0,
'y': 1,
'name': list[i].name
});
}
}
console.log(demo);

Related

Push array of objects that make up a count of 100

I have an array of objects like the following:
const items = [
{
'name': 'P1',
'value': 50
},
{
'name': 'P1',
'value': 49
},
{
'name': 'P2',
'value': 50
},
{
'name': 'P3',
'value': 50
}
]
I need to add up the values and then push into an array of objects only if the 1 or more objects make up a value total of 100.
Expected output:
const groupedItems = [
[
{
'name': 'P1',
'value': 50
},
{
'name': 'P1',
'value': 49
},
],
[
{
'name': 'P2',
'value': 50
},
{
'name': 'P3',
'value': 50
}
]
]
What I have tried:
const temp = [];
for (var i = 0; i < items.length; i++) {
if ((items[i] + items[i + 1]) < 100) {
temp.push(items[i]);
}
}
groupedItems.push(temp);
I guess i would approch your solution like this :
Each time retrieving the first array, reducing it and if the sum of the array + the value of your item is less than 100, pushing your item in the array.
Otherwise, pushing a new array in your groupedItems that contains your item.
There might be a better way of doing this but this is the main idea.
const items = [{
'name': 'P1',
'value': 50
},
{
'name': 'P1',
'value': 49
},
{
'name': 'P2',
'value': 50
},
{
'name': 'P3',
'value': 50
}
]
const groupedItems = []
items.forEach(item => {
if (groupedItems.length > 0) {
const lastArray = groupedItems[groupedItems.length - 1]
const sumLastArray = lastArray.reduce((total, current) => current.value + total,
0)
if (sumLastArray + item.value < 100) {
lastArray.push(item)
} else {
groupedItems.push([item])
}
} else {
groupedItems.push([item])
}
})
console.log(groupedItems)

How to randomly select exam 2 items of a category and each from others categories?

Suppose an array of objects:
arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
]
How can I randomly choose two Item of catId=1 and each Items from remaining category.
Required
arr2 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 3, 'name': 'G' }
]
This is a very simple and naïve approach like I explained on my comment, but it gets the job done:
var arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
];
//shuffles an array
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
//gets items with catId = 1, and shuffles them
var cat1 = shuffle(arr1.filter(function(item) {
return item.catId == 1;
}));
var otherCat = [];
//pushes items in the otherCat array that aren't catId = 1, and not duplicate category
for (var i = 0; i < arr1.length; i++) {
//if not catId = 1 and not already in array
if (arr1[i].catId != 1 && !find(arr1[i])) {
//get all items in this category, and shuffles them to get a random item
var thisCat = shuffle(arr1.filter(function(item) { return item.catId == arr1[i].catId; }))[0];
otherCat.push(thisCat);
}
}
//finds an item in otherCat array by catId
function find(item) {
return otherCat.find(function(i) {
return item.catId === i.catId;
});
}
var result = [];
result.push(cat1[0]);
result.push(cat1[1]);
//concatenate both arrays
Array.prototype.push.apply(result, otherCat);
console.log(JSON.stringify(result));
I coded it this way because it is very simple to see. You could in theory loop through the whole array once to get all catId = 1 and other catId into 2 different arrays (I know I am doing multiple passes to the array, but like I said, this is just so you can get the idea).
Another way of doing it (perhaps a little more complex) is by grouping the items by category, then looping thru each category and grabbing a random element (2 in the case of catId == 1):
var arr1 = [
{ 'catId': 1, 'name': 'A' },
{ 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' },
{ 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' },
{ 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' },
{ 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' },
{ 'catId': 1, 'name': 'J' }
];
//groups by a property
//https://stackoverflow.com/a/34890276/752527
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
//shuffles an array
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
var result = [];
var grouped = groupBy(arr1, 'catId');
var keys = Object.keys(grouped);
for (var i = 0; i < keys.length; i++) {
var group = grouped[keys[i]]
//if i have items in my group, shuffle them and grab 1, or 2 items from it
if (group && group.length > 0) {
var cat = shuffle(group);
result.push(cat[0]);
//adds the second item with catId ==1
if (group[0].catId === 1) {
result.push(cat[1]);
}
}
}
console.log(JSON.stringify(result));
If you want to return a list of n-number of items from a particular category and one from the remaining categories, then you could group the items by their catId and then map the entries to a randomized count (length) based on whether the current key is the chosen bias.
Edit: I added a bias to keep n-number of items from the category of choice.
const categories = [
{ 'catId': 1, 'name': 'A' }, { 'catId': 2, 'name': 'B' },
{ 'catId': 3, 'name': 'C' }, { 'catId': 2, 'name': 'D' },
{ 'catId': 1, 'name': 'E' }, { 'catId': 1, 'name': 'F' },
{ 'catId': 3, 'name': 'G' }, { 'catId': 3, 'name': 'H' },
{ 'catId': 2, 'name': 'I' }, { 'catId': 1, 'name': 'J' }
];
const sortFn = (
{ catId: ai, name: an },
{ catId: bi, name: bn }
) =>
(ai - bi) || an.localeCompare(bn);
const main = () => {
print(pickRand(categories, ({ catId }) => catId, 1, 2).sort(sortFn));
};
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
const grouped = (arr, keyFn) => arr.reduce((acc, item, idx) =>
({ ...acc, [keyFn(item)]: [...(acc[keyFn(item)] ?? []), idx] }), {});
const pickRand = (arr, keyFn, bias, count) =>
Object
.entries(grouped(arr, keyFn))
.flatMap(([key, idx]) =>
shuffle(idx).slice(0, bias == key ? count : 1)
.map(i => arr[i]));
const print = (arr) => console.log(arr.map(x => JSON.stringify(x)).join('\n'));
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }

Efficient way to get top count of an option in an array of objects

I have an array of objects like this,
[
{
user: 'A',
answers: [
{
id: 1,
score: 3,
},
{
id: 2,
score: 1,
},
...
]
},
{
user: 'B',
answers: [
...
where I have 200 users, each user answers a set of 40 questions, each question has an id and a score.
What I'm trying to do is add up each question's score. So that I can figure out which question has the highest score, which has the lowest. Aka, top question and bottom question.
What would be the best way to do this?
The current way I am doing feels a little long-winded.
const allAns = []
myList.forEach( user => allAns.push( ...user.answers ) )
const questionsScored = allAns.reduce( ( obj, cur ) => {
!obj[ cur.id ] ? obj[ cur.id ] = cur.score : obj[ cur.id ] += cur.score
return obj
}, {} )
const sortingList = []
for ( const qn in questionsScored ) {
sortingList.push( [ qn, questionsScored[ qn ] ] )
}
sortingList.sort( ( a, b ) => b[ 1 ] - a[ 1 ] )
console.log( sortingList[ 0 ], sortingList[ sortingList.length - 1 ] )
You're taking all the steps necessary so if it's working it's fine though you could replace some of your forEach() loops with available methods:
with .flatMap()
const allAns = myList.flatMap(({answers})=>answers);
and using Object.entries()
const sortingList = Object.entries(questionsScored);
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
allAns = input.flatMap(({ answers }) => answers),
questionsScored = allAns.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {}),
sortingList = Object.entries(questionsScored).sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
Or combined into a single chained call, but it's not necessarily better.
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
sortingList = Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
If you would like to avoid the sort() call you can instead collect the low and high counts using a forEach() after the initial reduce()
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
lowScore = { count: Infinity },
highScore = { count: -Infinity };
Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.forEach(([id, count]) => {
// update low count
if (count < lowScore.count) {
lowScore.count = count;
lowScore.id = id;
}
// update high count
if (count > highScore.count) {
highScore.count = count;
highScore.id = id;
}
});
console.log({ lowScore, highScore })
// sample data
let data = [{
user: 'A',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
{
user: 'B',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
]
let scoreSum = []; //scoreSum to store total score of each question
let initialValue = 0;
for (let i = 0; i < 4; i++) {
let sum = data.reduce(function (accumulator, currentValue) {
return accumulator + currentValue.answers[i].score;
}, initialValue)
scoreSum.push(sum);
}
let highestScore = Math.max(...scoreSum);
let lowestScore = Math.min(...scoreSum);
// increasing index by 1 to match with question numbers
let highestScoreIndex = scoreSum.indexOf(highestScore) + 1;
let lowestScoreIndex = scoreSum.indexOf(lowestScore) + 1;
// Array.prototype.getDuplicates returns an object where the keys are the duplicate entries
// and the values are an array with their indices.
Array.prototype.getDuplicates = function () {
var duplicates = {};
for (var i = 0; i < this.length; i++) {
if (duplicates.hasOwnProperty(this[i])) {
duplicates[this[i]].push(i);
} else if (this.lastIndexOf(this[i]) !== i) {
duplicates[this[i]] = [i];
}
}
return duplicates;
};
let sameScore = scoreSum.getDuplicates();
// checking if highest score has duplicates
// and if so then updaing highest score index
//with highest score indices
if (sameScore.hasOwnProperty(highestScore)) {
highestScoreIndex = sameScore[highestScore].map((a) => a + 1);
}
// checking if lowest score has duplicates
// and if so then updaing lowest score index
//with lowest score indices
if (sameScore.hasOwnProperty(lowestScore)) {
lowestScoreIndex = sameScore[lowestScore].map((a) => a + 1);
}
console.log(`Top question no(s): ${highestScoreIndex} highest score:${highestScore}`);
console.log(`bottom question no(s): ${lowestScoreIndex} lowest score:${lowestScore}`);
I only loop once through each answer in .answers for each user using nested reduce.
The input array got three users with each three answers.
let input = [{ user: 'A', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 4, }, { id: 3, score: 0, }] }, { user: 'c', answers: [{ id: 1, score: 0, }, { id: 2, score: 3, }, { id: 3, score:5, }] }]
function showBestAndWorstFrom(input) {
let highestScore = {'id': 0, 'score': -Infinity};
let lowestScore = {'id': 0, 'score': Infinity};
let currentScore = 0;
let id = 0;
const LAST_USER = input.length - 1;
let answers = input.reduce((combinedObj, user, userIndex) => {
return user.answers.reduce((_answerObj, _answer) => {
id = _answer.id
currentScore = (_answerObj[id] || 0) + _answer.score;
_answerObj[id] = currentScore;
if (userIndex == LAST_USER) {
highestScore = (highestScore.score < currentScore) ? {'id': id, 'score': currentScore } : highestScore;
lowestScore = (lowestScore.score > currentScore) ? {'id': id, 'score': currentScore } : lowestScore;
}
return _answerObj;
}, combinedObj);
}, {});
// console.log(answers); // { "1": 4, "2": 8, "3": 5 }
return {highestScore, lowestScore};
}
console.log(showBestAndWorstFrom(input))

Sum array of objects values by multiple keys in array

Suppose I have:
const arr = [
{'name': 'P1','value': 150, 'nn': 2},
{'name': 'P1','value': 150, 'nn': 3},
{'name': 'P2','value': 200, 'nn': 5},
{'name': 'P3','value': 450, 'nn': 1}
]
const keysToSum = ['value', 'nn' ]
and I want:
[
{ name: 'P1', value: 300, nn: 5 },
{ name: 'P2', value: 200, nn: 5 },
{ name: 'P3', value: 450, nn: 1 }
]
So I want to sum the values inside the keys value and nn (because these keys are in keysToSum) if name is the same.
How can I do that?
I know I can use reduce, for example:
const result = arr.reduce((acc, val) => {
const o = acc.filter((obj) => {
return obj.name == val.name;
}).pop() || {name: val.name, value: 0};
o.value += val.value;
acc.push(o);
return acc;
},[])
But this piece of code works with only a key (value in that case), how can generalize it using keysToSum?
const arr = [
{'name': 'P1','value': 150, 'nn': 2},
{'name': 'P1','value': 150, 'nn': 3},
{'name': 'P2','value': 200, 'nn': 5},
{'name': 'P3','value': 450, 'nn': 1}
];
const result = arr.reduce((acc, val) => {
const o = acc.filter((obj) => {
return obj.name == val.name;
}).pop() || {name: val.name, value: 0};
o.value += val.value;
acc.push(o);
return acc;
},[]);
console.log(result)
Using reduce with an object that uses the name as a key, you can easily keep track of the objecs with dupe and increment the properties.
const arr = [
{'name': 'P1','value': 150, 'nn': 0},
{'name': 'P1','value': 150, 'nn': 3},
{'name': 'P2','value': 200, 'nn': 2},
{'name': 'P3','value': 450, 'nn': 5}
]
const keysToSum = ['value', 'nn' ]
const summed = Object.values(arr.reduce((obj, record) => {
// have we seen this name yet? If not copy the record
if (!obj[record.name]) {
obj[record.name] = { ...record };
} else {
// if we saw it, sum the fields we care about
keysToSum.forEach(key => {
obj[record.name][key] += record[key];
})
}
return obj
}, {}));
console.log(summed)
You could reduce the array by using an object and iterate the wanted keys for summing property values.
const
array = [{ name: 'P1', value: 150, nn: 0 }, { name: 'P1', value: 150, nn: 3 }, { name: 'P2', value: 200, nn: 2 }, { name: 'P3', value: 450, nn: 5 }],
keysToSum = ['value', 'nn'],
result = Object.values(array.reduce((r, { name, ...o }) => {
if (!r[name]) r[name] = { name };
keysToSum.forEach(k => r[name][k] = (r[name][k] || 0) + o[k]);
return r;
}, []));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript finding value in recursive array

I made a function in order to find an element in a tree object. My function works, but sometimes the function don't find the value and stop before looking all the tree.
I can't explain why it works sometimes and sometimes not.
Here is my fiddle : http://jsfiddle.net/3cdwA/2/
When you click on categories like "Sciences" you can see that it works. But if you click on "Bandes-Dessinées" it should display "Comics" but it doesn't.
Here is my recursive function :
function getChildrenFromCurrentFolder(tree, targetFolder) {
console.log(tree);
// Find recursivly all direct children of targeted folder
if (targetFolder == tree.id) {
return tree.folders;
} else if (tree.folders.length > 0) {
var folders = [];
for (i = 0; folders.length == 0 && i < tree.folders.length; i++) {
folders = getChildrenFromCurrentFolder(tree.folders[i], targetFolder);
}
return folders;
}
return [];
}
here is my test tree :
tree = {
'id': 1,
'name': 'Mes Bookmarks',
'folders': [
{
'id': 2,
'name': 'Sciences',
'folders': [
{
'id': 3,
'name': 'Biologie',
'folders': [
{
'id': 12,
'name': 'Neurologie',
'folders': []
}
]
},
{
'id': 4,
'name': 'Astrophysique',
'folders': [
{
'id': 8,
'name': 'Cosmologie',
'folders': [
{
'id': 10,
'name': 'Système solaire',
'folders': []
}
]
},
{
'id': 9,
'name': 'Trous noirs',
'folders': []
}
]
},
{
'id': 5,
'name': 'Mathématiques',
'folders': []
}
]
},
{
'id': 6,
'name': 'Actualités',
'folders': [
{
'id': 11,
'name': 'Monde',
'folders': []
}
]
},
{
'id': 7,
'name': 'Bandes-dessinées',
'folders': [
{
'id': 13,
'name': 'Comics',
'folders': []
}
]
}
]
};
It's a simple, but common mistake. You forgot to declare your loop variables and that's trouble when doing recursion as you're creating a global that's being reused:
function displayFolders() {
...
for (var i = folders.length-1; i >= 0; i--)
... --^--
}
function getChildrenFromCurrentFolder(tree, targetFolder) {
...
for (var i = 0; folders.length == 0 && i < tree.folders.length; i++) {
... --^--
}
function getBreadcrumb(tree, targetFolder, breadcrumb) {
...
for (var i = 0; i < tree['folders'].length; i++)
... --^--
}
I'm not sure all the other logic is correct, but this definitely changes the behavior.
http://jsfiddle.net/3cdwA/4/

Categories