sorting array and keeping only the last value of each day reactjs - javascript

i'm having a bit of a problem here, i have an array that looks like this :
const input = {
name: 'xy',
lastname: 'yx',
history: [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" }]
}
but it have more data and more dates than this exemple, i want to sort it into a new array and put only the newest value of each day
so it will look like this :
const input = {
name: 'xy',
lastname: 'yx',
history: [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" }],
newHistory:
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" }],
}
i currently did the sorting of all the dates but i'm having trouble in doing the rest.

Since you've already sorted your data you could use a single reduce to iterate over your array of sorted data while keeping track of a new output array.
For each data point, you compare its date by the last element of the output. If the year-month-day part is not the same, you add the data point to the output array.
Here are these steps written out in code:
const input = [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" } ];
const output = input.reduce(
(out, current) => {
const lastDate = out.at(-1)?.date.slice(0, 10);
const currentDate = current.date.slice(0, 10);
if (lastDate !== currentDate) {
out.push(current);
}
return out;
},
[]
);
console.log(output);
If you like one-liners, you could also write this as:
const input = [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" } ];
const output = input.reduce(
(out, current) => out.at(-1)?.date.slice(0, 10) === current.date.slice(0, 10) ? out : out.concat(current),
[]
);
console.log(output);

I think this is definitely a problem where splitting different tasks into functions helps make the code easier to understand.
You said in a comment that you wanted to group the calendar dates according to UTC time, but (if you change your mind) the code below will also allow you to optionally use the system time zone (as well as some other options).
I've included lots of comments to explain as you read, but feel free to ask for clarity in a comment if something still isn't clear.
'use strict';
/** #returns a stringified number with a minimum length */
function padN (n, maxLength = 2, fillString = '0') {
return String(n).padStart(maxLength, fillString);
}
/**
* #returns a default sorting algorithm function
* for an array of objects each having a specific key
*/
function createDefaultSortByObjKey (key, {reverse = false} = {}) {
return reverse
? (oA, oB) => oA[key] > oB[key] ? -1 : oA[key] < oB[key] ? 1 : 0
: (oA, oB) => oA[key] < oB[key] ? -1 : oA[key] > oB[key] ? 1 : 0;
}
/**
* Set the `utc` option to `true` to use UTC
* #returns a date string format like `"20220703"`
*/
function getSerializedYMD (date, {utc = false} = {}) {
const year = utc ? date.getUTCFullYear() : date.getFullYear();
const month = utc ? date.getUTCMonth() : date.getMonth();
const dayOfMonth = utc ? date.getUTCDate() : date.getDate();
return `${year}${padN(month)}${padN(dayOfMonth)}`;
}
/** #pure */
function transform (array, {
newestFirst = false,
parseDates = false,
utc = false,
} = {}) {
// Create a function for sorting dates, sorting by oldest/newest
const sortFn = createDefaultSortByObjKey('date', {reverse: newestFirst});
const sorted = array
// Create actual date objects for every date property value
.map(o => ({...o, date: new Date(o.date)}))
// Sort them
.sort(sortFn);
// This will be used to compare if the current object's date occurred
// on the same date as the previous
let lastKey = '';
// The objects will be stored in inner arrays, grouped by calendar dates
const grouped = [];
for (const obj of sorted) {
const key = getSerializedYMD(obj.date, {utc});
// Create a new inner array group if a new calendar date is encountered
if (key !== lastKey) grouped.push([]);
// Add the object to the current date group
grouped.at(-1).push(obj);
// Update the last key
lastKey = key;
}
// Now just pick one date from each group
return grouped.map(group => {
// Pick the oldest/newest object in the group
const obj = group.at(newestFirst ? 0 : -1);
return parseDates
// Return it directly with the date value as an actual date object
? obj
// Or convert the date back to an ISO string first
: {...obj, date: obj.date.toISOString()};
});
}
const input = {
name: 'xy',
lastname: 'yx',
history: [
{ value: 0.02, date: '2022-08-02T23:03:22.895Z' },
{ value: 0.04, date: '2022-08-02T22:03:16.603Z' },
{ value: 0.08, date: '2022-08-02T21:03:20.378Z' },
{ value: 0.02, date: '2022-08-01T23:03:32.584Z' },
{ value: 0.04, date: '2022-08-01T22:03:30.311Z' },
],
};
const result = {
// Spread in all of the other properties
...input,
// And add a new transformed array from the `history` property
newHistory: transform(input.history, {newestFirst: true, utc: true}),
// You can even use the sorting function to sort the original history if you want:
// history: [...input.history].sort(createDefaultSortByObjKey('date', {reverse: true})),
};
console.log(result);
// You could have also used the options to get a different result variation:
// transform(input.history, {
// newestFirst: false,
// parseDates: true,
// utc: false,
// });

Related

How do I find the latest time for each date in a JSON array?

I have this array in JSON format:
var result=[
{
"index": 13,
"id": 1122,
*
The approach below:
get a unique list of only the dates from the array i.e. dd/mm/yyyy
for each date in the unique list, create a sorted array per the times for that date
return the 0th item from that sorted array for that date
Example code:
var result = [
{
"index": 13,
"id": 1122,
"price": 100,
"dateTime": "11/12/2020 1:59"
},
{
"index": 14,
"id": 1122,
"price": 300,
"dateTime": "11/12/2020 3:15"
},
{
"index": 15,
"id": 1122,
"price": 314,
"dateTime": "11/13/2020 2:20"
},
{
"index": 16,
"id": 1122,
"price": 280,
"dateTime": "11/13/2020 2:23"
}
];
// get a list of the dates in result
// nothing fancy - the date is just a key
var dates = result.map(k => k.dateTime.substr(0, 10));
// get unique dates from this array
var uniqueDates = Array.from(new Set(dates));
// for each unique date, sort the times descending
// return the first item (latest) for that date
var filtered = uniqueDates.map(ud => {
var dateItems = result.filter(d => d.dateTime.substr(0, 10) == ud);
dateItems.sort((a, b) => (new Date(b.dateTime)).getTime() - (new Date(a.dateTime)).getTime());
return dateItems[0];
});
// output
console.log(filtered);
Sorting the Array on the Date value of dateTime, write a result array with dates-only as keys, retrieve the values of the result.
The TypeError, by the way, is because you should check for i + 1 being smaller than result.length in the loop (this will be falsy for the last element within the loop. In that case new Date(result[i+1].dateTime) will throw the error).
// initialize log helper
const log = Logger();
// create an empty Object
const result = {};
// sort data ascending
const dataSorted = getData().sort((a, b) =>
new Date(a.dateTime) - new Date(b.dateTime));
// add to result with datestring as key.
// The value with the most recent date will be preserved
// because key values are unique (so equal keys are overwritten)
dataSorted.forEach(v => result[new Date(v.dateTime).toDateString()] = v);
// the values of [result] contain the most recent records per date
log(Object.values(result));
// this can also be a one liner, using a reducer method
const resultX = Object.values(
getData()
.sort( (a, b) => new Date(a.dateTime) - new Date(b.dateTime) )
.reduce( (acc, value) =>
({...acc, [new Date(value.dateTime).toDateString()]: value}), {} )
);
log(`\n**from reducer`, resultX);
function getData() {
return [{
"index": 13,
"id": 1122,
"price": 100,
"dateTime": "11/12/2020 1:59"
},
{
"index": 14,
"id": 1122,
"price": 300,
"dateTime": "11/12/2020 3:15"
},
{
"index": 15,
"id": 1122,
"price": 314,
"dateTime": "11/13/2020 2:20"
},
{
"index": 16,
"id": 1122,
"price": 280,
"dateTime": "11/13/2020 2:23"
}
];
}
function Logger() {
const report = document.querySelector("#report") ||
document.body.insertAdjacentElement(
"beforeend",
Object.assign(document.createElement("pre"), {id: "report"}));
return (...args) => args.forEach(stuff =>
report.textContent += (stuff instanceof Object
? JSON.stringify(stuff, null, 2) : stuff) + "\n");
}
You can group the data based on date like,
const groups = data.reduce((groups, currVal) => {
const date = currVal.dateTime.split(' ')[0];
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(currVal);
return groups;
}, {});
-> Here we split the date and and time part using split(' ') and took the date alone separately and form a group,
const date = currVal.dateTime.split(' ')[0];
And then you can get the recent date and time using the method,
groups[item].reduce((a, b) => (a.dateTime > b.dateTime ? a : b));
Working snippet:
const data = [
{
"index": 13,
"id": 1122,
"price": 100,
"dateTime": "11/12/2020 1:59"
},
{
"index": 14,
"id": 1122,
"price": 300,
"dateTime": "11/12/2020 3:15"
},
{
"index": 15,
"id": 1122,
"price": 314,
"dateTime": "11/13/2020 2:20"
},
{
"index": 16,
"id": 1122,
"price": 280,
"dateTime": "11/13/2020 2:23"
}
];
//Group the data based on date
const groups = data.reduce((groups, currVal) => {
const date = currVal.dateTime.split(' ')[0];
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(currVal);
return groups;
}, {});
//Get the recent date and time based on each group
const result = [];
Object.keys(groups).filter(item => {
const newData = groups[item].reduce((a, b) => (a.dateTime > b.dateTime ? a : b));
result.push(newData);
});
console.log(result)

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);

Find the least and max date in array using yyyy-mm-dd format

How can I get the least and max date in an array using YYYY-mm-dd format? Here is a sample of my date values.
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}]
console.log(data);
You can use reduce and Date.parse
const data = [{"date": "2012-10-21","value": 60}, { "date": "2012-10-22","value": 61}, {"date": "2012-10-23","value": 69}, {"date": "2012-10-24","value": 67}]
let maxDate = data.reduce((op,inp)=>{
if(Date.parse(inp.date) > Date.parse(op.max)){
op.max = inp.date
}
if(Date.parse(inp.date) < Date.parse(op.max)){
op.least = inp.date
}
return op
},{least:data[0].date,max:data[0].date})
console.log(maxDate)
If you just want the dates you can use Math.min and Math.max if you map your array to dates using .map:
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}];
const dates = data.map(({date}) => new Date(date));
const minDate = new Date(Math.min(...dates));
const maxDate = new Date(Math.max(...dates));
console.log("min", minDate.toISOString().slice(0,10));
console.log("max", maxDate.toISOString().slice(0,10));
Alternatively, you could sort the array and use the first and last elements:
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}];
const dates = data.map(({date}) => new Date(date));
const sortedDates = dates.sort((a, b) => a - b);
const minDate = sortedDates[0];
const maxDate = sortedDates[sortedDates.length-1];
console.log("min", minDate.toISOString().slice(0,10));
console.log("max", maxDate.toISOString().slice(0,10));
You could reduce the data and take just a string comparison.
const
data = [{ date: "2012-10-21", value: 60 }, { date: "2012-10-22", value: 61 }, { date: "2012-10-23", value: 69 }, { date: "2012-10-24", value: 67 }],
result = data.reduce((r, { date }) => {
if (!r) return { min: date, max: date };
if (r.min > date) r.min = date;
if (r.max < date) r.max = date;
return r;
}, undefined);
console.log(result);
If this is the date format used, a simple .sort() on the date property will work. The earliest date is the first element the array and the last date the last element.
This is the big advantage of using a date format ( like the ISO standard ) where the lexical storting by string value is the same as the logical sorting by date.
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}, {
"date": "2012-10-22",
"value": 102
}];
const sorted_by_date = data.sort(( a, b ) => a.date.localeCompare( b.date ));
const earliest_date = sorted_by_date[ 0 ];
const latest_date = sorted_by_date[ sorted_by_date.length - 1 ];
console.log( earliest_date );
console.log( latest_date );
function checkDate(data,condition){
if(condition == 'max'){
var max = 0
data.map((item)=>{
if(new Date(item.date)>max){
max = new Date(item.date)
}
})
return max;
}else if(condition == 'least'){
var least = new Date()
data.map((item)=>{
if(new Date(item.date)<least){
least = new Date(item.date)
}
})
return least;
}
}

