I am trying to find a logic in sorting some arrays, but after hours of brainstorming it's just beyond me and probably you have an idea
Let's say we have an array of objects.
The Objects looks like this:
let obj = {day:'2',month:'6',year:'1938' }
and the array will look like this
let array = [ {year:'1938, month:'6', day:'3'},{year:'1935', month:'5', day:'3'},{year:'1935, month:'', day:''}, {year:'1935', month:'5', day:''}, {year:'1934}, month:'3', day:''}, {year:'1934', month:'3', day:'15'}, {year:'1934}, month:'', day:''}];
and I want that the sorted array would look something like
let sortedArray = [{year:'1934}, month:'', day:''},{year:'1934}, month:'3', day:''},{year:'1934}, month:'3', day:'15'},{year:'1935, month:'', day:''},{year:'1935', month:'5', day:''},{year:'1935', month:'5', day:'3'},{year:'1938, month:'6', day:'3'}
The fields for year, month and day are not required because in my app probably someone know an estimate year when something happened and it's not mandatory, but when I display all the objects and I want them to start with all the objects that only have the year (sorted), then all the objects that have the year and the month (sorted by year and month), and then all the objects that have all 3. But also sorted chronologically. What I am building is a timeline. Also you cannot add only year and day and year is mandatory.
I tried building 3 arrays which: 1 which holds the objects with only years, then 1 which hold years and months, and one with all 3 of them.
something like
let array = [ {year:'1938, month:'6', day:'3'},{year:'1935', month:'5', day:'3'}, {year:'1935, month:'', day:''}, {year:'1935', month:'5', day:''}, {year:'1934}, month:'3', day:''}, {year:'1934', month:'3', day:'15'}, {year:'1934}, month:'', day:''}];
let array1 = [];
let array2 = [];
let array3 = [];
array.forEach((element) => {
if(element.month === '' && element.day ==='')
array1.push(element)
if(element.month !== '' && element.day ==='')
array2.push(element)
if(element.month !== '' && element.day !=='')
array3.push(element)
});
// I sorted array1 because they hold the years;
array1.sort(function(a,b) {
if(Number(a.year)>Number(b.year)) return 1;
if(Number(a.year)<Number(b.year)) return -1;
return 0;
});
let currentYear;
let currentYearIndex;
for(let i = 0;i< array1.length; i++} {
if(array1[i] !== array1[i+1] {
currentYear = array[i].year;
currentYearPosition=i}
}
for(let j = 0;j< array2.length; j++} {
if(array2[j] === currentYear){}
}
and here my logic died because I had to do another if to check if the month is less or greater that what we already have in the first array but we didn't at the beginning. I don't know if I tried to reinvent the wheel or not. If exists a solution to sort my array that way I would appreciate your help. Thank you!
You could try filter to groups and then converting them each to a date, and then perform the sort on each group.
There is scope to improve this code.
const array = [
{year:'1938', month:'6', day:'3'},
{year:'1935', month:'5', day:'3'},
{year:'1935', month:'', day:''},
{year:'1935', month:'5', day:''},
{year:'1934', month:'3', day:''},
{year:'1934', month:'3', day:'15'},
{year:'1934', month:'', day:''}
];
const yearSort = array => {
return array.filter(item => item.year && !item.month && !item.day).map(item => {
return {
item,
date: new Date(parseInt(item.year),0,1)
}
}).sort((a,b) => a.date < b.date ? -1 : 1)
.map(i => i.item)
}
const yearMonthSort = array => {
return array.filter(item => item.year && item.month && !item.day).map(item => {
return {
item,
date: new Date(parseInt(item.year),item.month - 1,1)
}
}).sort((a,b) => a.date < b.date ? -1 : 1)
.map(i => i.item)
}
const yearMonthDaySort = array => {
return array.filter(item => item.year && item.month && item.day).map(item => {
return {
item,
date: new Date(parseInt(item.year),item.month - 1,parseInt(item.day))
}
}).sort((a,b) => a.date < b.date ? -1 : 1)
.map(i => i.item)
}
const sorted = yearSort(array)
.concat(yearMonthSort(array))
.concat(yearMonthDaySort(array))
console.log({ sorted });
In your sort compare function, first check year, when same check month and when same check for day.
Convert all values to number and compare.
'' will be 0 (when converting to number from string using + operator)
const array = [
{ year: "1938", month: "6", day: "3" },
{ year: "1935", month: "5", day: "3" },
{ year: "1935", month: "", day: "" },
{ year: "1935", month: "5", day: "" },
{ year: "1934", month: "3", day: "" },
{ year: "1934", month: "3", day: "15" },
{ year: "1934", month: "", day: "" },
];
array.sort((a, b) => {
if (a.year === b.year) {
if (a.month === b.month) {
return +a.day - +b.day;
}
return +a.month - +b.month;
}
return +a.year - +b.year;
});
console.log(array);
Related
I have an array of objects with a date value. I want to filter the array based on the selectedDate and get the Max date in the list of dates. In the below code, I am filtering the array based on the month. Here I get 3 values after filtering, now I want to compare those values and get the MAX Date() value.
How can I do that in Angular or ES6 way?
let selectedDate = new Date();
let array = [{
"date": "2022-08-30T23:00:00Z",
"value": "4.0"
},
{
"date": "2022-08-28T23:00:00Z",
"value": "8.0"
},
{
"date": "2022-08-25T23:00:00Z",
"value": "2.0"
},
{
"date": "2022-07-25T23:00:00Z",
"value": "2.0"
}
];
let x = array.filter(d =>
new Date(d.date).getMonth() === selectedDate.getMonth() - 1
)
console.log(x)
Expected Output:
{
"date": "2022-08-30T23:00:00Z",
"value": "4.0"
}
You need to filter not only by month, but also by year.
Please do not use new in the loop, if possible.
const array = [{"date": "2022-08-30T23:00:00Z","value": "4.0"},{"date": "2022-08-28T23:00:00Z","value": "8.0"},{"date": "2022-08-25T23:00:00Z","value": "2.0"},{"date": "2022-07-25T23:00:00Z","value": "2.0"}];
const selectedDate = '2022-09-01T06:08:58.695Z' // new Date().toISOString();
const getYearMonth = (isoDateTime) => isoDateTime.slice(0,7)
const getMax = (data, targetDate) => {
const targetYearMonth = getYearMonth(targetDate);
const filtered = data.filter(({ date }) => getYearMonth(date) === targetYearMonth);
if (filtered.length === 0) return null;
if (filtered.length === 1) return filtered.at(0);
return filtered.reduce((max, cur) => max.date.localeCompare(cur.date) < 0 ? cur : max)
};
console.log(getMax(array, '2022-07-01T06:08:58.695Z'))
console.log(getMax(array, '2022-08-01T06:08:58.695Z'))
console.log(getMax(array, '2022-09-01T06:08:58.695Z'))
.as-console-wrapper { max-height: 100% !important; top: 0 }
let yourOutput = [
{
"date": "2022-08-30T23:00:00Z",
"value": "4.0"
},
{
"date": "2022-08-28T23:00:00Z",
"value": "8.0"
},
{
"date": "2022-08-25T23:00:00Z",
"value": "2.0"
}
];
//Sort by Date
yourOutput.sort((a, b) => new Date(a) > new Date(b));
//Get First Elem
if(yourOutput.length > 0) {
console.log(yourOutput[0])
}
I think you can use reduce function afterfilter to get the max.
Assuming we have 2 variables, selectedDate and array:
let max = array
.filter(d =>
new Date(d.date).getMonth() === selectedDate.getMonth() - 1
)
.reduce((max, current) => {
if (!max) return current;
let maxDate = new Date(max.date);
let currentDate = new Date(current.date);
return maxDate > currentDate? max: current;
}, null);
I have the following table data:
date
value
01/01/2000
1
01/02/2000
2
01/01/2001
2
01/01/2002
1.5
01/02/2002
1.6
[{date: "01/01/2000", value: "1"},{date: "01/02/2000", value: "2"},{date: "01/01/2001", value: "2"},{date: "01/01/2002", value: "1.5"},{date: "01/02/2002", value: "1.6"}]
I would like to convert it to columns:
Year
Jan
Feb
2000
1
2
2001
2
2002
1.5
1.6
[{Year: "2000", Jan: "1", Feb: "2"},
{Year: "2001", Jan: "", Feb: "2"},
{Year: "2002", Jan: "1.5", Feb: "1.6"}]
How can I change it using RxJS / TypeScript / JavaScipt in Angular?
Thanks.
See below approach using reduce
const initial = [{date: "01/01/2000", value: "1"},{date: "01/02/2000", value: "2"},{date: "01/01/2001", value: "2"},{date: "01/01/2002", value: "1.5"},{date: "01/02/2002", value: "1.6"}]
const allMonths = initial.reduce((prev, next) => {
const date = next.date.substr(6,4) + '/' + next.date.substr(3,2)
const month = new Date(date).toLocaleString('default', { month: 'short' });
return {...prev, [month]: ''}
}, {})
const temp = initial.reduce(
(prev, next) => {
const date = next.date.substr(6,4) + '/' + next.date.substr(3,2)
const month = new Date(date).toLocaleString('default', { month: 'short' });
const Year = new Date(date).getFullYear()
let prevYearVal = prev[Year]
if(!prevYearVal) { prevYearVal = {Year,...allMonths} ;}
return {...prev, [Year]: {...prevYearVal,Year, [month]: next.value}}
return prev
},
{}
)
const final = Object.values(temp)
console.log(final)
This requires a number of steps to complete the transformation.
Convert string date into usable JavaScript date object
because the date is mm/dd/YYYY, rather then dd/mm/YYYY it must be transposed
then a new Date is constructed using the decomposed string
Then it is just a matter of constructing the new object
The output from that is each date with corresponding values that must be combined
The second iterator (.reduce()) combines the objects if the Year properly match
const input = [{date: "01/01/2000", value: "1"},{date: "01/02/2000", value: "2"},{date: "01/01/2001", value: "2"},{date: "01/01/2002", value: "1.5"},{date: "01/02/2002", value: "1.6"}];
const result = input.map(i => {
const [day, month, year] = i.date.split('/');
const [_, monthName] = new Date(year, month - 1, day).toDateString().split(' ');
return {
Year: year,
[monthName]: i.value
}
}).reduce((acc, d, idx) => {
if (idx == 0) {
acc.push(d);
} else if (acc[acc.length - 1].Year == d.Year) {
acc[acc.length - 1] = Object.assign(acc[acc.length-1], d);
} else {
acc.push(d);
}
return acc;
}, []);
console.log(result);
Here is a version that works similarly to answers from Owen and Randy, but it separates the month-name processing into its own helper function. Written independently, the local variable names are all different, but they do the same work. It is also structured as a single function call rather than a set of steps:
const monthName = ((months) => (m) => months [m - 1])(
'01|02|03|04|05|06|07|08|09|10|11|12' .split ('|') .map (
m => new Date (`2021/${m}`).toLocaleString('default', {month: 'short'})
)
)
const transform = (xs) => {
const base = Object .fromEntries (
[...new Set(input .map (
({date}) => date .slice (3, 5)
))]
.map (month => [monthName (month), ""])
)
return Object .values (xs .reduce ((years, {date, value}) => {
const Year = date .slice (6, 10),
Month = date.slice (3, 5)
years [Year] = years [Year] || {Year, ...base}
years [Year] [monthName(Month)] = value
return years
}, {}))
}
const input = [{date: "01/01/2000", value: "1"}, {date: "01/02/2000", value: "2"}, {date: "01/01/2001", value: "2"}, {date: "01/01/2002", value: "1.5"}, {date: "01/02/2002", value: "1.6"}]
console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
One advantage is that it doesn't call the Date constructor for every object, simply calling it once for every calendar month. And if you don't want the locale string version but a fixed set of month names the helper can be even simpler:
const monthName = ((months) => (m) => months [m - 1]) (
'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec' .split ('|')
)
I tend to prefer functions written purely as expressions and not statement, so an alternate version might look like this:
const transform = (
xs,
base = Object .fromEntries (
[...new Set (xs .map (
({date}) => date .slice (3, 5)
))]
.map (month => [monthName (month), ""])
)
) => Object .values (
xs .reduce ((a, {date, value}, _, __,
Year = date .slice (6, 10), Month = date.slice (3, 5)
) => ({
...a,
[Year]: {Year, ...(a [Year] || base), [monthName (Month)]: value}
}), {})
)
It works the same way, and is somewhat less efficient, but I find it cleaner.
I am trying to take this array :
[
{
"date": a timestamp: June 20, 2020 at 7:32:42 PM UTC
"value": 3
..
}
..
]
and accomplish 3 things effeciently
Convert the timestamp to normal date and replace with the timestamp
Merge dates of the same day. ( so i add their values and set under a single day.
Sort the array when recent are first.
I have started with something like this :
array.sort(function(a,b){ return new Date(b.date) - new Date(a.date);
var salesDates = sales.map(function(element){element.date = new Date(element.date); return element }); });
Which will only sort, but i need to replace timestamp/date, sort and merge same dates elegantly and effeciently
Is it possible with only sort function ?
Here. First i group it with .reduce(). Then i sort it .sort(). After that i change the timestamp to a date format.
let arr = [
{
"date": 1594023899426,
"value": 3
},
{
"date": 1592423499234,
"value": 2
},
{
"date": 1594023899426,
"value": 1
}
];
let result = arr
.reduce((a, v) => {
let index = a.findIndex(el => el.date === v.date);
if (index !== -1) {
a[index].value += v.value;
return a;
}
a.push({
date: v.date,
value: v.value
});
return a;
}, [])
.sort((a, b) => b.date - a.date)
.map(el => {
el.date = new Date(el.date);
return el;
});
console.log(result);
Here's another approach to it:
let sortingReducer = (accumulator, obj) => {
// This is the merging logic
let existingObj = accumulator.find(
(compareObj) => {
return obj.date?.getDate() === compareObj.date?.getDate()
}
);
if (existingObj) {
existingObj.value += obj.value;
return accumulator;
}
// This is the sorting logic
const nextIndex = accumulator.findIndex(
(compareObj) => obj.date?.getTime() < compareObj.date?.getTime()
);
const index = nextIndex > -1 ? nextIndex : accumulator.length;
accumulator.splice(index, 0, obj);
return accumulator;
};
const input = [
{
date: new Date(),
value: 2,
},
{
date: new Date(new Date().setDate(1)),
value: 4,
},
{
date: new Date(new Date().setDate(1)),
value: 1,
},
{
date: new Date(new Date().setDate(2)),
value: 7,
},
];
const output = input.reduce(sortingReducer, []);
I've take some help from this answer: https://stackoverflow.com/a/50246275/1912288
I like the approach in the above answer, this is just a different approach to it.
I need some help. I need to calculate the amount of user actions in each month of current year. I have an array of dates:
let years = ['2017', '2018', '2019']
let datesArray = [
{date: "2019-06-05", userActionsAmount: 88},
{date: "2019-06-04", userActionsAmount: 314}
]
and I have the count object
let counts = {}
then I iterate through this like that:
years.forEach(year => {
counts[year] = datesArray.filter(singleDay => singleDay.date.slice(0, -6) === year).reduce((acc, obj) => {
return acc + obj.userActionsAmount
}, 0)
})
with this code result of counts is:
{2017: 0, 2018: 0, 2019: 402} which is ok, but I need to break the date in to months, so I need something like this:
{ 2017: []},
{ 2018: []}
{ 2019: [
{ '01': 0 },
{ '02': 0 },
{ '03': 0 },
{ '04': 0 },
{ '05': 0 },
{ '06': 402 },
{ '07': 0 },
{ '08': 0 },
{ '09': 0 },
{ '10': 0 },
{ '11': 0 },
{ '12': 0 }
]}
you can do it like this:
let datesArray = [
{date: "2019-06-05", userActionsAmount: 88},
{date: "2019-06-04", userActionsAmount: 314}
]
let result={};
datesArray.forEach(dateItem=>{
let date=dateItem.date.split("-");
let year=date[0];
let month=date[1];
if(!result[year])
result[year]={};
if(!result[year][month])
result[year][month]=0;
result[year][month]+=dateItem.userActionsAmount;
})
That's basically a very simple grouping
const datesArray = [
{date: "2019-06-05", userActionsAmount: 88},
{date: "2019-06-04", userActionsAmount: 314}
];
const groupedByMonth = datesArray.reduce((a, b) => a.set(b.date.substring(0,7), ~~a.get(b.date.substring(0,7)) + b.userActionsAmount), new Map);
console.log([...groupedByMonth]);
To get it to your format, you could do something like
const yourFormat = years.map(e => ({
[e]: Array.from(groupedByMonth).filter(([k, v]) => k.substring(0,4) === e).map(([k, v]) => ({[k.substring(5,7)]: v}))
}));
then
You could create properties when needed.
Here are two solutions : one with array methods and second more explicit.
Initialization :
const monthsKeys = ["01", "02", "03","04", "05", "06", "07", "08", "09", "10", "11", "12"];
const years = ['2017', '2018', '2019'];
const datesArray = [
{date: "2019-06-05", userActionsAmount: 88},
{date: "2019-06-04", userActionsAmount: 314}
];
const counts = {};
Solution 1 :
years.forEach( y => { counts[y] = []; });
datesArray.forEach(dateCount => {
const [year, month, day] = dateCount.date.split("-");
if (counts[year].length === 0) monthsKeys.forEach(m => {counts[year].push({[m] : 0});});
counts[year][Number(month) - 1][month] += dateCount.userActionsAmount;
});
console.log(counts);
Solution 2 :
// fill counts with years
for (const y of years) {
counts[y] = [];
}
// fill counts with months and count
for (const e of datesArray) {
const splittedDate = e.date.split("-");
const year = splittedDate[0];
const month = splittedDate[1];
// create year if needed, not necessary if years array is sure
if ( ! year in counts) {
counts[year] = [];
}
// create monthes if needed
if (counts[year].length === 0) {
for (const m of monthsKeys) {
counts[year].push({[m]: 0});
}
}
// add value
counts[year][Number(month) - 1][month] += e.userActionsAmount;
}
console.log(counts)
Why an array of objects for year values (months counts) and not simply an object?
This solution has some varation from the OP's expected output, but I believe that it should fit OP's requirements. If not, it's about a step to get the output as desired...
const years = [2017, 2018, 2019]
const dates = [{
date: "2019-06-05",
userActionAmount: 88
},
{
date: "2019-06-04",
userActionAmount: 314
}
]
const transform = (years, dates) => dates.reduce(
(output, {
date,
userActionAmount,
parsedDate = new Date(date),
year = parsedDate.getFullYear(),
month = parsedDate.getMonth() + 1,
yearData = output[year]
}) =>
(yearData[month] += userActionAmount) && output,
Object.fromEntries(years.map(year => [year, Object.fromEntries(Array.from({
length: 12
}, (_, x) => [x + 1, 0]))])))
const output = transform(years, dates)
console.log(output)
// This output lets you get total amount
// of some given month in the following way:
const monthAmount = output[2019][6]
console.log (monthAmount)
I have a json file with multiple transactions with a date and a price attribute. Now I want to compare the dates and if they are in the same month and year I want to sum up the prices.
JSON:
transactions: [
{
date: "2017-11-17",
price: "28",
},
{
...
}
JavaScript:
request.onload = function() {
for(const transaction of request.response.transactions) {
let year = new Date(transaction.date).getFullYear();
let month = new Date(transaction.date).getMonth();
console.log(year + ' ' + month); // output: 2017-11 ...
}
};
I tried to loop over the json object but I struggle to find a solution to compare the dates.
Edit: Edited example with Object.assign instead of Object spread.
You'll need to use reduce to sum the prices. See comments for details.
const transactions = [{
date: "2017-11-17",
price: "28",
},
{
date: "2017-12-17",
price: "23",
},
{
date: "2017-11-17",
price: "12",
},
{
date: "2017-10-17",
price: "55",
},
{
date: "2017-11-17",
price: "09",
},
];
const sumTransactions = (transactions) => {
const summed = transactions.reduce((acc, current) => {
// Get the current date object
const date = new Date(current.date);
// Create your key/identifier
const key = `${date.getFullYear()}-${date.getMonth() + 1}`;
// Retreive the previous price from the accumulator
const previousPrice = acc[key]; // Might also return undefined
// Create your temp current price value, and be sure to deal with numbers.
let currentPrice = Number(current.price);
// If you had a previous value (and not undefined)
if (previousPrice) {
// Add it to our value
currentPrice += Number(previousPrice);
}
// Return the future accumulator value
return Object.assign(acc, {
[key]: currentPrice, // new values will overwrite same old values
})
}, {})
// Once we have all values, get the dates, and sort them (default: earlier first)
// Return an array of each value from the summed object to our sortedArray
const sortedArray = Object.keys(summed).sort().map((val) => {
return summed[val];
});
console.log("sortedArray", sortedArray);
};
sumTransactions(transactions);
I experimented a bit and came up with this solution:
var transactions = [
{
date: "2017-11-17",
price: "28",
},
{
date: "2017-12-17",
price: "22",
},
{
date: "2017-12-17",
price: "20",
}
]
var sumedUpDates = [];
var prices = [];
function isDateSumedUp(date) {
return sumedUpDates.indexOf(date.substring(0, 7)) !== -1;
}
function sumUpDate(date) {
var sum = 0;
transactions.forEach(t => {
if(t.date.substring(0, 7) === date.substring(0, 7)) {
sum += parseInt(t.price);
}
});
sumedUpDates.push(date.substring(0, 7));
prices.push(sum);
}
transactions.forEach(t => {
if(!isDateSumedUp(t.date)) {
sumUpDate(t.date);
}
});
var obj = {};
sumedUpDates.forEach((d, i) => obj[d] = prices[i]);
console.log(obj);
This solutions uses map to format your dates into year/month format for each object entry and then reduce to sum them by those separated dates.
const transactions = [
{date:"2017-11-17", price: "28",},
{date:"2017-12-17", price: "28",},
{date:"2017-11-17", price: "20",},
{date:"2017-12-17", price: "2",},
{date:"2017-11-17", price: "58",},
{date:"2017-11-17", price: "8",},
{date:"2017-10-17", price: "30",},
{date:"2018-11-17", price: "1",},
];
const mapper = single => {
let d = single.date.split('-');
let p = Number(single.price);
return { year: d[0], month: d[1], price: p };
}
const reducer = (group, current) => {
let i = group.findIndex(single => (single.year == current.year && single.month == current.month));
if (i == -1) {
return [ ...group, current ];
}
group[i].price += current.price;
return group;
};
const sumPrices = transactions.map(mapper).reduce(reducer, []);
console.log(sumPrices);
var array = [];
for (var i = 0; i < transactions.length; i++) {
var date = new Date(transactions[i].date);
var ym = date.getFullYear() + "-" + date.getMonth();
if (array[ym] == null) {
array[ym] = 0;
}
array[ym] += parseInt(transactions[i].price);
}
With this data
var transactions = [{
date: "2017-11-17",
price: "28",
},
{
date: "2017-12-17",
price: "5",
},
{
date: "2016-02-17",
price: "28",
},
{
date: "2015-11-17",
price: "25",
},
{
date: "2016-02-17",
price: "12",
},
{
date: "2017-11-17",
price: "50",
}
];
This will give you the sum of all of the year-months duplicates like this :
[
2017-10: 78,
2017-11: 5,
2016-1: 40,
2015-10: 25
]
Another solution is reduce:
var transactions = [
{date: "2017-11-17",price: "28"},
{date: "2017-12-17",price: "22"},
{date: "2017-12-17",price: "20"}
];
var result = transactions.reduce(function(acc, obj) {
var key = obj.date.substr(0,7);
acc[key] = (acc[key] || 0) + +obj.price;
return acc;
}, Object.create(null));
console.log(result);