How to sum specific element in reduce object? - javascript

Lets say I have this array of objects:
const arrayOfObjects = [
{ task: "work", time: 1 },
{ task: "travel", time: 4 },
{ task: "work", time: 5 },
{ task: "eat", time: 3 },
{ task: "eat", time: 1 },
{ task: "eat", time: 5 }
];
and I want to return a single objects that returns each key as the task and each value as the sum of all values of the key.
for example, the produced object of the array above should be:
sumOfObejcts = {
work: 6,
travel: 4,
eat: 9
}
How can I do it properly with reduce function?
I don't know how to sum all the items of specific key, this is what I made after few tries from examples:
const sumOfObejcts = arrayOfObjects.reduce((acc, items) => {
let { task, time } = items;
return { ...acc, [task]: [...(acc[task] || []), time] };
}, {});
and the output I get is:
{
work: [1, 5],
travel: [4],
eat: [3, 1, 5]
}
So, I just want to return the sum of occurrence of the value instead.

Your solution is very close; except that you're creating array of values on each iteration, instead of summing the current time values.
Here I've changed the value you assign to the key by using a ternary statement. This checks to see if the task exists in the accumulator object; if the task already exists, that means there is already a sum for this task and thus we just need to add on the current time to the existing sum. Otherwise, if the accumulator object doesn't have the task, the value will be primed using the current task's time.
const sumOfObjects = arrayOfObjects
.reduce((acc, item) =>
({ ...acc, [item.task]: (
acc[item.task] // does the task exist in the accumulator object?
? acc[item.task] + item.time // if so, set a value equal to the current task's time plus the existing value
: item.time // otherwise, prime the task's value to the current time
) })
, {});

Use forEach and build the object
const sumOfObjects = (arr, all = {}) => (
arr.forEach(({ task, time }) => (all[task] = (all[task] ?? 0) + time)), all
);
const arrayOfObjects = [
{ task: "work", time: 1 },
{ task: "travel", time: 4 },
{ task: "work", time: 5 },
{ task: "eat", time: 3 },
{ task: "eat", time: 1 },
{ task: "eat", time: 5 },
];
console.log(sumOfObjects(arrayOfObjects));

Related

filtering similar ids by their latest version

I have a collection with id and version number properties. I want to filter this data so that only the latest record of each is shown.
const data = [
{
id: 1,
version: 1
},
{
id: 1,
version: 2
},
{
id: 1,
version: 3
},
{
id: 2,
version: 1
},
{
id: 3,
version: 1
},
{
id: 3,
version: 2
}
];
After the filter it should be:
const data = [
{
id: 1,
version: 1
},
{
id: 2,
version: 1
},
{
id: 3,
version: 2
}
];
What's the best way to do this filter with less complexity/Order? using lodash is allowed.
I was thinking to sort based on the version number, and pick all data with the greatest version number, then going to the next lower version and checking if it exists in the data then leaving it if not adding it, and so on until the version reaches 1. However, I am not sure if it's the best way and how I write it using lodash utils.
You can try following approach:
Create an Object with key as ID and value as version.
This will automatically retain latest value for a given key-value pair
Create a loop and create objects again
{ id: key, version: value }
const data = [{"id":1,"version":1},{"id":1,"version":2},{"id":1,"version":3},{"id":2,"version":1},{"id":3,"version":1},{"id":3,"version":2}]
const map = data.reduce(
(acc, {id, version}) => ({ ...acc, [id]: version}),
{}
)
const result = Object.entries(map).map(([k, v]) => ({id: k, version: v}))
console.log(result)
With lodash, group by the id, then map and take the object with the maximum version from each group.
Note: _.groupBy converts the array to an object using the id as key, and since you're using integer ids, the order would change according to the ids numeric values. If that's a problem see the 2nd solution.
const { map, groupBy, maxBy } = _
const data = [{"id":1,"version":1},{"id":1,"version":2},{"id":1,"version":3},{"id":2,"version":1},{"id":3,"version":1},{"id":3,"version":2}]
const result = map(
groupBy(data, 'id'),
g => maxBy(g, 'version')
)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Vanilla JS, reduce the the array to a Map. For each item check if it exists in Map, and if it doesn't or if it's version is greater than the one in the Map, set the current object for that id. Convert the Map's values back to array using Array.from().
Note: using a Map preserves the original order of the array items.
const data = [{"id":1,"version":1},{"id":1,"version":2},{"id":1,"version":3},{"id":2,"version":1},{"id":3,"version":1},{"id":3,"version":2}]
const result = Array.from(data.reduce((acc, o) =>
acc.has(o.id) && acc.get(o.id).version >= o.version
? acc
: acc.set(o.id, o)
, new Map()).values())
console.log(result)
This seems reasonably efficient: sort by both ID and version in one pass, then you can just filter down to the last instance of each ID, because that'll be the one with the highest version number. Of course, if your data comes pre-sorted, you can skip straight to the fun part; I reordered the source data here to show the sort working:
const latestVersions = d => {
// sort by ID and version:
d = d.sort((a, b) => {
if (a.id === b.id) {
return a.version - b.version
} else {
return a.id - b.id
}
}).filter((a, i) => {
// only return elements that aren't followed by another with the same ID:
return d[i + 1]?.id !== a.id
})
return d
}
const data = [{
id: 1,
version: 2
},
{
id: 2,
version: 1
},
{
id: 3,
version: 2
},
{
id: 1,
version: 1
},
{
id: 1,
version: 3
},
{
id: 3,
version: 1
}
];
console.log(latestVersions(data))

