Javascript loop through array recursively - javascript

I've run into a bit of an issue trying to map a list of items in javascript. I have the following array that I'm trying to map:
[{"project_id":"EGNL1701","title":"Test Energy Project",
"reservations":
[{"start_time":"1519887600000"},{"start_time":"1519891200000"},
{"start_time":"1519938000000"},{"start_time":"1519898400000"},
{"start_time":"1519902000000"},{"start_time":"1519905600000"},
{"start_time":"1519909200000"},{"start_time":"1529683200000"},
{"start_time":"1529686800000"},{"start_time":"1531893600000"},
{"start_time":"1531897200000"},{"start_time":"1531900800000"},
{"start_time":"1531904400000"}]},
{"project_id":"LENL1701","title":"Vive","reservations":[]}]
Basically it's a list of reservations per project. All the times are in unix code in miliseconds. I have no issue trying to convert the times to the right time, however what I do need is to check for reservation blocks of consecutive hours. A reservation is always an hour, so I want to map reservations that are directly after the previous reservation, to be included into the previous reservation so that it forms 1 reservation of multiple hours. So if the list would contain 3 reservations for 10-07-2018 at 10, 11 and 12 o'clock then it should combine those in one object.
The new array should look like this:
[
{
title: 'Energy Project',
startTime: 1530631437,
endTime: 1530638640
},
{
title: 'HTC VIVE',
startTime: 1530794845,
endTime: 1530797390
}
];
I'm not sure what the best way is to go about this, I was trying to mess with while and for loops to get it done, but I keep getting stuck in an infinite loop.
Here's the code I have now for checking if the next reservation is an hour after the current reservation in the loop:
while (moment.duration(getUnixTime(reservations[index + nextIndex].start_time).diff(getUnixTime(reservation.start_time))).asHours() === 1) {
nextIndex++;
}
I was hoping one of you might have a good idea on how to do this? I feel like I'm going about this all wrong. It's for a React project, I can use ES6 functions etc.

This might not be complete answer so try this
Create an empty reservations object R
Loop through (for each or for ) upto second last item. You can check if next item exists or not.
Check if the criteria for example next one is one hour after the current one then add them together and add it to R.
End time of R will become end time of current reservation or the one next to it.
Move to next one or skip based on what you did in previous step.
When you reach last item simply add it to R if its not being added in previous step.
One of the assumption is that all reservations are in order. You can order them via sorting functions to avoid loop and check where it fits.

I've fixed the issue using #Farrukh Subhani's suggestion.
First had to sort the list, then loop through it twice. Resulting code:
export const mapReservationTimes = reservations => {
const upcomingReservations = [];
reservations.sort((x, y) => {
return getUnixTime(x.start_time).toDate() - getUnixTime(y.start_time).toDate();
});
for (let index = 0; index < reservations.length; index++) {
let newReservation = { startTime: '', endTime: '' };
let reservation = reservations[index];
newReservation.startTime = getUnixTime(reservation.start_time).format('DD-MM-YYYY HH:MM');
newReservation.endTime = getUnixTime(reservation.start_time)
.add(1, 'hours')
.format('DD-MM-YYYY HH:MM');
for (let i = index; i < reservations.length - 1; i++) {
reservation = reservations[index];
const nextReservation = reservations[i + 1];
if (
moment
.duration(
getUnixTime(nextReservation.start_time).diff(
getUnixTime(reservation.start_time)
)
)
.asHours() === 1
) {
index++;
newReservation.endTime = getUnixTime(nextReservation.start_time)
.add(1, 'hours')
.format('DD-MM-YYYY HH:MM');
} else {
break;
}
}
upcomingReservations.push(newReservation);
}
return upcomingReservations;
};
Thanks a lot for the suggestion, it got me on the right track :)

