lodash: aggregating and reducing array of objects based on date - javascript

I am new to Lodash and Functional Programming concepts. So, I have an array of objects with day-wise date like these:
[
{
"date": '1-Jan-2015',
"count": 4
},
{
"date": '4-Jan-2015',
"count": 3
},
{
"date": '1-Feb-2015',
"count": 4
},
{
"date": '18-Feb-2015',
"count": 10
}
]
and I want to reduce and aggregate it in such a way that I get an array of objects where each object has monthly data instead of day-wise data like this:
[
{
"date": 'Jan, 2015',
"count": 7 // aggregating the count of January
},
{
"date": 'Feb, 2015',
"count": 14 //aggregating the count of February
}
]
Currently, I have a written a very unreadable and convoluted code full of ifs and fors which works. However, I want to refactor it using lodash. Is it possible using lodash? I looked around and found _.reduce and _.groupBy which I can probably use but I am stumped right now and can't figure out a good clean implementation.

We can use _.reduce & _.values
var arr = [
{
"date": '1-Jan-2015',
"count": 4
},
{
"date": '4-Jan-2015',
"count": 3
},
{
"date": '1-Feb-2015',
"count": 4
},
{
"date": '18-Feb-2015',
"count": 10
}
]
_.values(_.reduce(arr,function(result,obj){
var name = obj.date.split('-');
name = name[1]+', '+name[2];
result[name] = {
date:name,
count:obj.count + (result[name]?result[name].count:0)
};
return result;
},{}));

You don't need lodash to achieve what you want, you could use plain old Javascript:
var array = [{
"date": '1-Jan-2015',
"count": 4
}, {
"date": '4-Jan-2015',
"count": 3
}, {
"date": '1-Feb-2015',
"count": 4
}, {
"date": '18-Feb-2015',
"count": 10
}]
var result = array.reduce(function(ar, item) {
var index = item.date.split('-').slice(1,3).join(', ') //getting date Month-Year
_item = ar.filter(function(a) {
return a.date === index
})[0] // getting item if already present in array
// getting index of _item if _item is already present in ar
indexOf = ar.indexOf(_item)
if(indexOf > -1)
// we sum the count of existing _item
ar[indexOf] = {date: index, count: count: _item.count + item.count }
else
// item is not yet in the array, we push a new _item
ar.push({date: index, count: item.count})
return ar; // return the array as required by reduce
}, []) // initialize the reduce method with an empty array
console.log(result) // your array with aggregated dates
And for the fun, a lodash version:
_.values(array.reduce(function(obj, item) {
var index = item.date.split('-').slice(1, 3).join(', ')
obj[index] = {date: index, count: (obj[index] && obj[index].count || 0) + item.count}
return obj
}, {}))
See jsfiddle here

Related

Javascript - Get occurence of json array with ESLINT 6

I can't set up an algo that counts my occurrences while respecting ESlint's 6 standards in javascript.
My input table is :
[
{
"id": 2,
"name": "Health",
"color": "0190fe"
},
{
"id": 3,
"name": "Agriculture",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
}
]
And i want to get :
{"Urban planning": 2, "Health": 1, ...}
But that does not work with ESLINT / REACT compilation...
This is my code :
const jsonToIterate = *'MyPreviousInputJson'*
const names = []
jsonToIterate.map(item => (names.push(item.name)))
const count = []
names.forEach(item => {
if (count[item]){
count.push({text: item, value: 1})
} else {
count.forEach(function(top){top.text === item ? top.value =+ 1 : null})
}
})
Thank you so much
Well, you want an object in the end, not an array, so count should be {}. I also wouldn't use map if you're not actually returning anything from the call. You can use reduce for this:
let counts = topicsSort.reduce((p, c, i, a) => {
if (!p.hasOwnProperty(c.name)) p[c.name] = 0;
p[c.name]++;
return p;
}, {});
I'm half exppecting someone to close this as a duplicate because all you've asked for is a frequency counter. But here's an answer anyway:
const jsonToIterate = *'MyPreviousInputJson'*;
const names = {};
jsonToIterate.map(obj => {
if(obj.name in names){
names[obj.name]++
}
else{
names[obj.name] = 1;
}
})

