Validating free time slot in schedule - javascript

I am trying to come up with an efficient algorithm to do the following, ideally in javascript or golang:
Given a set of busy time intervals (start timestamp + end timestamp in ms), validate an incoming timeslot to be scheduled. Assume there aren't any existing overlaps.
Example:
const timeslots = [
{ start: 10, end: 14 },
{ start: 17, end: 21 },
{ start: 30, end: 37 },
// ...
];
const ts = timeslots.sort((a, b) => {
return a.start - b.start;
});
const checkValid = (inc) => {
// validation here
};
console.log(checkValid({ start: 15, end: 16 })); // should be true
console.log(checkValid({ start: 15, end: 18 })); // should be false
console.log(checkValid({ start: 16, end: 27 })); // should be false
console.log(checkValid({ start: 8, end: 39 })); // should be false
And any other edge cases not described should work as well.

If the intervals are (1) non-overlapping and (2) sorted then you can perform a binary search to find the first one that lies just after the desired interval, and then simply check if the previous one overlaps with it. This would be O(logN) instead of O(N).
Example in Go:
package main
import (
"fmt"
"sort"
)
type Slot struct {
start int
end int
}
var timeslots = []Slot{
{start: 10, end: 14},
{start: 17, end: 21},
{start: 30, end: 37},
}
func checkValid(slot Slot) bool {
i := sort.Search(len(timeslots), func(i int) bool {
return timeslots[i].start > slot.end
})
return i == 0 || timeslots[i-1].end < slot.start
}
func main() {
sort.Slice(timeslots, func(a, b int) bool {
return timeslots[a].start < timeslots[b].start
})
fmt.Println(checkValid(Slot{start: 15, end: 16})) // should be true
fmt.Println(checkValid(Slot{start: 15, end: 18})) // should be false
fmt.Println(checkValid(Slot{start: 16, end: 27})) // should be false
fmt.Println(checkValid(Slot{start: 8, end: 39})) // should be false
}

validate(start, end)
for timeslot in timeslots
if start >= timeslot["start"] and start <= timeslot["end"]
return false
if start < timeslot["start"] and end >= timeslot["start"]
return false
if end >= timeslot["start"] and end <= timeslot["end"]
return false
return true
I think something like this should work.

Related

How could I compute a list of "open hours" in javascript from a list of start and end times with overlaps?

I am trying to simplify a list of open times so that there are no overlaps/duplicate information showing (in javascript).
Here is an example of what I am trying to achieve:
The starting array would look something like this:
const mondayHours = [
{ start: "09:00", end: "14:00" },
{ start: "10:00", end: "15:00" },
{ start: "17:00", end: "23:00" },
];
And the data is currently displayed like:
Open: 9am-2pm, 10am-3pm, 5pm-11pm
I want the result to return an array for the total open hours like so:
const computedMondayHours = [
{ start: "09:00", end: "15:00" },
{ start: "17:00", end: "23:00" },
];
And so that the data will be displayed like:
Open: 9am-3pm, 5pm-11pm
I have found a solution online that returns the latest open times with the earliest close times, thinking I could convert it for my uses, but that has not worked at all:
const hours = [{
start: "09:00",
end: "14:00"
},
{
start: "10:00",
end: "15:00"
},
{
start: "17:00",
end: "23:00"
}
]
const isBetween = (value, start, end) => value > start && value < end
const computeOpenHours = (dayHours) => {
const index = {}
dayHours.forEach(({
start: aStart,
end: aEnd
}) => {
dayHours.forEach(({
start: bStart,
end: bEnd
}) => {
aStart = isBetween(bStart, aStart, aEnd) && bStart > aStart ? bStart : aStart
aEnd = isBetween(bEnd, aStart, aEnd) && bEnd < aEnd ? bEnd : aEnd
})
const key = `${aStart}-${aEnd}`
const value = {
start: aStart,
end: aEnd
}
index[key] = index[key] || value
})
return Object.keys(index).map(k => index[k])
}
console.log(computeOpenHours(hours))
You can reduce the hours and check if the current start is greater than the prev end time.
Note: This naïve algorithm assumes the input array is already sorted.
const calcTimeSlots = (workingHours) =>
workingHours.reduce((acc, { start, end }) => {
if (acc.length === 0) {
acc.push({ start, end });
} else {
const latest = acc[acc.length - 1];
if (parseTimeAsMillis(start) > parseTimeAsMillis(latest.end)) {
acc.push({ start, end });
} else if (parseTimeAsMillis(end) > parseTimeAsMillis(latest.end)) {
latest.end = end;
}
}
return acc;
}, []);
const parseTimeAsMillis = (str) =>
str.split(':')
.map((t, i, a) => parseInt(t) * 60 * (a.length - 1))
.reduce((total, curr) => total + curr);
const mondayHours = [
{ start: '09:00', end: '14:00' },
{ start: '10:00', end: '15:00' },
{ start: '17:00', end: '23:00' }
];
const tuesdayHours = [
{ start: '09:00', end: '14:00' },
{ start: '10:00', end: '11:00' }
];
console.log(calcTimeSlots(mondayHours));
console.log(calcTimeSlots(tuesdayHours));
.as-console-wrapper { top: 0; max-height: 100% !important; }
Also, here is a related question that I previously answered that may help:
"Moment.js - Duration within business/open/shift hours?"
const hours = [{"start":"09:00","end":"14:00"},
{"start":"10:00","end":"15:00"},
{"start":"17:00","end":"23:00"}]
// returns -ve if a<b, 0 if a=b, +ve if a>b
function cmp(a, b) {
return a.replace(':','') - b.replace(':','')
}
function mergeIfOverlap({start:s1, end:e1}, {start:s2, end:e2}) {
return cmp(s1,s2)<=0 && cmp(e1,s2)>=0
&& {start: s1, end: cmp(e1,e2)>=0 ? e1 : e2}
}
const result = [...hours].sort(({start:a},{start:b})=>cmp(a,b))
.reduce((a,c,_,x,y)=>(
x=a.findIndex(i => y=mergeIfOverlap(i,c)||mergeIfOverlap(c,i)),
y && (a[x]=y) || a.push(c), a), [])
console.log(result)

