Javascript modified array before group by - javascript

i am trying to modified array before groupBy
i have this array
[
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
]
i am trying to modified as per count value has in object value count 3 + 2 = 5 object should in array
witch function i use ? for output array like this
[
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
{
name: 'BoxTwo',
count: 2
},
]

It looks like you want to build a new array based on the contents of an existing one. For this, I would turn to Array.reduce(). For example:
const input = [
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
];
const output = input.reduce((boxes, box) => {
const { count = 0 } = box;
for (let i = 0; i < count; i++) {
boxes.push(box);
}
return boxes;
}, []);

You can iterate and flatten the before array with Array.flatMap(), and generate an array of items according to count using Array.from().
Note: You need to shallow clone the objects, if you don't want multiple references to the same object. This prevent all connected "objects" from changing, when one of them is changed.
const before = [{"name":"BoxOne","count":3},{"name":"BoxTwo","count":2}]
const after = before.flatMap(o =>
Array.from({ length: o.count }, () => ({ ...o }))
)
console.log(after)

let newArr = yourArr.reduce((arr, item) => {
for (let i = 0; i < item.count; i++) {
arr.push(item);
}
return arr;
}, []);
console.log(newArr);
you can use reduce array's function. And make iterating by count inside it
or if you need create new objects try spread syntax
let newArr = yourArr.reduce((arr, item) => {
for (let i = 0; i < item.count; i++) {
arr.push({...item});
}
return arr;
}, []);
console.log(newArr);

If your intention is to clone the object in the array the number of times the count inside it, here is the solution.
let boxes = [
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
]
let newBoxes = [];
boxes.forEach(box =>
{
let count = box.count;
while(count > 0)
{
newBoxes.push(box);
count--;
}
}
);
Your newBoxes will have the expected output.
[
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
{
name: 'BoxTwo',
count: 2
},
]

Here's a more concise way to use .reduce().
const boxes = [{"name":"BoxOne","count":3},{"name":"BoxTwo","count":2}]
const newBoxes = boxes.reduce((acc, o) =>
[...acc, ...new Array(o.count).fill(o)]
, [])
console.log(JSON.stringify(newBoxes, null, 2))
It assumes you don't mind using the same object reference.
It uses spread syntax to always fill the return value with the current content of the accumulator, and the content of a new array containing the desired number of objects for the current iteration, using .fill().

Related

Nested Reduce with values in JS

Good morning, after an array.map I have an array containing the same assignments with some nested ratings:
const assignments = [
{
name: "assignmentOne",
difficultyRating: 1,
funRating: 2
},
{
name: "assignmentOne",
difficultyRating: 3,
funRating: 4
},
{
name: "assignmentOne",
difficultyRating: 5,
funRating: 1
}
]
Now I would like to get the total difficulty/fun rating, which would look like one of the following:
//Both the difficulty and fun rating in the same record
const assignmentsTotal = [
{
name: "assignmentOne",
totalDifficultyRating: 9,
totalFunRating: 7
}
]
//Difficulty and fun rating as separate records
const assignmentsDifficultyTotal = [
{
name: "assignmentOne",
totalDifficultyRating: 9
}
]
const assignmentsFunTotal = [
{
name: "assignmentOne",
totalFunRating: 7
}
]
I'm pretty confident the best way to do this is using the reduce method.
After some digging around the only thing that came close to what I want to achieve is the following article, yet I'm not able to get this to work properly. Is there a good way to do this from the starting point above, or would it be better to create separate arrays using array.map and after use the reduce method?
If you are looking for same 'name' objects in array, below should be ok:
const reducer = assignments.reduce((total, current) => {
return { name: current.name, difficultyRating : total.difficultyRating + current.difficultyRating, funRating : total.funRating + current.funRating } });
if you want to group objects by name, then look at lodash groupby function. In general, lodash is very handy in all array/obj functionalities.
const assignments = [{
name: "assignmentOne",
difficultyRating: 1,
funRating: 2
},
{
name: "assignmentOne",
difficultyRating: 3,
funRating: 4
},
{
name: "assignmentOne",
difficultyRating: 5,
funRating: 1
},
{
name: "assignmentTwo",
difficultyRating: 5,
funRating: 3
},
{
name: "assignmentTwo",
difficultyRating: 5,
funRating: 1
}
];
// if you want the totals as an array:
const assignmentsTotalArray = assignments.reduce((totalArr, item) => {
// check whether the assignment is already in the array
const assignmentIndex = totalArr.findIndex(elem => elem.name === item.name);
// if the assignment is not in the array, add it and initialize the totals
// otherwise update the totals
if (assignmentIndex === -1) {
totalArr.push({
name: item.name,
totalDifficultyRating: item.difficultyRating,
totalFunRating: item.funRating
});
} else {
totalArr[assignmentIndex].totalDifficultyRating += item.difficultyRating;
totalArr[assignmentIndex].totalFunRating += item.funRating;
}
return totalArr;
}, []);
console.log('### assignmentsTotalArray:');
console.log(assignmentsTotalArray);
// if you want the totals as an object:
const assignmentsTotalObject = assignments.reduce((totalObj, item) => {
// if the output object already contains the assignment, sum the ratings
// otherwise create a new key for the assignment and initialize the ratings
if (totalObj[item.name]) {
totalObj[item.name].totalDifficultyRating += item.difficultyRating;
totalObj[item.name].totalFunRating += item.funRating;
} else {
totalObj[item.name] = {
totalDifficultyRating: item.difficultyRating,
totalFunRating: item.funRating
};
}
return totalObj;
}, {});
console.log('### assignmentsTotalObject:')
console.log(assignmentsTotalObject);

