I receive an array of posts through an API and want to merge the ones with the same "month" and "year" (day is not important), into one object. I looked up for answers but there are just too many foo-bar examples that confuses more than helping. I want to know the cleanest, most elegant way of handling such problems, without getting into call-back hell and nested blocks...
Here is the API response:
0:
{
date: {day: 27, month: 1, year: 2020}
id: 3
}
1:
{
date: {day: 28, month: 1, year: 2020}
id: 4
}
2:
{
date: {day: 31, month: 1, year: 2020}
id: 5
}
3:
{
date: {day: 1, month: 2, year: 2020}
id: 6
}
4:
{
date: {day: 2, month: 2, year: 2020}
id: 7
}
The expected outcome:
0:
result: {month: 1, year: 2020, id:[3,4,5]}
1:
result: {month: 2, year: 2020, id:[6,7]}
One approach would be to use the Array#reduce() method to transform the input array into a dictionary, where each value contains the accumulation of id's for that month and year. Once this dictionary has been built, you could then extract the values of that dictionary to an array via Object#values() to obtain the required output:
let input=[{date:{day:27,month:1,year:2020},id:3},{date:{day:28,month:1,year:2020},id:4},{date:{day:31,month:1,year:2020},id:5},{date:{day:1,month:2,year:2020},id:6},{date:{day:2,month:2,year:2020},id:7}];
/* Convert the dictionary that will be created by reduce to a value array */
var output = Object.values(input.reduce((dict, item) => {
const { date, id } = item;
/* The distinct key for this item based on month/year of date field */
const key = `${date.month}-${date.year}`;
/* Check if dictionary already has an object value for key. This short hand
will only insert a new object value for key, if one does not already exist
in the dictionary */
const value = dict[key] || { month : date.month, year : date.year, id : [] };
/* Add the item id to the dictionary entries id array */
value.id.push(id);
/* Update value object for key */
return { ...dict, [key] : value };
}, {}))
console.log(output);
The idea here is that the dictionary is built using Compound Keys, where the keys are derived from the month and year of the current array item.
When no value exists for the current key, a new value object is inserted to the dictionary for that key:
{ month : date.month, year : date.year, id : [] }
The id of the current array item is then added (accumulated) to the id sub array of the object for that key:
dict[key].id.push(id);
Hope that helps
Here is an alternate approach, if you are not a big fan of Array.reduce and Array.values and also, if you like to consider performance when running the response for a larger data set.
This approach avoids cloning object (or rather non-mutating object) with spread operator i.e {...<anyObject>} while iterating. which should be fine for minimal set of data but but definitely not when you deal with huge volume.
const response = [{
date: { day: 27, month: 1, year: 2020 },
id: 3
}, {
date: { day: 28, month: 1, year: 2020 },
id: 4
}, {
date: { day: 31, month: 1, year: 2020 },
id: 5
},{
date: { day: 1, month: 2, year: 2020 },
id: 6
},{
date: { day: 2, month: 2, year: 2020 },
id: 7
}];
function groupByMonthYear(response) {
// output
const groupedData = []
// Using map for lookup to avoid iterating again on the grouped data
const referenceMap = new Map();
// destructing month, year and id from the response
for (const { date: { month, year }, id } of response) {
const groupKey = `${month}${year}`
// check if the month and year reference is already seen using the groupKey MMYYYY
if (referenceMap.has(groupKey)) {
referenceMap.get(groupKey).id.push(id);
// early return
continue;
}
// simply add a new entry if it doesn't exist
const data = {
month,
year,
id: [id]
};
groupedData.push(data);
referenceMap.set(groupKey, data)
}
return groupedData;
}
// Invoke and Print the result
console.log(groupByMonthYear(response));
Related
So I have an array with a data of
var today = "2020-08-31"
var array = [
{
name: "Joshua",
id: 1,
date: "2020-08-31"
},
{
name: "Michael",
id: 2,
date: "2020-09-1"
}]
I want to create a sectionList that the sectionHeader title will be depending on the date today and will compare it to the date value from the array. so for example the date from the array is "2020-08-31" and today's date is same as "2020-08-31" the title should be "Today" and tomorrow is "2020-09-01" and the date from the array is still "2020-08-31" the title should be "Yesterday" is it possible?? please help me. im stuck with this. Thank you!!!!
Use the parse function from the JS Date library to parse the date hence convert it into long and then return the string (yesterday, today, tomorrow).
Add the displayDate into you array in order to loop through and display the field's value.
const today = "2020-08-31"
let array = [{
name: "Joshua",
id: 1,
date: "2020-08-31"
},
{
name: "Michael",
id: 2,
date: "2020-09-1"
}
]
array = array.map(x => ({
...x,
displayDate: (() => {
if (Date.parse(today) < Date.parse(x.date)) {
return 'yesterday';
} else if (Date.parse(today) > Date.parse(x.date)) {
return 'tomorrow';
}
return 'today';
})()
}));
console.log(array)
I have two arrays of objects:
\\offers
[
{DeskUID: "B11A13", Day: 06 Jun 2020}
{DeskUID: "B11A13", Day: 07 Jun 2020}
{DeskUID: "B12B34", Day: 23 Jun 2020}
]
\\reservations
[
{DeskUID: "B11A13", Day: 06 Jun 2020, Name: "Mike"}
{DeskUID: "B12B34", Day: 23 Jun 2020, Name: "Ali"}
]
I would like to have a result where are available offers, that means only the offers without already reserved desks.
\\result
[
{DeskUID: "B11A13", Day: 07 Jun 2020}
]
How to get the difference between two arrays of objects in JavaScript
I already tried solutions on the link above but without success, I just got a result array as sum of all objects from the two arrays.
function comparer(otherArray){
return function(current){
var reserveDay = new Date (current.Day)
return otherArray.filter(function(other){
var offerDay = new Date (other.Day)
return other.DeskUID == current.DeskUID && offerDay == reserveDay
}).length == 0;
}
}
var onlyInA = offers.filter(comparer(reservations));
var onlyInB = reservations.filter(comparer(offers));
result = onlyInA.concat(onlyInB);
You can do in a single function, like:
const available = offers.filter(offer => {
return reservations.findIndex(reservation => reservation.DeskUID === offer.DeskUID && sameDay(new Date(reservation.Day), new Date(offer.Day))) === -1;
});
function sameDay(d1, d2) {
return (
d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate()
);
}
console.log(available);
So, what is exactly happening...
offers.filter runs a check for each element in the offers array.
reservations.findIndex will try to find if there is already a reservation for that offer. It does by going into the reservations array and checking if there is the same unique ID and same date for the offer that is currently filtering.
If the result is equal to -1 it means that there is no reservation for that offer. Therefore it is available.
I've used the dates as string for the sake of simplicity, you can just alter to Date object. Hope it helps!
EDIT
I've added a small helper function for you to compare if the dates are from the same day. You can check this answer for a more detailed explanation: how to tell if two dates are in the same day?
You could take a Set and filter the reservations.
var getKey = ({ DeskUID, Day }) => [DeskUID, Day].join('|'),
offers = [{ DeskUID: "B11A13", Day: "2020-06-06" }, { DeskUID: "B11A13", Day: "2020-06-07" }, { DeskUID: "B12B34", Day: "2020-06-23" }],
reservations = [{ DeskUID: "B11A13", Day: "2020-06-06", Name: "Mike" }, { DeskUID: "B12B34", Day: "2020-06-23", Name: "Ali" }],
reservationsSet = new Set(reservations.map(getKey)),
open = offers.filter(o => !reservationsSet.has(getKey(o)));
console.log(open);
You can simply filter it with some inside it.
var offers = [{ DeskUID: "B11A13", Day: "2020-06-06" }, { DeskUID: "B11A13", Day: "2020-06-07" }, { DeskUID: "B12B34", Day: "2020-06-23" }];
var reservations = [{ DeskUID: "B11A13", Day: "2020-06-06", Name: "Mike" }, { DeskUID: "B12B34", Day: "2020-06-23", Name: "Ali" }];
var result = offers.filter(k=>!reservations.some(d=>d.DeskUID == k.DeskUID && d.Day==k.Day));
console.log(result);
Question
I have the following multi-dimensional array where slot_date is coming from the database query. I have a button for every student_id. But I need only one button for the closest date.
Output in Ajax call
[
'1': {
slot_date: "2019-01-11"
student_id: 26
}
'2': {
slot_date: "2019-01-21"
student_id: 27
}
'3': {
slot_date: "2019-03-11"
student_id: 28
}
'4': {
slot_date: "2019-03-18"
student_id: 29
}
]
Javascript file
join_button = '<a onclick="studentJoinMeeting()"><button class="ongoing-btn">Join Meeting</button></a>';
Desired Output
I need the id of date 2019-03-11 i.e closest date to current date. I need the join button only on that 2019-03-11 date.
You can sort your array based on the dates, by calculating their absolute distance to today. You can then take the first element.
I modified your data array to be a data object since your are using key/values pairs.
const data = {
'1': {
slot_date: "2019-01-11",
student_id: 26
},
'2': {
slot_date: "2019-01-21",
student_id: 27
},
'3': {
slot_date: "2019-03-11",
student_id: 28
},
'4': {
slot_date: "2019-03-18",
student_id: 29
}
};
const today = Date.now();
// This function returns the absolute difference in ms between 2 dates
const dateAbsoluteDiff = (date1, date2) => {
if (date1 - date2 < 0) return date2 - date1;
else return date1 - date2;
};
// get the entries (array of key/values) of the data, sort them based
// on their date distance to today
// take the first element (the closest), this is a key/value pair
// return the second item from this pair, which is the original object
const closest = Object.entries(data).sort((e1, e2) => {
return dateAbsoluteDiff(new Date(e1[1].slot_date), today) -
dateAbsoluteDiff(new Date(e2[1].slot_date), today);
})[0][1];
console.log(closest);
console.log('Student id:', closest.student_id);
If your input data was instead an array, you can sort it directly:
const data = [{
slot_date: "2019-01-11",
student_id: 26
}, {
slot_date: "2019-01-21",
student_id: 27
}, {
slot_date: "2019-03-11",
student_id: 28
}, {
slot_date: "2019-03-18",
student_id: 29
}];
const closest = data.sort((e1, e2) => {
return dateAbsoluteDiff(new Date(e1.slot_date), today) -
dateAbsoluteDiff(new Date(e2.slot_date), today);
})[0];
If instead you want to get the next closest date, then don't take the absolute difference, map your array to an array containing the differences, then sort them and find the first difference greater or equal to 0.
const data = [{
slot_date: "2019-01-11",
student_id: 26
}, {
slot_date: "2019-01-21",
student_id: 27
}, {
slot_date: "2019-03-11",
student_id: 28
}, {
slot_date: "2019-03-18",
student_id: 29
}];
const today = Date.now();
const nextDate = data
.map(({ slot_date }) => ({ slot_date, diff: new Date(slot_date) - today }))
.sort((e1, e2) => e1.diff - e2.diff)
.find(date => date.diff >= 0)
console.log(nextDate.slot_date)
You given array is not valid. Arrays cannot have keys. You cant write key:value in [] use {} for that. Here is example with array.
And closest date to current date will the highest date and it will be 2019-03-18
let arr = [{slot_date: "2019-01-11",student_id: 26},
{slot_date: "2019-01-21",student_id: 27},
{slot_date: "2019-03-11",student_id: 28},
{slot_date: "2019-03-18",student_id: 29},
]
let maxDate = 0;
let maxDateID;
for(let item of arr){
if(new Date(item.slot_date) > maxDate){
maxDate = new Date(item.slot_date);
maxDateID = item.student_id;
}
}
I am new to D3 and need to design a heatmap using D3. I had the data for 20 years and required to show the max and min for every month. The question is different month has different days and for February there is 28 days or 29 days. Can anybody help me?
Sorry for my unclear description.This is the part of the data.enter image description here I need to use the max and min value of each month to draw a heatmap. The data is from 1997-01-01 to 2007-12-31.
You can sort this out using underscorejs and d3.extent.
For Example your list is like,
data = [{
month: 1,
value: 10
}, {
month: 1,
value: 20
}, {
month: 2,
value: 30
}, {
month: 2,
value: 40
}];
You underscore groupBy method like,
result = _.groupBy(data, 'month');
Your result will be,
result = {
1: [{
month: 1,
value: 10
}, {
month: 1,
value: 20
}],
2: [{
month: 2,
value: 30
}, {
month: 2,
value: 40
}]
};
Then you can plot max and min values using d3.extent.
I have an array of objects that contain a date and an amount (among other things).
There is an object for each date with a specific amount, but there can also be multiple objects for the same date containing different amounts.
I'd like to consolidate the objects so I only have one object in the array for each date ... and have the amount corresponding to that date be the total sum of all previous amounts in those objects.
Examples will probably help here:
What my array looks like now:
[
{
date: "2019-1-1", // this is a dupe date
amount: 20,
...
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
What I would like my array to look like:
[
{
date: "2019-1-1", // this is now a unique date
amount: 60, // and the amount was totaled
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
Use .reduce to reduce an array into an object (or into anything else) by iterating over its properties. You just need to test to see if an object with a matching date already exists in the accumulator first:
const input = [
{
date: "2019-1-1", // this is a dupe date
amount: 20,
foo: 'bar',
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
foo: 'bar',
},
{
date: "2019-1-2",
amount: 40,
foo: 'bar',
},
{
date: "2019-1-3",
amount: 40,
foo: 'bar',
}
];
const output = input.reduce((accum, item) => {
const { date, amount } = item;
const foundObj = accum.find(({ date: findDate }) => findDate === date);
if (foundObj) {
foundObj.amount += amount;
return accum;
}
accum.push(item);
return accum;
}, []);
console.log(output);
You may do as follows;
var data = [ { date: "2019-1-1", // this is a dupe date
amount: 20},
{ date: "2019-1-1", // this is a dupe date
amount: 40},
{ date: "2019-1-2",
amount: 40},
{ date: "2019-1-3",
amount: 40}
],
result = Object.values(data.reduce((r,d) => r[d.date] ? (r[d.date].amount += d.amount, r)
: (r[d.date] = d, r), {}));
console.log(result);
Regarding a comment i guess i have to explain this a little for those who may not be familiar with some ES6 functionalities.
Object.values() is a Object method which returns all own property values in an array.
So we are reducing our objects into an hash object of which we collect the properties by Object.values() later. While reducing we check if the currently examined object's date value exists as key in our map. If not we create that key and insert the examined object at that key position, if yes then we increment the previously inserted objects amount property by the value of currently examined objects amount value.
If you don't want to mutate the original data then please change r[d.date] = d into r[d.date] = Object.assign({},d).
The way I would do it is to create an object with the dates as the key, then you can iterate over the array and create a new date property if it doesn't exist or increase the amount if it does, then convert it back into an array:
const items = data.reduce((acc, curr) => {
if (!acc[curr.date]) { // basically creating a property with the date as the key and the value is the current object
acc[curr.date] = { ...curr };
} else { // if it exists already, then just increment the amount
acc[curr.date].amount += curr.amount;
}
return acc;
}, {});
const newArray = Object.values(items); // grab all the values from the object above