How to reformat a JSON array into another format "grouping" based on different keys

Question: How can I reformat this JSON array by "grouping" via different keys, using ReactJS?
I have a JSON array as :
[
{Product: "Shoes", Sold: 5, Bought : 0, Reversed : 2} ,
{Product: "Table", Sold: 2, Bought : 0, Reserved : 4}
]
The reason for this is the data type I'm working with, and on realizing I need to visualize this data in a different way (due to one of the graph packages I am using) I need to structure this data as:
[
{
Status: "Sold",
Shoes : 5,
Table : 2
} ,
{
Status: "Bought",
Shoes : 0,
Table : 0
} ,
{
Status: "Reserved",
Shoes : 2,
Table : 4
}
]
So I'm grouping the data into the keys other than Product, and then the keys after this are Product with the Value being the Product and it's "status".
Frankly, I am at a complete loss as to what to do, as I'm thinking the code required to generate this would be quite convoluted, so I'm very open to know if this just is too much work.
const data = [
{
Product: "Shoes",
Sold: 5,
Bought : 0,
Reserved : 2
} , {
Product: "Table",
Sold: 2,
Bought : 0,
Reserved : 4
}
];
let resultData = [];
Object.keys(data[0]).forEach((key, idx) => {
if (idx !== 0) {
let resultUnit = {
Status: key,
};
data.forEach(item => {
return resultUnit = {
...resultUnit,
[item.Product]: item[key],
}
})
resultData.push(resultUnit);
}
})
console.log(resultData);
// 0: {Status: "Sold", Shoes: 5, Table: 2}
// 1: {Status: "Bought", Shoes: 0, Table: 0}
// 2: {Status: "Reserved", Shoes: 2, Table: 4}
You can do this using the Array.reduce function. (Actually, two reduce functions).
Here's an extensible solution that allows for other statuses.
Note that I changed everything to lowercase, as is standard convention.
const items = [
{product: "Shoes", sold: 5, bought : 0, reserved : 2} ,
{product: "Table", sold: 2, bought : 0, reserved : 4}
]
//We declare the status types here.
const keys = ["sold", "bought", "reserved"];
// Just create the initial 'statuses' array.
function initAcc(keys) {
return keys.map((key) => {
return {
status: key
}
});
}
//Here we are iterating over each item, getting it to return a single accumulator array each time.
const newItems = items.reduce((acc, cur) => {
return addItemToAccumulator(acc, cur);
}, initAcc(keys));
console.log(newItems);
// This function maps of the accumulator array (ie. over each status).
function addItemToAccumulator(acc, item) {
return acc.reduce((acc, statusLine) => {
//Find the count from the existing status if it exists,
//Add the current items count for that status to it.
const itemCount = item[statusLine.status] + (statusLine[item.product] || 0);
//Return a modified status, with the new count for that product
return [
...acc,
{
...statusLine,
[item.product]: itemCount
}
];
}, []);
}
Lets just do a simple loop function and create a couple objects to clearly solve the problem here:
const data = [YOUR_INITIAL_ARRAY];
let Sold, Bought, Reserved = {};
data.forEach(({Product, Sold, Bought, Reserved})=> {
Sold[Product] = Sold;
Bought[Product] = Bought;
Reservered[Product] = Reserved;
});
let newArray = [Sold, Bought, Reserved];
I think you can see where this is going ^ I see a few others have given complete answers, but try and go for the clear understandable route so it makes sense.
All you have to do after this is set the status which i'd do off an enum and you are good

