Format array of objects - javascript

I have this array of objects (data). These are the first to indexes:
0: Object
granularity_time: "total"
granularity_geo: "nation"
location_code: "norge"
border: "2020"
age: "90+"
sex: "female"
year: "1900"
week: "1"
yrwk: "1900-01"
season: "1899/1900"
x: "24"
date: "1900-01-01"
n: "219"
date_of_publishing: "2022-01-12"
tag_outcome: "death"
1: Object
granularity_time: "total"
granularity_geo: "nation"
location_code: "norge"
border: "2020"
age: "90+"
sex: "male"
year: "1900"
week: "1"
yrwk: "1900-01"
season: "1899/1900"
x: "24"
date: "1900-01-01"
n: "127"
date_of_publishing: "2022-01-12"
tag_outcome: "death"
Its statistics where men and woman in the same age has its own object. To index 0 is for woman age 90+, index 1 for men age 90+. Index 2 is for woman 80+, index 3 men 80+ etc.
I want to format the data so each index holds two objects, for men and woman in the same age.
Something like this:
const newData = [
{
woman: data[0],
men: data[1]
},
{
woman: data[2],
men: data[3]
},
{
woman: data[4],
men: data[5]
},
{
woman: data[6],
men: data[7]
},
{
woman: data[8],
men: data[9]
},
{
woman: data[10],
men: data[11]
},
{
woman: data[12],
men: data[13]
}
];
Ive tried to make a function that iterates over the data, but each entry ends up undefined:
const formatData = (data) => {
const newData = [];
data?.map((item, i) => {
i % 2 === 0 ? newData.push({ woman: item[i], men: item[i++] }) : null;
});
return newData;
};
What am I missing here?

Since the output is half of the input, it's really more of a reduce()...
const data = [{ gender: "m", name: "sam" }, { gender: "f", name: "sally" }, { gender: "m", name: "allen" }, { gender: "f", name: "abby" }, { gender: "m", name: "jim" }, { gender: "f", name: "jill" }];
const newData = data.reduce((acc, obj, i) => {
i % 2 ? acc[acc.length-1].woman = obj : acc.push({ man: obj })
return acc;
}, []);
console.log(newData)

You could use Array.from() and Array.map() to create the desired output from your original data array.
The length of the new array will be data.length / 2, and each alternate item will be assigned to either the men or women property of each element in the output array.
const template = { year: 1900, location_code: 'norge' };
// Some array of input data...
const data = Array.from({length: 14}, (v,k) => ({...template, id: k, gender: ( k % 2 === 0 ? 'female':'male' )}));
const result = Array.from( { length: data.length / 2 }, (v, idx) => {
return { women: data[2*idx], men: data[2*idx + 1], };
})
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }

This actually works, but im not sure if its best practice since im just using the map to get i..
const formatData = (data) => {
const newData = [];
data?.map((item, i) => {
i % 2 === 0
? newData.push({ woman: data[i], men: data[i + 1] })
: null;
});
return newData;
};

Related

Compare objects within an array and create a new array without duplicates