I feel this might be a good fit for an array.reduce, though this might stem from my personal taste. This solution can and should be simplified and cleaned, but I tried to convey the general idea so I made it a little more verbose then I think production code should be.
const input = [{"project_id":"EGNL1701","title":"Test Energy Project","reservations":
[{"start_time":"1519887600000"},{"start_time":"1519891200000"},
{"start_time":"1519938000000"},{"start_time":"1519898400000"},
{"start_time":"1519902000000"},{"start_time":"1519905600000"},
{"start_time":"1519909200000"},{"start_time":"1529683200000"},
{"start_time":"1529686800000"},{"start_time":"1531893600000"},
{"start_time":"1531897200000"},{"start_time":"1531900800000"},
{"start_time":"1531904400000"}]},
{"project_id":"LENL1701","title":"Vive","reservations":[]}]
const hourInMilliseconds = 3600000
console.log(input.reduce((acc, project) => {
const title = project.title
const sortedReservations = project.reservations.map(r => r.start_time).sort()
const reservationCount = sortedReservations.length
const earliest = reservationCount > 0 ? sortedReservations[0] : "none"
const latest = reservationCount > 0 ? sortedReservations[reservationCount - 1] : "none"
const projectObject = {
title,
startTime: earliest,
endTime: isNaN(latest) ? latest : (Number(latest) + hourInMilliseconds).toString()
}
acc.push(projectObject)
return acc
}, [])
)
Disclaimer: This assumes that all the reservations in one array item are consecutive, which seems to be the case in your sample data. If this is not the case the function would have to be altered a bit.

Related

Removing items from an array in both directions