How to check the JavaScript array objects are overlapping

I have an array of objects containing start and end range.
var ranges = [{
start: 1,
end: 5
}]
I want to push an object like this without overlapping with the previous start and end range
{
start: 6,
end: 10
}
How to check new object overlapping with the existing objects
Edit:
{
start: 50,
end: 100
},
{
start: 11,
end: 49
}
Got the answer thanks everyone for your response.
let isValid = timeRanges.every(r => (r.begin != selected.begin && r.begin < selected.begin && selected.begin > r.end) && (r.end != selected.end && r.end < selected.begin && selected.end > r.end));
You can try this :
const ranges = [{
start: 1,
end: 5
}];
const rangeObj = {
start: 6,
end: 10
};
function checkRangeOverlap(obj) {
let res = ''
ranges.forEach((obj) => {
res = (obj.end > rangeObj.start) ? 'Overlaping': 'No Overlap';
});
return res;
}
console.log(checkRangeOverlap(rangeObj));

How to map array elements in JavaScript

Only elements that have a value greater than or equal to the threshold must be kept in the array. Then a new array will have to be created which will contain several objects. Each of these objects will have two properties, the start and the end.
If there are several elements in a row (which have a timestamp 10 minutes apart), they will be grouped in the same object. Where the start value will be the timestamp of the first element and the end value will be the timestamp value of the last element of the group plus 10 min.
If there are not several elements followed, the start value will be the timestamp and the end will be the timestamp plus 10 minutes.
const data = [{
timestamp: '2021-11-23T14:00:00+0000',
amount: 21
},
{
timestamp: '2021-11-23T14:10:00+0000',
amount: 27
},
{
timestamp: '2021-11-23T14:20:00+0000',
amount: 31
},
{
timestamp: '2021-11-23T14:30:00+0000',
amount: 29
},
{
timestamp: '2021-11-23T14:40:00+0000',
amount: 18
},
{
timestamp: '2021-11-23T14:50:00+0000',
amount: 17
},
{
timestamp: '2021-11-23T15:00:00+0000',
amount: 25
},
{
timestamp: '2021-11-23T15:10:00+0000',
amount: 21
}
]
const threshold = 25
const aboveThreshold = data.filter(element => element.amount >= threshold)
const workSchedule = []
for (let i = 0; i < aboveThreshold.length; i++) {
if (i === 0) {
workSchedule.push({
start: aboveThreshold[i].timestamp,
end: aboveThreshold[i + 1].timestamp
})
}
if (i > 0 && i < aboveThreshold.length - 1) {
if (aboveThreshold[i].timestamp.slice(11, 13) === aboveThreshold[i + 1].timestamp.slice(11, 13)) {
workSchedule.push({
start: aboveThreshold[i].timestamp,
end: aboveThreshold[i + 1].timestamp
})
}
}
if (i === aboveThreshold.length - 1) {
workSchedule.push({
start: aboveThreshold[i].timestamp,
end: aboveThreshold[i].timestamp
})
}
}
console.log(workSchedule)
But the end result I want is the following:
[
{
start: '2021-11-23T14:10:00+0000',
end: '2021-11-23T14:40:00+0000'
},
{
start: '2021-11-23T15:00:00+0000',
end: '2021-11-23T15:10:00+0000'
}
]
I hope I was clear 😬 and is there a simpler and easier to understand/read approach than what I've done so far?
You can apply a simple reduce function to get the result you want with a little bit of help from Date object. Here is a solution:
const aboveThreshold = data.filter(element => element.amount >= threshold);
const nws = aboveThreshold.reduce((acc, v) => {
const end = new Date(Date.parse(v.timestamp) + 600000);
if (acc.length === 0) return [{ start: v.timestamp, end: end.toISOString() }];
let diff = Date.parse(v.timestamp) - Date.parse(acc[acc.length - 1].end);
// checks if the difference is less than 10 minutes
if (diff <= 10 * 60 * 1000) {
acc[acc.length - 1].end = end.toISOString();
} else {
acc.push({ start: v.timestamp, end: end.toISOString() });
}
return acc
}, []);
Check out Reduce Documentation.
This is the result it gives with your data
[{
end: "2021-11-23T14:40:00.000Z",
start: "2021-11-23T14:10:00+0000"
}, {
end: "2021-11-23T15:10:00.000Z",
start: "2021-11-23T15:00:00+0000"
}]

