Looking for help to convert/group an object to an array using a key, the key has a difference with its (-)suffix.
const obj = {
"name-1":"a",
"age-1":"20",
"email-1":"a#email.com",
"name-2":"b",
"age-2":"24",
"email-2":"b#email.com",
"name-3":"c",
"age-3":"22",
"email-3":"c#email1.com"
};
Expected result
[
{
"name":"a",
"age":"20",
"email":"a#email.com"
},
{
"name":"b",
"age":"24",
"email":"b#email.com"
},
{
"name":"c",
"age":"22",
"email":"c#email.com"
}
]
Maybe due wrong search keyword unable to find a duplicate question.
You can run a reduce on Object.entries and split the keys say name-1, age-2 etc by '-' and return an array of objects.
const obj = {
"name-1":"a",
"age-1":"20",
"email-1":"a#email.com",
"name-2":"b",
"age-2":"24",
"email-2":"b#email.com",
"name-3":"c",
"age-3":"22",
"email-3":"c#email1.com"
};
const res = Object.entries(obj).reduce((acc, [key, value]) => {
const [ k, i ] = key.split('-');
acc[i - 1] = acc[i - 1] || {};
acc[i-1][k] = value;
return acc;
}, [])
console.log(res);
At the code above inside the reduce I split the key by '-' and it gives me a key of an object and the index of the final array.
Then I check if the index i - 1 exists in the array. If not then initialized it by an empty object. Here I use i - 1 because the given object keys are starting from 1 but an array starts from 0.
Finally, I put the object value into the newly created object.
This is a nice algorithm question that I will resolve by writing it with ES6 syntax.
You can achieve this thanks to some functions such as Object.entries and reduce
Example:
const obj = {
"name-1":"a",
"age-1":"20",
"email-1":"a#email.com",
"name-2":"b",
"age-2":"24",
"email-2":"b#email.com",
"name-3":"c",
"age-3":"22",
"email-3":"c#email1.com"
};
const result = Object.entries(obj)
// Here we destructure the entry with on the left the key, and value on the right
.reduce((accumulator, [key, value]) => {
const [property, index] = key.split('-');
// Get the value currently being filled, or an empty object if it doesn't
// exist yet.
const entry = accumulator[index] || {};
accumulator[index] = {
// Spread the current entry to which we are adding
// the property to the object being filled
...entry,
// Dynamic key syntax
[property]: value,
};
return accumulator;
}, [])
// Remove "holes" from the array since it's indexed with given keys
.filter(value => value !== undefined);
console.log(result);
Related
Suppose I have an array of object:
const apple = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},
{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}]
I want to get count of all common values along with the value name as :
Expected O/P : [{"Harry Pottar":2},{"LOTR":3"}]
For this I tried as:
const id = "Harry Pottar";
const count = array.reduce((acc, cur) => cur.bookName === id ? ++acc : acc, 0);
As this gives the count, by this I can get count for each bookName. But how can I achieve my expected O/P scenario.
If anyone needs any further information please do let me know.
Good to see you know about .reduce! You’re pretty close, just need to save the result to a hashmap (plain object in JS).
const array = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}]
const result = array.reduce((acc, item) => {
const key = item.bookName
if (!acc.hasOwnProperty(key)) {
acc[key] = 0
}
acc[key] += 1
return acc
}, {})
// not sure why you want the result to be multiple objects. But here you go:
const output = Object.entries(result).map(([key, value])=> ({ [key]: value }))
Create a map from your data keyed by the book names, where the corresponding values are the objects you want in the output, with the count set to zero (you can use the computed property name syntax for the object's dynamic property). Then iterate the data again to increment the counters. Finally extract the values from the map into an array:
const apple = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},
{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}];
let map = new Map(apple.map(({bookName}) => [bookName, { [bookName]: 0 }]));
for (let {bookName} of apple) map.get(bookName)[bookName]++;
let result = Array.from(map.values());
console.log(result);
You were pretty close. You don't necessarily need to have those objects in an array though. Just have an object with the booknames as the property keys. It would make it easier to manage.
If you then want to create an array of objects from that data you can use map over the Object.entries of that object.
const apple = [{"bookName" :'Harry Pottar',part:"1"},{"bookName" :'Harry Pottar',part:"2"},{"bookName": 'LOTR',part:"1"},{"bookName": 'LOTR',part:"2"},{"bookName": 'LOTR',part:"3"}];
const out = apple.reduce((acc, { bookName }) => {
// If the property doesn't exist, create it
// and set it to zero, otherwise increment the value
// of the existing property
acc[bookName] = (acc[bookName] || 0) + 1;
return acc;
}, {});
console.log(out);
const result = Object.entries(out).map(([ key, value ]) => {
return { [key]: value };
});
console.log(result);
so I want to find unique values from an array.
so for example I have this array:
const mainArr = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884']
so I want to find the first matching value for each unique item.
for example, in the array, I have two strings with the shape prefix, six items with the size prefix, and two items with the height prefix.
so I want to output to be something like
const requiredVal = ["shape-10983", "size-2364", "height-3399"]
I want only the first value from any set of different values.
the simplest solution will be to iterate on the list and storing what you got in a dictionary
function removeSimilars(input) {
let values = {};
for (let value of input) {//iterate on the array
let key = value.splitOnLast('-')[0];//get the prefix
if (!(key in values))//if we haven't encounter the prefix yet
values[key] = value;//store that the first encounter with the prefix is with 'value'
}
return Object.values(values);//return all the values of the map 'values'
}
a shorter version will be this:
function removeSimilars(input) {
let values = {};
for (let value of input)
values[value.splitOnLast('-')[0]] ??= value;
return Object.values(values);
}
You could split the string and get the type and use it aks key for an object along with the original string as value. At result take only the values from the object.
const
data = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884'],
result = Object.values(data.reduce((r, s) => {
const [type] = s.split('-', 1);
r[type] ??= s;
return r;
}, {}));
console.log(result);
If, as you mentioned in the comments, you have the list of prefixes already available, then all you have to do is iterate over those, to find each first element that starts with that prefix in your full list of possible values:
const prefixes = ['shape', 'size', 'height'];
const list = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884']
function reduceTheOptions(list = [], prefixes = [], uniques = []) {
prefixes.forEach(prefix =>
uniques.push(
list.find(e => e.startsWith(prefix))
)
);
return uniques;
}
console.log(reduceTheOptions(list, prefixes));
Try this:
function getRandomSet(arr, ...prefix)
{
// the final values are load into the array result variable
result = [];
const randomItem = (array) => array[Math.floor(Math.random() * array.length)];
prefix.forEach((pre) => {
result.push(randomItem(arr.filter((par) => String(par).startsWith(pre))));
});
return result;
}
const mainArr = ['shape-10983', 'size-2364', 'size-7800', 'size-4602', 'shape-11073', 'size-15027', 'size-15030', 'size-15033', 'height-3399', 'height-5884'];
console.log("Random values: ", getRandomSet(mainArr, "shape", "size", "height"));
I modified the #ofek 's answer a bit. cuz for some reason the ??= is not working in react project.
function removeSimilars(input) {
let values = {};
for (let value of input)
if (!values[value.split("-")[0]]) {
values[value.split("-")[0]] = value;
}
return Object.values(values);
}
create a new array and loop over the first array and check the existing of element before in each iteration if not push it to the new array
I have an object that I need to convert into an array. This is the object:
const dogCounts: {
maltese: 4,
poodle: 2,
labrador: 10,
corso: 0
}
And I send it to a component thru props and I have a useMemo hook to convert it into a structure like this: [ [maltese, 4], [poodle, 2], ... ]
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.map(i => {
if (dogCounts[i] === 0) return; // Don't add it to map and skip it
return [i, dogCounts[i]]
})
}, [dogCounts])
When the number is zero I don't want to add it to the formatDogCounts variable. What I put above doesn't fit to eslints rules. Arrow function expected no return value.eslintconsistent-return.
Also I put that {} in the object.keys for the case when the counts haven't loaded yet is there a cleaner way to null check that?
map doesn't filter out values; doing a simple return; in map makes the corresponding entry in the result array undefined. If you want to do that, you'll need to filter first, or build the array another way (such as a simple loop).
Here's the filter approach:
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?
.filter(name => dogCounts[name] !== 0)
.map(name => [name, dogCounts[i]]);
}, [dogCounts]);
Note that Object.entries provides the very [name, value] pairs you want, so you could avoid map, and there's no reason for the conditional chaining operator as neither Object.keys nor Object.entries ever returns undefined or null:
const formatDogCounts = useMemo(() => {
const z = Object.entries(dogCounts || {})
.filter(([, value]) => value !== 0);
}, [dogCounts]);
Note the , prior to value in the [, value] destructuring pattern so we're grabbing the second array entry (the value), not the first (the name).
We can also avoid the calls to Object.entries and filter entirely when there is no dogCounts:
const formatDogCounts = useMemo(() => {
const z = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
}, [dogCounts]);
In a comment you've said:
The entries solution worked really well for me! Is there a way now to return an object instead of an array with total dog counts and then an array of items? Ex: formatDogCounts: { totalDogs: 30, items: [...] }
Sure. If there will only ever be a reasonable number of dogs (fewer than hundreds of thousands), I'd just do it as a separate operation at the end:
const formatDogCounts = useMemo(() => {
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => value !== 0)
: [];
return {
totalDogs: items.reduce((sum, [, value]) => sum + value, 0),
items,
};
}, [dogCounts]);
(A straight sum is the only ad hoc use of reduce I'll do, and even then I don't much care for it.)
Or you could make your filter callback slightly impure and count them as you go:
const formatDogCounts = useMemo(() => {
let totalDogs = 0;
const items = dogCounts
? Object.entries(dogCounts).filter(([, value]) => {
totalDogs += value;
return value !== 0;
})
: [];
return {
totalDogs,
items,
};
}, [dogCounts]);
If you want to perform a map and filter operation together, you can use flatMap, returning an empty array to skip an element.
const formatDogCounts = useMemo(() => {
const z = Object.keys(dogCounts || {})?.flatMap(i => {
if(dogCounts[i] === 0) return []; // Dont add it to map and skip it
return [[i, dogCounts[i]]];
})
}, [dogCounts])
You are trying to filter using a map which isn't possible. Map will return the same amount of values as you put in. You can do a foreach or you can do a combination of map and filter to get the expected results.
Foreach
const z = []
Object.keys(dogCounts).forEach((key) => {
if(dogCounts[key]) {
// if the value is truthy push the structure to the array.
z.push([key, dogCounts[key]]);
}
}
Map/Filter
const z = Object.keys(dogCount)
.map((key) => [key, dogCount[key]) // map to restructure object.keys
.filter(([key, value]) => value); // filter to remove falsey values (0, null, undefined)
I am trying to use reduce() for getting economy rate for a particular wicket.
Example data:
var data = [
{wicket:0, econ:20 },
{wicket:1, econ:10 },
{wicket:3, econ:45 },
{wicket:0, econ:15 },
{wicket:1, econ:32 }
]
I want reduce() method to return an array of objects which will look like this:
0: 20, 15
1: 10, 32
3: 45
What I am trying to do is initialize accumulator with object but in reduce() method I am not able to figure out how can I get the required array of objects with key value as wicketand values as economy.
My code:
const Economy = data.reduce( (a, {econ, wicket}) => {
a[wicket].push(econ);
},{})
I get undefined behaviour with above code.
If your data was meant to be an Array and not an Object (which it isn't right now, at least not a valid one) :
let data = [
{wicket:0, econ:20 },
{wicket:1, econ:10 },
{wicket:3, econ:45 },
{wicket:0, econ:15 },
{wicket:1, econ:32 }
];
let result = data.reduce((acc, curr) => {
if(acc[curr.wicket]) acc[curr.wicket].push(curr.econ);
else acc[curr.wicket] = [curr.econ];
return acc;
},{});
console.log(result);
You can use group the array using reduce like:
var data = [{"wicket":0,"econ":20},{"wicket":1,"econ":10},{"wicket":3,"econ":45},{"wicket":0,"econ":15},{"wicket":1,"econ":32}];
var result = data.reduce((c, v) => {
c[v.wicket] = c[v.wicket] || []; //Initiate the property as empty array of it does not exist
c[v.wicket].push(v.econ);
return c;
}, {});
console.log(result);
|| is an OR operator.
This means if c[v.wicket] exist, it will assign it to c[v.wicket] again. If it does not, assign an empty array []
c[v.wicket] = c[v.wicket] || [];
I have a data object as below:
[{"Positive":"14.71","Neutral":"50.0","Negative":"35.29"}]
I want to change this to the below specified format:
[{"type":"Positive","value":14.71},{"type":"Neutral","value":50.0},{"type":"Negative","value":35.29}]
Use Object.keys and Array#map methods.
var data = [{
"Positive": "14.71",
"Neutral": "50.0",
"Negative": "35.29"
}];
var res = Object.keys(data[0]) // get all object property names
// iterate over property names array
.map(function(k) {
// generate array element using property value
return {
type: k,
// cast data value to number using plus-sign prefix(as per requirement)
value: +data[0][k]
}
})
console.log(res);
I solve this using a generic pairs function which will be useful for you when trying to map, reduce, or filter objects in JavaScript
let input = [{"Positive":"14.71","Neutral":"50.0","Negative":"35.29"}];
let pairs = o =>
Object.keys(o).reduce((acc,k) => [...acc, [k, o[k]]], []);
let output = pairs(input[0]).map(([type,value]) =>
({type, value: Number(value)}));
console.log(output);
Alternatively, pairs can be implemented as a generator which will produce an overall more readable result
let input = [{"Positive":"14.71","Neutral":"50.0","Negative":"35.29"}];
function* pairs(o) {
for (let k of Object.keys(o))
yield [k, o[k]];
}
let output = Array.from(pairs(input[0]), ([type, value]) => {
return {type, value: Number(value)};
});
console.log(output);
Array.from provides a convenient way to collect values from an iterable value (and optionally map over them too), but you don't have to use it if you don't want to. Before you become familiar with it, you could just as easily use a for-of loop to collect the key/value pairs into your output object.
let input = [{"Positive":"14.71","Neutral":"50.0","Negative":"35.29"}];
function* pairs(o) {
for (let k of Object.keys(o))
yield [k, o[k]];
}
let output = [];
for (let [type, value] of pairs(input[0]))
output.push({type, value: Number(value)});
console.log(output);