How can I obtain a new json with only unique values from the original

I'm having a hard time trying to obtain a json that has only unique (date, hour) values from two keys combined. The json is something like this:
{
"date": "2019-07-11",
"hour": "12:00",
"idAppointmentsHour": 47914218,
"shift": "T"
},
So if they have the same date and the same shift, I want to group this info. Im using ES5, and my code so far is like this:
for (var i = 0; i < agendaDatas.length; i++) {
if (!datas.includes(agendaDatas[i].data)) {
if (!datas.includes(agendaDatas[i].turno)) {
datas.push({"date": agendaDatas[i].data});
datas.push({"shift": agendaDatas[i].shift})
}
}
}
I have tried includes
All the values from the json are being copyied. i can't use ES6 Set because the platform runs in ES5.
Basically, you could take an array of keys for a grouping identifier and use an object for grouping same groups.
function getKey(keys) {
return function (object) {
return keys.map(function (k) { return object[k]; }).join('|');
};
}
var getKeyDateShift = getKey(['date', 'shift']),
groups = Object.create(null),
result = data.reduce(function (r, o) {
var key = getKeyDateShift(o);
if (!groups[key]) {
result.push(groups[key] = { date: o.date, shift: o.shift, data: [] });
}
groups[key].data.push(o);
return r;
}, []);
You can do it with a cycle, on each iteration via .filter() check whether a match already exists. If not, then create it. Anyway, add the unique-ish values to the group:
//Test data
var items = [
{
"date": "2019-07-11",
"hour": "12:00",
"idAppointmentsHour": 47914218,
"shift": "T"
}, {
"date": "2019-07-11",
"hour": "12:01",
"idAppointmentsHour": 47914219,
"shift": "T"
}, {
"date": "2019-07-11",
"hour": "12:02",
"idAppointmentsHour": 47914220,
"shift": "T"
}, {
"date": "2019-07-11",
"hour": "12:03",
"idAppointmentsHour": 47914218,
"shift": "TY"
},
];
//The logic
var output = [];
for (var item of items) {
var existent = output.filter(function(current) {
return ((item.date === current.date) && (item.shift === current.shift));
});
existent = (existent.length ? existent[0] : {date: item.date, shift: item.shift, group : []});
if (!existent.group.length) output.push(existent);
existent.group.push({
idAppointmentsHour: item.idAppointmentsHour,
hour: item.hour
});
}

Sort by the sum of array in object

I am looking for a solution to sort an array by the sum of an array property within an object.
For example if the main array is
[
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
]
How can I sort the sum of Day to return as
[
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
},
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
}
]
You just need sort your array with comparator, that uses reduce to calc sum of inner array values:
let arr = [{"Grid": {"Day": [11,12]}, "Name": "One"},
{"Grid": {"Day": [5,2]}, "Name": "Two"},
{"Grid": {"Day": [1,2]}, "Name": "Two"}];
let sum = el => el.Grid.Day.reduce((a,b) => a + b);
arr.sort((a,b) => sum(a) - sum(b));
console.log(arr)
You can use a combination of reduce to sum the array, and sort to order the output:
var input = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
];
var result = input.sort( (a,b) => sumOfDay(a) - sumOfDay(b));
console.log(result);
function sumOfDay(obj){
return obj.Grid.Day.reduce( (acc,curr) => acc + curr, 0);
}
Note that Array.prototype.sort actually mutates the original array in place. so the above could also do
input.sort( (a,b) => sumOfDay(a) - sumOfDay(b));
console.log(input);
So, don't fall into the trap of thinking the original array is unchanged just because I assigned the result to result!.
If you do wish to sort a copy of the array do this:
var result = input.slice().sort( (a,b) => sumOfDay(a) - sumOfDay(b));
Create a new Array of a by mapping through it and using reduce on the Day Array of Grid to get your sum which you can compare within a sort to return your list sorted by summed days.
const a = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
]
const daySum = ({Grid}) => Grid.Day.reduce((prev, curr) => prev+curr, 0)
const sorted = [...a].sort(daySum)
console.log(sorted)
console.log(a) //Original array intact
Just "another" approach to solve the issue: assuming you (someday, later, eventually) may need to sort again, a good approach may also be to add a property to each grid item holding the sum of the days, avoiding the .reduce call every time you need to sort the array.
In this approach, .forEach is used to create the new property (through .reduce), and then .sort is used to sort the array in-place.
const input = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
];
// Add a DaySum property evaluating the sum of the days.
input.forEach(i => i.Grid.DaySum = i.Grid.Day.reduce((a,b) => a + b));
// ^--- the second parameter (initial value) is unneeded here due to the fact that all elements are actually numeric, hence if the initial value is the first element of the array, which is a number already.
// Sor the array by that property.
input.sort((a,b) => a.Grid.DaySum - b.Grid.DaySum);
console.log(input);
Or, as suggested by #Andreas below, you can directly assign the property while sorting:
const input = [
{
"Grid": {
"Day": [
11,
12
]
},
"Name": "One"
},
{
"Grid": {
"Day": [
5,
2
]
},
"Name": "Two"
}
];
const sum = (a,b) => a + b;
input.sort((a,b) => {
a.Grid.DaySum = a.Grid.DaySum || a.Grid.Day.reduce(sum);
b.Grid.DaySum = b.Grid.DaySum || b.Grid.Day.reduce(sum);
return a.Grid.DaySum - b.Grid.DaySum;
});
console.log(input);