How to invert the structure of nested array of objects in Javascript?

I currently have an array that has the following structure:
data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
But I would like to restructure the array to get something like this:
data = [
{
name: "thing1",
info: [{
time: 100,
count: 3
}, {
time: 1000,
count: 7
}, {
}]
},
{
name: "thing2",
info: [{
time: 100,
count: 2
}, {
time: 1000,
count: 0
}, {
}]
}
];
So basically the key would have to be switched from time to name, but the question is how. From other posts I have gathered that using the map function might work, but since other posts had examples to and from different structures I am still not sure how to use this.
There are a number of ways to achieve this however, the key idea will be to perform a nested looping of both data items and their (nested) info items. Doing that allows your algorithm to "visit" and "map" each piece of input data, to a corresponding value in the resulting array.
One way to express that would be to use nested calls to Array#reduce() to first obtaining a mapping of:
name -> {time,count}
That resulting mapping would then be passed to a call to Object.values() to transform the values of that mapping to the required array.
The inner workings of this mapping process are summarized in the documentation below:
const data=[{time:100,info:[{name:"thing1",count:3},{name:"thing2",count:2},{}]},{time:1e3,info:[{name:"thing1",count:7},{name:"thing2",count:0},{}]}];
const result =
/* Obtain array of values from outerMap reduce result */
Object.values(
/* Iterate array of data items by reduce to obtain mapping of
info.name to { time, count} value type */
data.reduce((outerMap, item) =>
/* Iterate inner info array of current item to compound
mapping of info.name to { time, count} value types */
item.info.reduce((innerMap, infoItem) => {
if(!infoItem.name) {
return innerMap
}
/* Fetch or insert new { name, info } value for result
array */
const nameInfo = innerMap[ infoItem.name ] || {
name : infoItem.name, info : []
};
/* Add { time, count } value to info array of current
{ name, info } item */
nameInfo.info.push({ count : infoItem.count, time : item.time })
/* Compound updated nameInfo into outer mapping */
return { ...innerMap, [ infoItem.name] : nameInfo }
}, outerMap),
{})
)
console.log(result)
Hope that helps!
The approach I would take would be to use an intermediate mapping object and then create the new array from that.
const data = [{time: 100, info: [{name: "thing1", count: 3}, {name: "thing2", count: 2}, {}]}, {time: 1e3, info: [{name: "thing1", count: 7}, {name: "thing2", count: 0}, {}]} ];
const infoByName = {};
// first loop through and add entries based on the name
// in the info list of each data entry. If any info entry
// is empty ignore it
data.forEach(entry => {
if (entry.info) {
entry.info.forEach(info => {
if (info.name !== undefined) {
if (!infoByName[info.name]) {
infoByName[info.name] = [];
}
infoByName[info.name].push({
time: entry.time,
count: info.count
});
}
});
}
});
// Now build the resulting list, where name is entry
// identifier
const keys = Object.keys(infoByName);
const newData = keys.map(key => {
return {
name: key,
info: infoByName[key]
};
})
// newData is the resulting list
console.log(newData);
Well, the other guy posted a much more elegant solution, but I ground this one out, so I figured may as well post it. :)
var data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
var newArr = [];
const objInArray = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o)
return true;
}
return false;
}
const getIndex = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o) {
return i;
}
}
return false;
}
const getInfoObj = (t, c) => {
let tmpObj = {};
tmpObj.count = c;
tmpObj.time = t;
return tmpObj;
}
for (var i=0; i < data.length; i += 1) {
let t = data[i].time;
for (var p in data[i].info) {
if ("name" in data[i].info[p]) {
if (objInArray(data[i].info[p].name, newArr)) {
let idx = getIndex(data[i].info[p].name, newArr);
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newArr[idx].info.push(newInfoObj);
} else {
let newObj = {};
newObj.name = data[i].info[p].name;
let newInfo = [];
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newInfo.push(newInfoObj);
newObj.info = newInfo;
newArr.push(newObj);
}}
}
}
console.log(newArr);
try to use Object.keys() to get the key

