mirror of
https://github.com/multipleof4/lynchmark.git
synced 2026-01-14 16:47:55 +00:00
Docs: Update benchmark results
This commit is contained in:
@@ -1,115 +1,75 @@
|
||||
async function findAvailableSlots(calA, calB, c) {
|
||||
const { DateTime, Interval } = await import('https://cdn.skypack.dev/luxon').then(m => m);
|
||||
const d = c.durationMinutes;
|
||||
const z = DateTime.utc().zoneName;
|
||||
const parseIso = s => DateTime.fromISO(s, { zone: 'utc' });
|
||||
const parseHm = (hm, base) => {
|
||||
const [H, M] = hm.split(':').map(Number);
|
||||
return base.set({ hour: H, minute: M, second: 0, millisecond: 0 });
|
||||
};
|
||||
const toIso = dt => dt.toUTC().toISO();
|
||||
async function findAvailableSlots(cal1, cal2, c) {
|
||||
const { DateTime, Interval } = await import('https://cdn.skypack.dev/luxon')
|
||||
const d = c.durationMinutes
|
||||
const sR = c.searchRange
|
||||
const wH = c.workHours
|
||||
const z = 'UTC'
|
||||
const p = (x, y) => DateTime.fromISO(x, { zone: y })
|
||||
const b = c => c.map(v => Interval.fromDateTimes(p(v.start, z), p(v.end, z)))
|
||||
const m = (a, b) => [...a, ...b].sort((x, y) => x.start - y.start)
|
||||
const n = i => {
|
||||
const r = []
|
||||
let c = i[0]
|
||||
for (let k = 1; k < i.length; k++) {
|
||||
const v = i[k]
|
||||
if (c.overlaps(v) || c.abutsStart(v)) c = Interval.fromDateTimes(c.start, c.end > v.end ? c.end : v.end)
|
||||
else r.push(c), (c = v)
|
||||
}
|
||||
r.push(c)
|
||||
return r
|
||||
}
|
||||
const iA = b(cal1)
|
||||
const iB = b(cal2)
|
||||
const all = m(iA, iB)
|
||||
const busy = all.length ? n(all) : []
|
||||
const srI = Interval.fromDateTimes(p(sR.start, z), p(sR.end, z))
|
||||
const whS = wH.start.split(':').map(Number)
|
||||
const whE = wH.end.split(':').map(Number)
|
||||
|
||||
const rStart = parseIso(c.searchRange.start);
|
||||
const rEnd = parseIso(c.searchRange.end);
|
||||
|
||||
const dayRange = [];
|
||||
for (let d0 = rStart.startOf('day'); d0 < rEnd; d0 = d0.plus({ days: 1 })) {
|
||||
const ws = parseHm(c.workHours.start, d0);
|
||||
const we = parseHm(c.workHours.end, d0);
|
||||
const s = ws < rStart ? rStart : ws;
|
||||
const e = we > rEnd ? rEnd : we;
|
||||
if (e > s) dayRange.push(Interval.fromDateTimes(s, e));
|
||||
const dayI = []
|
||||
let cur = srI.start.startOf('day')
|
||||
const endDay = srI.end.startOf('day')
|
||||
while (cur <= endDay) {
|
||||
const ws = cur.set({ hour: whS[0], minute: whS[1] })
|
||||
const we = cur.set({ hour: whE[0], minute: whE[1] })
|
||||
const w = Interval.fromDateTimes(ws, we)
|
||||
const clipped = srI.intersection(w)
|
||||
if (clipped && clipped.isValid && clipped.length('minutes') >= d) dayI.push(clipped)
|
||||
cur = cur.plus({ days: 1 })
|
||||
}
|
||||
|
||||
const normalizeBusy = cal =>
|
||||
cal
|
||||
.map(({ start, end }) => {
|
||||
const s = parseIso(start), e = parseIso(end);
|
||||
return e > s ? Interval.fromDateTimes(s, e) : null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => a.start - b.start);
|
||||
|
||||
const mergeBusy = busy => {
|
||||
const m = [];
|
||||
for (const i of busy) {
|
||||
const last = m[m.length - 1];
|
||||
if (!last || i.start > last.end) m.push(i);
|
||||
else if (i.end > last.end) last.end = i.end;
|
||||
const inv = []
|
||||
let pS = null
|
||||
for (const x of busy) {
|
||||
const xs = x.start < srI.start ? srI.start : x.start
|
||||
const xe = x.end > srI.end ? srI.end : x.end
|
||||
if (!pS) {
|
||||
if (xs > srI.start) inv.push(Interval.fromDateTimes(srI.start, xs))
|
||||
pS = xe
|
||||
} else {
|
||||
if (xs > pS) inv.push(Interval.fromDateTimes(pS, xs))
|
||||
if (xe > pS) pS = xe
|
||||
}
|
||||
return m;
|
||||
};
|
||||
}
|
||||
if (!busy.length) inv.push(srI)
|
||||
else if (pS < srI.end) inv.push(Interval.fromDateTimes(pS, srI.end))
|
||||
|
||||
const intersectWithRange = (busy, ranges) => {
|
||||
const res = [];
|
||||
let j = 0;
|
||||
for (const r of ranges) {
|
||||
while (j < busy.length && busy[j].end <= r.start) j++;
|
||||
let k = j;
|
||||
while (k < busy.length && busy[k].start < r.end) {
|
||||
const s = busy[k].start < r.start ? r.start : busy[k].start;
|
||||
const e = busy[k].end > r.end ? r.end : busy[k].end;
|
||||
if (e > s) res.push(Interval.fromDateTimes(s, e));
|
||||
k++;
|
||||
}
|
||||
const freeWithinRange = inv
|
||||
.map(v => dayI.map(dv => dv.intersection(v)).filter(x => x && x.isValid))
|
||||
.flat()
|
||||
.filter(x => x.length('minutes') >= d)
|
||||
|
||||
const res = []
|
||||
for (const f of freeWithinRange) {
|
||||
let st = f.start
|
||||
const mins = f.length('minutes')
|
||||
const steps = Math.floor(mins / d)
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const en = st.plus({ minutes: d })
|
||||
res.push({ start: st.toISO(), end: en.toISO() })
|
||||
st = en
|
||||
}
|
||||
return mergeBusy(res);
|
||||
};
|
||||
|
||||
const invertBusy = (busy, ranges) => {
|
||||
const free = [];
|
||||
for (const r of ranges) {
|
||||
let cur = r.start;
|
||||
for (const b of busy) {
|
||||
if (b.end <= r.start || b.start >= r.end) continue;
|
||||
if (b.start > cur) free.push(Interval.fromDateTimes(cur, b.start));
|
||||
if (b.end > cur) cur = b.end;
|
||||
if (cur >= r.end) break;
|
||||
}
|
||||
if (cur < r.end) free.push(Interval.fromDateTimes(cur, r.end));
|
||||
}
|
||||
return free;
|
||||
};
|
||||
|
||||
const intersectFree = (fa, fb) => {
|
||||
const res = [];
|
||||
let i = 0, j = 0;
|
||||
while (i < fa.length && j < fb.length) {
|
||||
const a = fa[i], b = fb[j];
|
||||
const s = a.start > b.start ? a.start : b.start;
|
||||
const e = a.end < b.end ? a.end : b.end;
|
||||
if (e > s) res.push(Interval.fromDateTimes(s, e));
|
||||
if (a.end < b.end) i++;
|
||||
else j++;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const filterByDuration = (slots, minutes) => {
|
||||
const out = [];
|
||||
const dur = { minutes };
|
||||
for (const s of slots) {
|
||||
let cur = s.start;
|
||||
while (cur.plus(dur) <= s.end) {
|
||||
const end = cur.plus(dur);
|
||||
out.push({ start: toIso(cur), end: toIso(end) });
|
||||
cur = end;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const busyA = mergeBusy(normalizeBusy(calA));
|
||||
const busyB = mergeBusy(normalizeBusy(calB));
|
||||
|
||||
const busyAIn = intersectWithRange(busyA, dayRange);
|
||||
const busyBIn = intersectWithRange(busyB, dayRange);
|
||||
|
||||
const freeA = invertBusy(busyAIn, dayRange);
|
||||
const freeB = invertBusy(busyBIn, dayRange);
|
||||
|
||||
const mutualFree = intersectFree(freeA, freeB);
|
||||
|
||||
return filterByDuration(mutualFree, d);
|
||||
}
|
||||
return res
|
||||
}
|
||||
export default findAvailableSlots;
|
||||
Reference in New Issue
Block a user