How to add new value inside array of object by id (not new item)

I'm trying add a new value inside my array by id. I'm not trying add a new item in my array... For this I can use push(), but it add new item not a new value.
I'm trying do it:
My array:
const data =
[
{
"id": 1,
"year":2019,
"value": 2,
},
{
"id": 2,
"year": 2019,
"value": 89,
},
{
"id": 3,
"year": 2019,
"value": 99,
}
]
Inside an especific id I would to add a new value like this:
data.forEach(item => {
if(item.id === 2){
//data inside id 2 -> item: 55
}
})
So my new dataarray looks like this:
const data =
[
{
"id": 1,
"year":2019,
"value": 2,
},
{
"id": 2,
"year": 2019,
"value": 89,
"item": 55
},
{
"id": 3,
"year": 2019,
"value": 99,
}
]
In most of my searches, I found just how to add a new element. But this I know how to do (push()).
So how to add a new value inside specified id?
Just assign the property you want to add:
data.forEach(item => {
if(item.id === 2){
item.item = 55;
}
})
If the IDs are unique, you can use the .find() method:
var el = data.find(item => item.id === 2);
if (el) {
el.item = 55;
}
try
data.find(x=> x.id==2).item=55;
const data =
[
{
"id": 1,
"year":2019,
"value": 2,
},
{
"id": 2,
"year": 2019,
"value": 89,
},
{
"id": 3,
"year": 2019,
"value": 99,
}
]
data.find(x=>x.id==2).item=55;
console.log(data);
You can iterate and assign value based on your criteria
data.map(function(x){
if(x.id == 2){
x.value = 100;
}
})
You can implement method using Array.find to avoid unnecessary iterations:
const array = [
{
"id": 1,
"year":2019,
"value": 2,
},
{
"id": 2,
"year": 2019,
"value": 89,
},
{
"id": 3,
"year": 2019,
"value": 99,
}
];
const changeValue = (array, id, field, value) =>
array.find(el => el.id === id)[field] = value;
changeValue(array, 1, 'year', 9999);
console.log('result: ', array);
You have an array of objects and you want to add a field to one of the objects. So, first, you have to find the object you want to change. Array items can be accessed by index, but you don't know the index. There are several methods to find an item in an array.
var item = data.find(function(d, i){
return item.id === 2; //criteria
});
or in ES6 syntax:
var item = data.find(d=>d.id == 2);
after that, you can change item the way you want.
item.anotherField = 'another value';
As you said, push() adds an item to the array. It doesn't change existing items in the array.
Your code is more or less right there. To set the property of an item, you can do either object.propertyName = ... or object["propertyName"] = ....
With that, you'd simply need to update your example to look like this:
data.forEach(item => {
if(item.id === 2){
item.item = 55; //data inside id 2 -> item: 55
}
})
As a more efficient alternative, consider Array.find(). It won't continue to loop through the array after it finds the id, whereas your forEach will always loop through the array in its entirety.
const data = [ { "id": 1, "year":2019, "value": 2, }, { "id": 2, "year": 2019, "value": 89, }, { "id": 3, "year": 2019, "value": 99, } ];
( data.find(({id})=> id === 2) || {} ).item = 55;
console.log(data);
You'll notice I've followed the .find() with || {}. This is simply so that if an item with id === 2 isn't found, attempting to set the property won't throw an error.