Javascript underscore data format array of object

By using underscoreJS lib and manipulating some datas, i have this object
var data = {
"2017-09-26": [
{
"id": 274281,
"value": 10
},
{
"id": 274282,
"value": 20
}],
"2017-09-27": [
{
"id": 274281,
"value": 12
},
{
"id": 274282,
"value": 13
}],
}
i would like to obtain this result below by passing the keys as date in the child object and transform the value of id key as the new key of the value of value
var data = [{
date:"2017-09-26",
274281: 10,
274282: 20
},
{
date:"2017-09-27",
274281: 12,
274282: 13
}]
Please does someone as an idea to help me to do this and ideally efficiently?
Thanks
Here it is in one line:
Object.keys(data).map(key => ({date: key, ...data[key].reduce((p, c) => {p[c.id] = c.value; return p}, {})}))
Result:
[{
"274281":10,
"274282":20,
"date":"2017-09-26"
},
{
"274281":12,
"274282":13,
"date":"2017-09-27"
}]
You need nested loops. The first level creates the objects with the date property, then you loop over the objects in that value, and add the id: value properties to the result.
var newdata = _.map(data, (date, objects) => {
res = {date: date};
_.each(objects, obj => {
res[obj.id] = obj.value;
});
return res;
});
You can use Array.from() on the result of Object.entries(data) to create an array of objects by passing a callback function as the second argument.
Then for each sub-array, use .reduce() to create a new object from its members.
var data = {
"2017-09-26": [
{ "id": 274281, "value": 10 },
{ "id": 274282, "value": 20 }
],
"2017-09-27": [
{ "id": 274281, "value": 12 },
{ "id": 274282, "value": 13 }
],
};
const result = Array.from(Object.entries(data), ([key, arr]) =>
arr.reduce((res, {id, value}) =>
Object.assign(res, {[id]: value})
, {date: key})
);
console.log(result);
Here's one that's just about the same, but uses the new object literal spread syntax.
var data = {
"2017-09-26": [
{ "id": 274281, "value": 10 },
{ "id": 274282, "value": 20 }
],
"2017-09-27": [
{ "id": 274281, "value": 12 },
{ "id": 274282, "value": 13 }
],
};
const result = Array.from(Object.entries(data), ([key, arr]) =>
({date: key,
...Object.assign({}, ...arr.map(({id, value}) => ({[id]: value})))
})
);
console.log(result);

lodash: aggregating and reducing array of objects based on date

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

Categories

Resources