I've an array of objects:
[
{ name: "John", age: "34" },
{ name: "Ace", age: "14" },
{ name: "John", age: "45" },
{ name: "Harry", age: "11" },
]
I want to compare the objects within an array by name. If the duplicate name exists, I should compare the age and only keep the higher age object.
The expected output should be:
[
{ name: "Ace", age: "14" },
{ name: "John", age: "45" },
{ name: "Harry", age: "11" },
]
I am new to javascript/typescript and couldn't find any optimal solution for this problem. I hope, I was able to explain my problem clearly.
Thanks.
The next provided approach uses reduce and creates in a first step just an index/map of items of highest age which are each unique by name. Thus one could use the temporary state of the programmatically built result as lookup for already existing named items.
Within a second step one would retrieve the array of unique named items of highest age by passing such an index to Object.values.
function collectHighestAgeItemOfSameName(result, item) {
const { name, age } = item;
if (
!(name in result) ||
Number(result[name].age) < Number(age)
) {
result[name] = item;
}
return result;
}
const sampleData = [{
name: "John",
age: "34"
}, {
name: "Ace",
age: "14"
}, {
name: "Harry",
age: "9"
}, {
name: "John",
age: "45"
}, {
name: "Harry",
age: "11"
}, {
name: "Ace",
age: "13"
}];
console.log(
'reduced index of unique person items of highest age ...',
sampleData
.reduce(collectHighestAgeItemOfSameName, {})
)
console.log(
'array of unique person items of highest age ...',
Object
.values(
sampleData
.reduce(collectHighestAgeItemOfSameName, {})
)
)
.as-console-wrapper { min-height: 100%!important; top: 0; }
Maybe something like that
const obj = [{ name: "John", age: "34" }, { name: "Ace", age: "14" }, { name: "John", age: "45" }, { name: "Harry", age: "11" }];
const objCopy = JSON.parse(JSON.stringify(obj))
const res = objCopy.reduce((acc, obj) => {
const personExist = acc.find(({ name }) => name === obj.name);
if (personExist) {
if (parseInt(obj.age, 10) > parseInt(personExist.age, 10)) {
personExist.age = obj.age;
}
} else {
acc.push(obj);
}
return acc;
}, []);
console.log({ res });
.as-console-wrapper { min-height: 100%!important; top: 0; }
try this
var objArr=...your json object;
var maxValueGroup = "name";
var maxValueName = "age";
console.log(JSON.stringify(newArr(objArr,maxValueGroup, maxValueName)));
newArr
var newArr = function (objArr,maxValueGroup, maxValueName) {
var arr = groupBy(objArr, maxValueGroup);
var newArr = [];
$.each(arr, function (key) {
var maxVal = 0;
var maxValItem;
arr[key].forEach((element) => {
if (element[maxValueName] > maxVal) {
maxVal = element[maxValueName];
maxValItem = element;
}
});
newArr.push(maxValItem);
});
return newArr;
};
groupby
var groupBy = function (xs, key) {
return xs.reduce(function (rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
This works basically the same as #PeterSeliger's fine and upvote-worthy answer, except it uses a Map object which is nice because Map.set returns the Map object, allowing you to return it as the accumulator for the next iteration of the reduce function.
const data = [{name: "John", age: "34"}, {name: "Ace", age: "14"}, {name: "John", age: "45"}, {name: "Harry", age: "11"}];
const res = [...data.reduce(
(acc, val) =>
+(acc.get(val.name)?.age ?? -1) >= +val.age ?
acc :
acc.set(val.name, val),
new Map()
).values()];
console.log(JSON.stringify( res ));
.as-console-wrapper { min-height: 100%!important; top: 0; }
Other references:
Unary plus (+)
Optional chaining (?.)
Nullish coalescing operator (??)
Conditional (ternary) operator
Array.prototype.reduce()
Spread syntax (...)

Scalable table sorting approach

I want to sort each column in react. I can do it, but forcing the code, for example if I have this array and display it in a html table, I want when I click id it sort in ascending order, when I click name it sort in ascending order and when click again it sort descending order, i want the same with name and age. And at the same time i want an arrow that if that column is in ascendig is looking up otherwise is looking down.
const USERS = [
{ id: 1, name: "Andy", age: 32 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Tom Hulk", age: 40 },
{ id: 4, name: "Tom Hank", age: 50 },
{ id: 5, name: "Audra", age: 30 },
{ id: 6, name: "Anna", age: 68 },
{ id: 7, name: "Tom", age: 34 },
{ id: 8, name: "Tom Riddle", age: 28 },
{ id: 9, name: "Bolo", age: 23 },
];
The way that i can do it is like this, but it's ugly and not practical. And also, when i change the sort state for the arrow, it updates for all the arrow not for the arrow of the column that i clicked
const sortBy = (k) => {
if (k === "id") {
if (sort) {
const res = USERS.sort((a, b) => (a.id > b.id ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
} else {
const res = USERS.sort((a, b) => (a.id < b.id ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
}
} else if (k === "age") {
if (sort) {
const res = USERS.sort((a, b) => (a.age > b.age ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
} else {
const res = USERS.sort((a, b) => (a.age < b.agek ? 1 : -1));
setDataFilter(res);
setSort(!sort);
console.log(res);
}
} else if (k === "name") {
if (sort) {
const res = USERS.sort((a, b) => a.name.localeCompare(b.name));
setDataFilter(res);
setSort(!sort);
console.log(res);
} else {
const res = USERS.sort((a, b) => b.name.localeCompare(a.name));
setDataFilter(res);
setSort(!sort);
console.log(res);
}
} else {
console.log("hmm");
}
};
Try the below code. It checks the type of the sorting key value and choose the sorting method accordingly. I re-used your logic to make it simple for you.
const USERS = [
{ id: 1, name: "Andy", age: 32 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Tom Hulk", age: 40 },
{ id: 4, name: "Tom Hank", age: 50 },
{ id: 5, name: "Audra", age: 30 },
{ id: 6, name: "Anna", age: 68 },
{ id: 7, name: "Tom", age: 34 },
{ id: 8, name: "Tom Riddle", age: 28 },
{ id: 9, name: "Bolo", age: 23 },
];
const sortBy = (k, sort) => {
var res;
if( USERS[0] && (typeof USERS[0][k] == 'string')) {
res = sort ? USERS.sort((a, b) => a[k].localeCompare(b[k])) : USERS.sort((a, b) => b[k].localeCompare(a[k]));
} else {
const f = sort ? 1 : -1;
res = USERS.sort((a, b) => (a[k] > b[k] ? f : -f));
}
// setDataFilter(res);
// setSort(!sort);
console.log(res);
}
sortBy('name', 1);
sortBy('age', 1);
sortBy('name', 0);
sortBy('age', 0);
You may want to have sorting callbacks within a mapping object (by property name or by property type).
Also, do not forget to leverage useMemo()/ useCallback() hooks to boost sorting performance through memoizing the sorting output (which may be beneficial for large number of items):
const users = [
{ id: 1, name: "Andy", age: 32 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Tom Hulk", age: 40 },
{ id: 4, name: "Tom Hank", age: 50 },
{ id: 5, name: "Audra", age: 30 },
{ id: 6, name: "Anna", age: 68 },
{ id: 7, name: "Tom", age: 34 },
{ id: 8, name: "Tom Riddle", age: 28 },
{ id: 9, name: "Bolo", age: 23 },
],
sortDirection = {
'asc': 1,
'desc': -1
},
numericSorter = propName =>
sortOrder =>
({[propName]:a}, {[propName]:b}) =>
(a-b)*sortDirection[sortOrder],
stringicSorter = propName =>
sortOrder =>
({[propName]:a}, {[propName]:b}) =>
a.localeCompare(b)*sortDirection[sortOrder],
sortMap = {
id: numericSorter('id'),
name: stringicSorter('name'),
age: numericSorter('age')
},
sortUsers = (users, byProp, sortOrder) =>
users.sort(sortMap[byProp](sortOrder))
console.log(sortUsers(users, 'name', 'desc'))
console.log(sortUsers(users, 'age', 'asc'))
.as-console-wrapper{min-height: 100%;}

How to add property to json file with map function?

Hello I m trying to map through a json file and add a new property to every object in it.
DATA.JSON
const arr = [
{
index: 0,
name: "John",
hair: "Brown",
},
{
index: 1,
name: "Bob",
hair: "Blonde",
},
];
APP.JS
const data = require("./data.json");
const items = data.map((item) => {
return ????
});
I want to map through the array, and add a "age" property to every index with a value of 30 or even just an empty value would be ok. with result something like this:
RESULT
const a =
[
{
index: 0,
name: "John",
hair: "Brown",
age: 30
},
{
index: 1,
name: "Bob",
hair: "Blonde",
age: 30
},
];
const arr = [
{
index: 0,
name: "John",
hair: "Brown",
},
{
index: 1,
name: "Bob",
hair: "Blonde",
},
];
const result = arr.map((obj) => {
return { ...obj, age: 30 };
});
console.log(result);
You can also make it one-liner
const result = arr.map((obj) => ({ ...obj, age: 30 }));
If you want to print only hair of each index then
const arr = [
{
index: 0,
name: "John",
hair: "Brown",
},
{
index: 1,
name: "Bob",
hair: "Blonde",
},
];
const result = arr.map((obj) => {
return { ...obj, age: 30 };
});
const hairs = result.map((o) => o.hair);
console.log(hairs); // Printing as an array
console.log(hairs.toString()); // Printing as comma separated value
Hello you could do something like this:
const data = [
{
index:0,
name: "John",
hair: "Brown",
},
{
index:1,
name: "Bob",
hair: "Blonde",
},
];
const newData = data.map(item => ({
...item,
newProp: 'hello world',
}));
// Print whole data:
console.log(newData);
// Filter specific properties
const hair = newData.map(item => item['hair'])
console.log('only hair ->', hair);
Side note, you don't need the index property in the object since you can get it while looping.
map(currentItem, index) Docs
If you want to print specific properties of the object you can do as such:
console.log(newData.map(item => item['hair']));

Count and update duplicate entries from array

I have an array with duplicated entries.
I'm looking for:
removing all duplicate occurrences
And increment the val propertie off an element if we find an occurrence
here is an exemple of what I have:
const arr = [
{"id":"46","name":"Productivity","val":1},
{"id":"1","name":"test","val":1},
{"id":"43","name":"Health and Fitness","val":1},
{"id":"46","name":"Productivity","val":1},
{"id":"1","name":"test","val":1},
{"id":"46","name":"Productivity","val":1}
]
// Wanted result
const result = [
{"id":"46","name":"Productivity","val":3},
{"id":"43","name":"Health and Fitness","val":1},
{"id":"1","name":"test","val":2}
]
Here is a JsFiddle
You can easily achieve this using reduce and object destructuring.
const arr = [
{ id: "46", name: "Productivity", val: 1 },
{ id: "1", name: "test", val: 1 },
{ id: "43", name: "Health and Fitness", val: 1 },
{ id: "46", name: "Productivity", val: 1 },
{ id: "1", name: "test", val: 1 },
{ id: "46", name: "Productivity", val: 1 },
];
const result = arr.reduce((acc, curr) => {
const { id, name, val } = curr;
const isPresent = acc.find((el) => el.id === id);
if (!isPresent) acc = [...acc, { id, name, val }];
else isPresent.val = isPresent.val + val;
return acc;
}, []);
console.log(result);

How to add new attribute that need to be calculated recursively in JavaScript

I want to dynamically add new attribute to all of the items in an array using map. But while adding it to each item, the value of this attribute is the sum of all previous items newly added attribute
Consider this example :
let persons = [{
"name": "A",
"salary": 2
}, {
"name": "B",
"salary": 5
},{
"name":"C",
"salary":12
}];
I want to return :
[{
"name": "A",
"salary": 2,
"sumSalary":2
}, {
"name": "B",
"salary": 5,
"sumSalary":7
},{
"name":"C",
"salary":12,
"sumSalary":19
}];
I've tried this one:
let mutatedPersons = persons.map((currentValue, index, mutatedPersons) => ({
...currentValue,
sumSalary: currentValue.name === 'A' ? currentValue.salary : mutatedPersons[index - 1].sumSalary + currentValue.salary
}))
but i keep getting this :
[
0: {name: "A", salary: 2, sumSalary: 2}
1: {name: "B", salary: 5, sumSalary: NaN}
2: {name: "C", salary: 12, sumSalary: NaN}
]
The mutatedPersons you refer to is the original array (see map's parameters), and not the updated array, which doesn't actually exist before the map end. You can cache the previous sum in an external variable (prevSumSalary), and use it as the basis of the new one:
const persons = [{ name: "A", salary: 2 }, { name: "B", salary: 5 }, { name: "C", salary: 12 }]
let prevSumSalary = 0;
const mutatedPersons = persons.map((currentValue, index) => ({
...currentValue,
sumSalary: (prevSumSalary = prevSumSalary + currentValue.salary)
}))
console.log(mutatedPersons);
Another option is to use Array.reduce(), since you have access to the accumulated values:
const persons = [{ name: "A", salary: 2 }, { name: "B", salary: 5 }, { name: "C", salary: 12 }]
const mutatedPersons = persons.reduce((r, currentValue) => [...r,
({
...currentValue,
sumSalary: currentValue.salary + (r.length && r[r.length - 1].sumSalary)
})
], [])
console.log(mutatedPersons);
You could use a closure over sum and take it for added sumSalary property.
var data = [{ name: "A", salary: 2 }, { name: "B", salary: 5 }, { name: "C", salary: 12 }],
result = data.map((sum => o => ({ ...o, sumSalary: sum += o.salary }))(0));
console.log(result);
let persons = [{
"name": "A",
"salary": 2
}, {
"name": "B",
"salary": 5
},{
"name":"C",
"salary":12
}];
let sum = 0;
persons.map(curr => {
sum += curr.salary;
curr.sumSalary = sum;
});
console.log(persons);
This is an ideal case for Array#reduce.
One can use an accumulator (the whole salarySum) to get direct access to the last salarySum value.
Whenever one thinks about an aggregate function, one should immediately think about Array#reduce in first place!
Also, I've provided you a solution which doesn't mutate the source persons but it creates a new array which has salarySum on each item:
const persons = [{
name: "A",
salary: 2
}, {
name: "B",
salary: 5
}, {
name: "C",
salary: 12
}]
const {
persons: persons_,
salarySum
} = persons.reduce((result, person) =>
result.persons.push({
...person,
salarySum: (result.salarySum += person.salary)
}) && result, {
salarySum: 0,
persons: []
})
console.log(persons_)
console.log(salarySum)

Categories

Resources