JavaScript/JSON - Get the total average of values having the same key from multiple arrays

I am receiving real-time responses from the back-end that contains the following JSON (almost every second):
One Array:
{
"newUpdate": [
{
"id": "TP",
"val" : 3
},
{
"id": "TPE20",
"val" : 3
}]
}
Another array (after one second or less)
{
"newUpdate": [
{
"id": "CRK",
"val" : 24
},
{
"id": "TPE20",
"val" : 44
}]
}
I am getting the above JSON almost every second knowing that each time it comes with different values and id's, and the array itself does not have a specific size.
Well, what I want to do is to get the average of the values having the same key 'id'.
For example, for the above array, the average will be for TPE20 :
(3+44)/2 =23.2 (as it computes the average for the id : TPE20)
Then it should show it here (using JQuery for example) [Think of the real-time average value like in the stock market]
<div id="TPE20"></div>
Currently, using the below for loop, I print the JSON listed above:
for(var i in load.updates){
var id = load.newUpdate[i].id;
updatesMap[id] = load.newUpdate[i].value;
var valueOfID = newUpdate[id];
}
The challenge is that I am receiving a lot of arrays at once (1/sec), each array contains different "id" and "val", I really don't know how I can compute the average using the way I described above!
Just use an object with keys representing the ids of the array objects and the values as objects containing the count, total, and average of those ids.
When you receive a new array simply update the object:
function updateObj(arr) {
arr.forEach(function(el) {
var key = el.id;
obj[key] = obj[key] || { count: 0, total: 0, avg: 0 };
obj[key].count++;
obj[key].total += el.val;
obj[key].avg = obj[key].total / obj[key].count;
});
}
Here's a simulation with setInterval sending one array to the function each second, and then displaying the completed object in the console.
Does this help? It get the Average of the search term like you asked. It uses jquery $.each to iterate through the array
var newdata = [
{
"newUpdate": [
{
"id": "TP",
"val" : 3
},
{
"id": "TPE20",
"val" : 3
}]
},
{
"newUpdate": [
{
"id": "CRK",
"val" : 24
},
{
"id": "TPE20",
"val" : 44
}]
}
]
function getAverage(array, term){
var sum = 0, n = 0;
$.each(array, function(i, item){
n++
var arrs = item.newUpdate
$.each(arrs, function(d, place){
// console.log(d)
if (place.id == term){
sum +=place.val
}
})
})
return sum / n
}
document.write(getAverage(newdata, "TPE20"))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
You can use Array.prototype.forEach() , create a private property at an object to store number of occasions unique property occurs within callback; pass each object individually to function in succession to maintain averages of properties at single object
var a = {
"newUpdate": [{
"id": "TP",
"val": 3
}, {
"id": "TPE20",
"val": 3
}]
};
var b = {
"newUpdate": [{
"id": "CRK",
"val": 24
}, {
"id": "TPE20",
"val": 44
}]
}
var avg = {};
function update(update) {
update.newUpdate.forEach(function(value, index) {
if (!avg[value.id] || !avg.hasOwnProperty(value.id)) {
avg["_" + value.id] = 1;
avg[value.id] = value.val / avg["_" + value.id];
} else {
++avg["_" + value.id];
avg[value.id] += value.val;
avg[value.id] = avg[value.id] / avg["_" + value.id];
}
});
return avg
}
console.log(update(a), update(b), avg)

Categories

Resources