Merge objects with corresponding key values from two different arrays of objects

I've got two arrays that have multiple objects
[
{
"name":"paul",
"employee_id":"8"
}
]
[
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
}
]
How can I achieve the following with either ES6 or Lodash?
[
{
"name":"paul",
"employee_id":"8"
"data": {
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
}
]
I can merge but I'm not sure how to create a new child object and merge that in.
Code I've tried:
school_data = _.map(array1, function(obj) {
return _.merge(obj, _.find(array2, {employee_id: obj.e_id}))
})
This merges to a top level array like so (which is not what I want):
{
"name":"paul",
"employee_id":"8"
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
The connector between these two is "employee_id" and "e_id".
It's imperative that it's taken into account that they could be 1000 objects in each array, and that the only way to match these objects up is by "employee_id" and "e_id".
In order to match up employee_id and e_id you should iterate through the first array and create an object keyed to employee_id. Then you can iterate though the second array and add the data to the particular id in question. Here's an example with an extra item added to each array:
let arr1 = [
{
"name":"mark",
"employee_id":"6"
},
{
"name":"paul",
"employee_id":"8"
}
]
let arr2 = [
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
},
{
"years_at_school": 12,
"department":"Arr",
"e_id":"6"
}
]
// empObj will be keyed to item.employee_id
let empObj = arr1.reduce((obj, item) => {
obj[item.employee_id] = item
return obj
}, {})
// now lookup up id and add data for each object in arr2
arr2.forEach(item=>
empObj[item.e_id].data = item
)
// The values of the object will be an array of your data
let merged = Object.values(empObj)
console.log(merged)
If you perform two nested O(n) loops (map+find), you'll end up with O(n^2) performance. A typical alternative is to create intermediate indexed structures so the whole thing is O(n). A functional approach with lodash:
const _ = require('lodash');
const dataByEmployeeId = _(array2).keyBy('e_id');
const result = array1.map(o => ({...o, data: dataByEmployeeId.get(o.employee_id)}));
Hope this help you:
var mainData = [{
name: "paul",
employee_id: "8"
}];
var secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
var finalData = mainData.map(function(person, index) {
person.data = secondaryData[index];
return person;
});
Sorry, I've also fixed a missing coma in the second object and changed some other stuff.
With latest Ecmascript versions:
const mainData = [{
name: "paul",
employee_id: "8"
}];
const secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
// Be careful with spread operator over objects.. it lacks of browser support yet! ..but works fine on latest Chrome version for example (69.0)
const finalData = mainData.map((person, index) => ({ ...person, data: secondaryData[index] }));
Your question suggests that both arrays will always have the same size. It also suggests that you want to put the contents of array2 within the field data of the elements with the same index in array1. If those assumptions are correct, then:
// Array that will receive the extra data
const teachers = [
{ name: "Paul", employee_id: 8 },
{ name: "Mariah", employee_id: 10 }
];
// Array with the additional data
const extraData = [
{ years_at_school: 6, department: "Mathematics", e_id: 8 },
{ years_at_school: 8, department: "Biology", e_id: 10 },
];
// Array.map will iterate through all indices, and gives both the
const merged = teachers.map((teacher, index) => Object.assign({ data: extraData[index] }, teacher));
However, if you want the data to be added to the employee with an "id" matching in both arrays, you need to do the following:
// Create a function to obtain the employee from an ID
const findEmployee = id => extraData.filter(entry => entry.e_id == id);
merged = teachers.map(teacher => {
const employeeData = findEmployee(teacher.employee_id);
if (employeeData.length === 0) {
// Employee not found
throw new Error("Data inconsistency");
}
if (employeeData.length > 1) {
// More than one employee found
throw new Error("Data inconsistency");
}
return Object.assign({ data: employeeData[0] }, teacher);
});
A slightly different approach just using vanilla js map with a loop to match the employee ids and add the data from the second array to the matching object from the first array. My guess is that the answer from #MarkMeyer is probably faster.
const arr1 = [{ "name": "paul", "employee_id": "8" }];
const arr2 = [{ "years_at_school": 6, "department": "Mathematics", "e_id": "8" }];
const results = arr1.map((obj1) => {
for (const obj2 of arr2) {
if (obj2.e_id === obj1.employee_id) {
obj1.data = obj2;
break;
}
}
return obj1;
});
console.log(results);

Remove duplicate elements based on date field in javascript

I want a function that takes an array and filters out old duplicates.
Specifically, if duplicate ids exist in myList, keep only the object with the newest date. Given the following array
let myList = [{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-21 21:04:13"
},
{
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
},
{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
the function should return:
[{
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
},
{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
You can use the function reduce to build the desired output.
let myList = [{ id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-21 21:04:13"},{ id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5", date: "2018-02-22 21:04:13"},{ id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-23 21:04:13"}];
let result = Object.values(myList.reduce((a, {id, date}) => {
if (a[id]) {
if (a[id].date < date) a[id] = {id, date};
} else a[id] = {id, date};
return a;
}, {}));
console.log(result);
Put the entries into a hash table keyed by id. Each time you add an entry, look up the id and either keep the existing entry or replace it with the new one, based on whichever has a more recent date.
Map and Array.prototype.map() can be combined to functionally filter key based duplicates from arrays.
Array.prototype.sort() can be leveraged to guarantee order.
See below for a practical example.
// Input.
const input = [
{id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-21 21:04:13"},
{id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5", date: "2018-02-22 21:04:13"},
{id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-23 21:04:13"}
]
// Sort By Date.
const sortDate = array => array.sort((A, B) => new Date(A.date)*1 - new Date(B.date)*1)
// Filter Duplicates.
const filter = array => [...new Map(array.map(x => [x.id, x])).values()]
// Output.
const outputRaw = filter(input) // No guaranteed order.
const outputSorted = sortDate(filter(sortDate(input))) // Guaranteed latest.
// Proof.
console.log('Raw', outputRaw)
console.log('Sorted', outputSorted)
This isn't the best answer, just another take on #Ele's solution offered for completeness. Instead of plucking the values after the unique set is found, it works on the returned array for each iteration. The find during each iteration should be less efficient than a key lookup, which is one of the reasons it's not the best answer.
let myList = [{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-21 21:04:13"
}, {
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
}, {
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
let result = myList.reduce((arr, { id, date }) => {
let found = arr.find(v=>v.id==id)
if (found) {
if (found.date < date)
found.date = date
}
else
arr.push({ id, date });
return arr;
}, []);
console.log(result);

Counting and storing distinct elements and their counts from an array of object

I have an array of object and I want to count the number of distinct elements and counts of those objects.
[ { name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
]
I want to count the number of distinct names and store them in an object. I have tried it by 1# pushing all the names in an array,
2# then sorting them,
3# then calculating the number of distinct names and
4# finally pushing them to the object.
This process is too long. Is there a shorter way to do this. I am using Nodejs
Thanks in advance
You will create a new object, where the key is the name and the value the count:
var youArr = [
{ name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
];
var count = {}
for(var i=0; i < youArr.length; i++){
count[youArr[i].name] = count[youArr[i].name] || 0;
count[youArr[i].name]++;
}
alert(count['Namus']); // 2
This is a great place to use the reduce function:
The reduce() method applies a function against an accumulator and each
value of the array (from left-to-right) has to reduce it to a single
value.
...
reduce executes the callback function once for each element present in
the array, excluding holes in the array, receiving four arguments: the
initial value (or value from the previous callback call), the value of
the current element, the current index, and the array over which
iteration is occurring.
It would look something like this:
var arr = [ { name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
]
var counts = arr.reduce(function(counts, item) {
counts[item.name] = (counts[item.name] || 0) + 1;
return counts;
}, {});
counts is then:
{ Suman: 2, Namus: 2 }
Asked in the comments:
what if i want the count as well as name in an array of object like
[{name: 'Suman', count:'2'}, {name:'Namus', count:'2'}]
If you already have counts from the reduce call above, then you can map its keys to the format you want:
var countsArray = Object.keys(counts).map(function(name) {
return {name: name, count: counts[name]};
});
countsArray is then:
[ { name: 'Suman', count: 2 },
{ name: 'Namus', count: 2 } ]

Categories