how to count duplicate values object to be a value of object

how to count the value of object in new object values
lets say that i have json like this :
let data = [{
no: 3,
name: 'drink'
},
{
no: 90,
name: 'eat'
},
{
no: 20,
name: 'swim'
}
];
if i have the user pick no in arrays : [3,3,3,3,3,3,3,3,3,3,3,90,20,20,20,20]
so the output should be an array
[
{
num: 3,
total: 11
},
{
num: 90,
total: 1
},
{
num:20,
total: 4
}
];
I would like to know how to do this with a for/of loop
Here is the code I've attempted:
let obj = [];
for (i of arr){
for (j of data){
let innerObj={};
innerObj.num = i
obj.push(innerObj)
}
}
const data = [{"no":3,"name":"drink"},{"no":90,"name":"eat"},{"no":20,"name":"swim"}];
const arr = [3,3,3,3,3,3,3,3,3,3,3,20,20,20,20,80,80];
const lookup = {};
// Loop over the duplicate array and create an
// object that contains the totals
for (let el of arr) {
// If the key doesn't exist set it to zero,
// otherwise add 1 to it
lookup[el] = (lookup[el] || 0) + 1;
}
const out = [];
// Then loop over the data updating the objects
// with the totals found in the lookup object
for (let obj of data) {
lookup[obj.no] && out.push({
no: obj.no,
total: lookup[obj.no]
});
}
document.querySelector('#lookup').textContent = JSON.stringify(lookup, null, 2);
document.querySelector('#out').textContent = JSON.stringify(out, null, 2);
<h3>Lookup output</h3>
<pre id="lookup"></pre>
<h3>Main output</h3>
<pre id="out"></pre>
Perhaps something like this? You can map the existing data array and attach filtered array counts to each array object.
let data = [
{
no: 3,
name: 'drink'
},
{
no:90,
name: 'eat'
},
{
no:20,
name: 'swim'
}
]
const test = [3,3,3,3,3,3,3,3,3,3,3,90,20,20,20,20]
const result = data.map((item) => {
return {
num: item.no,
total: test.filter(i => i === item.no).length // filters number array and then checks length
}
})
You can check next approach using a single for/of loop. But first I have to create a Set with valid ids, so I can discard noise data from the test array:
const data = [
{no: 3, name: 'drink'},
{no: 90, name: 'eat'},
{no: 20, name: 'swim'}
];
const userArr = [3,3,3,3,3,3,3,3,7,7,9,9,3,3,3,90,20,20,20,20];
let ids = new Set(data.map(x => x.no));
let newArr = [];
for (i of userArr)
{
let found = newArr.findIndex(x => x.num === i)
if (found >= 0)
newArr[found].total += 1;
else
ids.has(i) && newArr.push({num: i, total: 1});
}
console.log(newArr);

How can I refine this JS array search function?