Merge two array of timelines into one

Hey guys I am currently stuck on a question.
I have to merge to timelines. Each timeline is given as an array of objects.
Currently my thought process was to concat then sort both inputs. After that, compare start and end times. Any help ?
The original question:
Write a function to merge two array timelines into one. If an objects value is different, your new value should be false.
I CREATED THIS TO VISUALLY UNDERSTAND THE QUESTION BETTER:
Timeline1:
// null 45 89 null
// <-----------||---------------------||----------------->
// true false true
Timeline2:
// null 67 null
// <-----------------------||---------------------------->
// true false
MergedTimeline:
// null 45 67 89 null
// <-----------||----------||---------||----------------->
// true false false false
Example inputs:
let timeline1 = [
{ start: null, end: 45, value: true },
{ start: 45, end: 89, value: false },
{ start: 89, end: null, value: false }
]
let timeline2 = [
{ start: null, end: 67, value: true },
{ start: 67, end: null, value: false }
]
//expected output
return [
{ start: null, end: 45, value: true },
{ start: 45, end: 67, value: false },
{ start: 67, end: 89, value: false },
{ start: 89, end: null, value: false }
]
Here is my current attempt:
const mergeTimeline = (arr1,arr2) =>{
let combine = arr1.concat(arr2)
let sortedTimeline= combine.sort((a,b)=>{
return a.start - b.start
})
const mergedTimeline = [sortedTimeline[0]];
for (let i = 1; i < sortedTimeline.length; i++) {
const currentTimeLine = sortedTimeline[I];
const lastMergedTime = mergedTimeline[mergedTimeline.length - 1]
if (lastMergedTime.value !== currentTimeLine.value) {
currentTimeLine.value = false
lastMergedTime.end = Math.max(lastMergedTime.end,currentTimeLine.start)
mergedTimeline.push(currentTimeLine)
} else{
mergedTimeline.push(currentTimeLine)
}
}
return mergedTimeline
}
mergeTimeline(timeline1,timeline2)

Change index in array.map

I have a variable of the form:
var data=[
{
start:22,
end: 8
},
{
start:60,
end: 43
},
{
start: 35,
end: 55
},
{
start:25,
end:40
}
];
I want to map it to look like this
var newData = { 22:8, 60:43, 35:55, 25:40};
Is this possible? I mainly just want to use the start numbers as a key to access the end numbers without using search. I have tried to do this:
var mapData = data.map(function(data){
var x = {};
x[data.start]=data.end;
return x;
});
but it gives:
0
:
{22: 8}
1
:
{60: 43}
2
:
{35: 55}
3
:
{25: 40}
which means i have to use 0, 1,2, 3 as indices.
Only Array#map does not work in this case, because without post processing, you get a single array with objects. You need to combine all objects into a single object.
With Object.assign and spread syntax ..., you get a single array with all properties from the objects in the array.
var data = [{ start: 22, end: 8 }, { start: 60, end: 43 }, { start: 35, end: 55 }, { start: 25, end: 40 }],
result = Object.assign(...data.map(({ start, end }) => ({ [start]: end })));
console.log(result);
You can use array.reduce:
var data=[
{
start:22,
end: 8
},
{
start:60,
end: 43
},
{
start: 35,
end: 55
},
{
start:25,
end:40
}
];
var res = data.reduce((m, o) => {
m[o.start] = o.end;
return m;
}, {});
console.log(res);

Categories