I have an array of times (these are not constant) [1:00, 2:00, 3:00, 4:00, 5:00, 6:00, 7:00]. The goal is to calculate times that can be bookable. If there is an existing booking from 4:00 - 5:00 then 3:00 should be unavailable as it would overlap the existing booking. To calculate this, I have a function that tells us the start and end indexes of the booking, I need to find a way to remove x times from behind the start index.
To make it more clear, I drew a diagram.
Doing this calculation will allow to calculate available times no matter how long the existing booking is. I'm trying to figure out how to create a function that does what I described. Below is the code I have to return the available times based on the start/end index provided however I'm stuck on how to approach the calculation I described above.
// This filters times that are less than the start index
const filteredTimes1 = availableHours.filter((item, index) => index < (
(startTimeIndex || availableHours.length - 0)
))
// This filters times that greater than the end index
const filteredTimes2 = availableHours.filter((item, index) =>
index > (endTimeIndex || availableHours.length)
)
// Then combine the results into an array
const validAvailableHours = [...filteredTimes1 , ...filteredTimes2]
Any help would be appreciated, thanks.
You're way overcomplicating this.
Just keep a map of the times already booked, in this case 4pm.
Later add 3pm since you're adding that too.
Or if you want it simpler just use a Set.
const bookedHours = new Set()
// too add booked times
bookedHours.add(4)
// to get an array with available times
const available = [1,2,3,4,5,23,24]
const availableUpdate = available.filter(n => !bookedHours.has(n))
I mean you could simply use array.slice() to do the trick.
let allHours = [1, 2, 3, 4, 5, 6, 7]
let [startHours, endHours] = [4, 5];
let duration = 1;
let availableHours = [
...allHours.slice(
0,
allHours.indexOf(startHours - duration)
),
...allHours.slice(
allHours.indexOf(endHours + 1)
)]
console.log(availableHours)
You alreay have a couple good answers. For fun, an alternate use I like to take for more advanced situations like a schedule etc.. make a schedule object and give it all the logic it needs to control the entire schedule.I added a few more features than you requested for fun, such as add time, remove time and set all available times.
const schedule = {
availHours: [],
responses: ['Appointment Confirmed', 'Time Not available, please choose
another time'],
set addHour(hour) {
this.availHours = [...schedule.availHours, hour];
},
set removeHour(hour) {
this.availHours = this.availHours.filter((availHours) => availHours !=
hour);
},
set setHours(hoursArray) {this.availHours = hoursArray},
set request(reqHour) {
this.availHours.includes(reqHour) ? console.log(this.responses[0]) :
console.log(this.responses[1]);
this.availHours = this.availHours.filter(
(hour) => hour != reqHour && hour != Number(reqHour) + 1 && hour !=
Number(reqHour) - 1
);
},
};
I dont know the exact logic you need for your situation but this is just a general idea of how it can work. I chose to work in numbers only and add the :00 when I call anything that out puts a time.
heres a general example below of how it would function and log out
schedule.setHours = [1, 2, 3, 4, 5, 6, 7]
schedule.request = 5;
console.log(schedule.availHours.map((hour) => (hour += ':00'))); //[ '1:00', '2:00', '3:00', '7:00' ] 'Appointment Confirmed'
schedule.addHour = 8;
console.log(schedule.availHours.map((hour) => (hour += ':00')));//[ '1:00', '2:00', '3:00', '7:00', '8:00' ]
schedule.removeHour = 7;
console.log(schedule.availHours.map((hour) => (hour += ':00')));//[ '1:00', '2:00', '3:00', '8:00' ]
schedule.request = 12;
console.log(schedule.availHours.map((hour) => (hour += ':00')));// ['1:00', '2:00', '3:00', '8:00'] 'Time Not available, please choose another time'
schedule.setHours = [1,2,3,4,5,6,7,8,9,10]
console.log(schedule.availHours.map( hour=> hour += ':00') ) // [ '1:00', '2:00', '3:00', '4:00','5:00', '6:00','7:00', '8:00','9:00', '10:00']
and finally, completely aside from above.. if you just want a function option different from the examples above, you can do something like this.
function openTimes(availTimeArray, reqTime = '') {
availTimeArray = availTimeArray.map((time) => time.replace(/:00/, ''));
return availTimeArray.includes(reqTime)
? (availTimeArray = availTimeArray
.filter((time) => time != reqTime && time != Number(reqTime) + 1 && time != reqTime - 1)
.map((times) => (times += ':00')))
: (availTimeArray = availTimeArray.map((time) => (time += ':00')));
}
let availHours = openTimes(['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00'], '3');
console.log(availHours); // [ '1:00', '5:00', '6:00', '7:00' ]
availHours = openTimes(availHours, '6')
console.log(availHours) // ['1:00']
If you pass no second argument it will just return all of the numbers in the array you give to the first argument. Also, if you define the availHours with let you can keep redefining itself by passing itself into the function to keep a running tally with one variable. The function could also be shorter and more straight forward if you only work in numbers and then add the :00 with map when you use the variable when displaying the hours, it's also only built to accept hours, not half hours.

How to average values in JSON after grouping within a group in javascript

I have a json file with the following data which I would like to group by Year , Theme and then average values on each theme. Can someone help me with this? I have grouped by date and then theme but struggling after that.
[
{"Bid":"BidTest1","QNo":"1","Score":"0.7","Theme":"Social Value","QDate":"01/01/2021"},
{"Bid":"BidTest1","QNo":"2","Score":"0.5","Theme":"Tech Mgt","QDate":"01/01/2021"},
{"Bid":"BidTest1","QNo":"3","Score":"0.8","Theme":"Agile","QDate":"01/01/2021"},
{"Bid":"BidTest1","QNo":"4","Score":"0.7","Theme":"Social Value","QDate":"01/01/2021"},
{"Bid":"BidTest456","QNo":"1","Score":"0.5","Theme":"Tech Mgt","QDate":"03/04/2021"},
{"Bid":"BidTest456","QNo":"2","Score":"0.7","Theme":"Social Value","QDate":"03/04/2021"},
{"Bid":"BidTest456","QNo":"3","Score":"0.5","Theme":"Agile","QDate":"03/04/2021"},
{"Bid":"BidHO","QNo":"1","Score":"0.8","Theme":"Agile","QDate":"06/10/2021"},
{"Bid":"BidHO","QNo":"2","Score":"0.7","Theme":"Social Value","QDate":"06/10/2021"}
]
The code I have used for grouping it is
let result1 = dataQ1.reduce((state1, current1 ) => {
let {QDate, Theme} = current1;
let date = state1[QDate] || (state1[QDate] = {});
let themeArr = date[Theme] || (date[Theme] = []);
// let monthArr = yearObj[month] || (yearObj[month] = []);
themeArr.push(current1);
return state1;
}, {});
This gives me an object with objects containing arrays for each Theme. I need to create a chart grouped by Month, Theme and mark the respective average values per theme.Final Chart.Final Data Sample
Could someone please guide me on how to go about this ? Thanks
I would slightly change your reducing function to make it easier to compute averages:
let groupedDataQ1 = dataQ1.reduce((accumulator, item) => {
let {QDate, Theme} = item;
accumulator[Theme] = accumulator[Theme] || {};
// i'm assuming your dates are in dd/mm/yyyy format -> we extract mm/yyyy
let month = QDate.slice(-7);
(accumulator[Theme][month] = accumulator[Theme][month] || []).push(item);
return accumulator;
}, {});
Now you have as keys the Themes, and each Theme is an object that, for each month, has an array of corresponding values. So, if you want to compute the average Scores for Theme "Agile" in April 2021 you would simply do
let agile_april_data = groupedDataQ1['Agile']['04/2021'];
let agile_april_avg_score = agile_april_data.reduce((val, item) => val + parseFloat(item.Score),0) / agile_april_data.length
The reason why I decided to change the first term of the grouping function (you group first by date [leaving aside the fact that you keep the day in the key], and then by Theme, while I do the other way around) is because in the screenshot examples you provided, the Theme is the series over which the computations are performed - so, for each Theme, one line in the plot and one table in the tabular data. What happens if, for a month, you don't have any data for a specific Theme? You would need to perform a big amount of checks, while in this approach you only need to iterate over the object keys.

Vuex: How to avoid errors when updating store (or: how to correctly watch for changes in store)?

I am building an app that contains several entries of different months. To show them, the user chooses a month (with a slider) and the app returns the machting entries. Only months (or better: month-year combinations) exist if there are corresponding entries. Until here, this works fine.
However, when I want to add an entry, I get weird errors: Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
I noticed that it all works fine if the corresponding month of the new entry already exists.
To get the current month-year combinations, I use a getter:
entriesMonths(state) {
// Return an ordered arrays of all months present among entries
var entriesMonths = [];
var months_unique = [];
var current_index = 0;
const entries = state.entries;
//Sort entries
var order = -1;
entries.sort(function(a, b) {
a = new Date(a.date);
b = new Date(b.date);
var results = a > b ? -1 : a < b ? 1 : 0;
return results * order;
});
// Add entry if not present yet
for (var i in entries) {
var month = moment(String(entries[i].date)).format("MM");
var year = moment(String(entries[i].date)).format("YYYY");
var displayMonth = moment(String(entries[i].date)).format("MMMM YYYY");
if (!months_unique.includes(displayMonth)) {
// Push if displayMonth not existent yet
months_unique.push(displayMonth);
entriesMonths.push({
index: current_index,
month: month,
year: year,
displayMonth: displayMonth,
});
current_index++;
}
}
return entriesMonths;
To also get the months after an entry was added, I use the getter within a computed property:
computed: {
entriesMonths() {
return this.$store.getters.entriesMonths;
},
},
If I use the getter in data(), I don't get an error after adding a new entry, but the app also does not show the new entry.
Adding the new entry to the store seems to work fine. So does the re-calculation of entriesMonths (I used debugger to check).
The full code can be found on Github: https://github.com/juliangermek/haushaltsbuch
The error message doesn't really tell what is wrong (and I don't think that it is an internal bug) and I can't find out what really is. Can you make sense of this?
Thanks a lot!

Comparing multiple values from two arrays and storing the results in a new array

I need to go over a sheet, take employees start date and end date which I successfully receive as props, format them in a proper way (different sheets = different date formats), that goes well, but depending on the date clicked I need to display a list of employees that worked in that period, meaning if they stopped working after that particular date, they'll be listed, but if they started after that date, they wouldn't be listed.
Imagine a Reports component with all different reports listed (depending on the date), when a user clicks one he should be forwarded to details component where I need to list all the active employees in that particular month.
const { year } = this.props.history.location.state.item;
const { month } = this.props.history.location.state.item;
const selectedMonth = moment().month(month).format("MM");
// it's a number 201703
const finalSelect = parseInt(year + selectedMonth);
// BASIC FORMATTING:
const {employees, reports} = this.props;
const renderActive = [];
employees.map(emp => {
if( finalSelect > parseInt(moment(emp.startdate).format('YYYYMM'))
&& finalSelect > parseInt(moment(emp.enddate).format('YYYYMM'))) {
renderActive.push(emp);
}});
/* employees.map(emp => {
if( emp.enddate == undefined) {
renderActive.push(emp);
}}); */
const unique = renderActive
.map( item => item )
.filter( ( item, idx, arr ) => arr.indexOf( item ) == idx )
console.log(unique);
** So what I [think] that I need to accomplish is: **
Start date needs to be higher the selected Date and the end date also needs to be higher than the one selected. And if there's no endDAte it's undefined, so I need to take that into account too.
startDate > selectedReport < endDate
THANK YOU GUYS IN ADVANCE :)
THIS WAS THE SOLUTION. NOW I CAN FINALLY GO TO SLEEP :)
const result = [];
employees.forEach(emp => {
if (finalSelect > parseInt(moment(emp.startdate).format('YYYYMM')) &&
(finalSelect < parseInt(moment(emp.enddate).format('YYYYMM'))) ||
(parseInt(moment(emp.enddate).format('YYYYMM') !"= 'undefined'))) {
result.push(emp);
}
});

firebase -> date order reverse

I am currently making an app using Firebase.
It is one of those bulletin boards that can be seen anywhere on the web.
But there was one problem.
This is a matter of date sorting.
I want to look at the recent date first, but I always see only the data I created first.
postRef.orderByChild('createData').startAt(reverseDate).limitToFirst(1).on('child_added',(data)=>{
console.log(data.val().name + data.val().createData);
})
result - >hello1496941142093
My firebase tree
My code is the same as above.
How can I check my recent posts first?
How Do I order reverse of firebase database?
The Firebase Database will always return results in ascending order. There is no way to reverse them.
There are two common workaround for this:
Let the database do the filtering, but then reverse the results client-side.
Add an inverted value to the database, and use that for querying.
These options have been covered quite a few times before. So instead of repeating, I'll give a list of previous answers:
Display posts in descending posted order
Sort firebase data in descending order using negative timestamp
firebase sort reverse order
Is it possible to reverse a Firebase list?
many more from this list: https://www.google.com/search?q=site:stackoverflow.com+firebase+reverse%20sort%20javascript
You can simply make a function to reverse the object and then traversing it.
function reverseObject(object) {
var newObject = {};
var keys = [];
for (var key in object) {
keys.push(key);
}
for (var i = keys.length - 1; i >= 0; i--) {
var value = object[keys[i]];
newObject[keys[i]]= value;
}
return newObject;
}
This is how I solved it:
First I made a query in my service where I filter by date in milliseconds:
getImages (): Observable<Image[]> {
this.imageCollection = this.asf.collection<Image>('/images', ref => ref.orderBy('time').startAt(1528445969388).endAt(9999999999999));
this.images = this.imageCollection.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as Image;
const id = a.payload.doc.id;
return { id, ...data };
}))
);
return this.images;
}
Then to get the newest date first I added this to my component where I call the method from my service:
let date = new Date;
let time = 9999999999999 - date.getTime();
console.log(time);
I pass the time let as the date. Since a newer date will be a bigger number to deduct from the 9999999999999, the newest date will turn up first in my query inside my service.
Hope this solved it for you
If you want to display it in the front end, I suggest that after you retrieve the data, use the reverse() function of JavaScript.
Example:
let result = postRef
.orderByChild("createData")
.startAt(reverseDate)
.limitToFirst(1)
.on("child_added", data => {
console.log(data.val().name + data.val().createData);
});
result.reverse();
Ive ended changing how I create my list on the frontend part.
was
posts.add(post);
changed to
posts.insert(0, post);
You could use a method where you save the same or alternate child with a negative value and then parse it.
postRef.orderByChild('createData').orderByChild('createData').on('child_added',(data)=>{
console.log(data.val().name + data.val().createData);})
Far more easier is just use Swift's reversed():
https://developer.apple.com/documentation/swift/array/1690025-reversed
https://developer.apple.com/documentation/swift/reversedcollection
let decodedIds = try DTDecoder().decode([String].self, from: value)
// we reverse it, because we want most recent orders at the top
let reversedDecodedIds = decodedIds.reversed().map {$0}
orderBy("timestamp", "desc")
I think you can give a second argument name "desc".
It worked for me

Categories