I have the following function:
export const functionName = (key) => {
const data = [
{
displayOrder: 0,
key: 'key-name-1',
},
{
displayOrder: 2,
key: 'key-name-2',
},
];
for (let index in data) {
return data[index].displayOrder === 0
? data[index].key
: data[0].key;
}
};
The purpose of the function is to return the key with the lowest displayOrder. This works, but is there a better more slick method to achieve this?
Secondly, how best could I create a similar version to re-order the entire array based on displayOrder?
The proper method to use when you want to extract a single thing from an array (and you can't identify the element by itself with .find) is to use .reduce:
const functionName = () => {
const data = [
{
displayOrder: 0,
key: 'key-name-1',
},
{
displayOrder: 2,
key: 'key-name-2',
},
];
const lowestDisplayObj = data.reduce((lowestObjSoFar, currentObj) => {
if (currentObj.displayOrder < lowestObjSoFar.displayOrder) return currentObj;
return lowestObjSoFar;
}, { displayOrder: Infinity, key: null });
return lowestDisplayObj.key;
};
console.log(functionName());
Note that your current argument to functionName, key, was unused in your snippet, I'm not sure what it's for.
You could also use .sort() and select the first element in the array, but that's more computationally expensive than it needs to be.
You could make use of array reduce and do something like below.
const data = [{
displayOrder: 10,
key: 'key-name-10',
},
{
displayOrder: 2,
key: 'key-name-2',
},
{
displayOrder: 1,
key: 'key-name-1 Lowest',
}
];
let a = data.reduce((prev, item) => {
if (!prev) {
return item;
}
return (item.displayOrder < prev.displayOrder) ? item : prev;
});
console.log(a.key);
const data = [
{
displayOrder: 0,
key: 'key-name-1',
},
{
displayOrder: 2,
key: 'key-name-2',
},
];
const min = Math.min.apply(null, data.map(({displayOrder}) => +displayOrder));
const obj = data.find((obj) => obj.displayOrder == min);
console.log(obj.key);

Group multiple elements in array with JavaScript

I have an array
[
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 20 },
{ price: 20 },
]
and I want it transformed into
[
{ numElements: 4, price: 10 },
{ numElements: 2, price: 20 },
]
I have tried using arr.reduce((prev, curr) => ..., []) to accomplish this, but I can't figure out how to do it.
A traditional method might use a for/loop to wrangle the data, but these days JavaScript has a number of functional methods that can help. This code uses reduce and map. To get your data in the format you want is a two stage process.
First, use reduce to create a hash table using the price as a key (because you know the each price is going to be unique:
const obj = arr.reduce((p, c) => {
// If price exists as a key its value by 1
// otherwise set it to 1.
p[c.price] = ++p[c.price] || 1;
return p;
}, {});
OUTPUT
{
"10": 4,
"20": 2
}
As it stands you've got a perfectly good object that you can access by the key/price and I would probably just stop there:
obj['10'] // 4
But if you want to get that data into the format in your question, map over the object keys to return an array of new objects.
const out = Object.keys(obj).map(key => {
return { price: +key, numElements: obj[key] };
});
DEMO
var hash = {}, result = [];
arr.forEach(function(el){
if(hash[el.price]){
hash[el.price].numElements++;
}else{
result.push(hash[el.price]={price:el.price,numElements:1});
}
});
Run
May use a hash table for price lookup. Or with reduce and find:
arr.reduce((res,{price})=>
(( res.find(el=>el.price===price) || res[res.push({price,numElements:0})-1] )
.numElements++,res)
);
Run
You can use try this:
let arr = [
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 20 },
{ price: 20 },
]
let result = []
let counter = {}
arr.forEach( el => {
if (!counter[el.price]) counter[el.price] = 1
else counter[el.price]++
console.log(counter[el.price])
})
for (let id in counter) {
result.push({numElements: counter[id], price: id})
}
Assuming that the data comes sorted on price property, with a single .reduce() you may do as follows;
var data = [{ price: 10 }, { price: 10 }, { price: 10 }, { price: 10 }, { price: 20 }, { price: 20 }],
result = data.reduce((r,d,i) => i ? r[r.length-1].price === d.price ? (r[r.length-1].numElemenets++, r)
: (r.push(Object.assign({}, d, {numElemenets: 1})),r)
: [Object.assign({}, d, {numElemenets: 1})], {});
console.log(result);
You could look up the price in the result array and if not found insert a new object.
var data = [{ price: 10 }, { price: 10 }, { price: 10 }, { price: 10 }, { price: 20 }, { price: 20 }],
grouped = data.reduce((r, { price }) => {
var t = r.find(p => price === p.price);
t || r.push(t = { numElements: 0, price });
t.numElements++;
return r;
}, []);
console.log(grouped);

Categories