diff --git a/src/app/app-calendar/calendar/calendar.component.html b/src/app/app-calendar/calendar/calendar.component.html
index 83abb7f..91762a2 100644
--- a/src/app/app-calendar/calendar/calendar.component.html
+++ b/src/app/app-calendar/calendar/calendar.component.html
@@ -29,10 +29,11 @@
{{ i + 1 }}
diff --git a/src/app/app-calendar/calendar/calendar.component.ts b/src/app/app-calendar/calendar/calendar.component.ts
index 9af60da..ea8c6b7 100644
--- a/src/app/app-calendar/calendar/calendar.component.ts
+++ b/src/app/app-calendar/calendar/calendar.component.ts
@@ -2,6 +2,14 @@ import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { CalendarData, CalendarTableContent } from '../../../types/calendar';
+import {
+ END_AFTERNOON_SESSION,
+ END_EVENING_SESSION,
+ END_MORNING_SESSION,
+ START_AFTERNOON_SESSION,
+ START_EVENING_SESSION,
+ START_MORNING_SESSION,
+} from '../../../constants/calendar';
@Component({
selector: 'app-calendar',
@@ -12,6 +20,13 @@ import { CalendarData, CalendarTableContent } from '../../../types/calendar';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CalendarComponent {
+ START_MORNING_SESSION = START_MORNING_SESSION;
+ END_MORNING_SESSION = END_MORNING_SESSION;
+ START_AFTERNOON_SESSION = START_AFTERNOON_SESSION;
+ END_AFTERNOON_SESSION = END_AFTERNOON_SESSION;
+ START_EVENING_SESSION = START_EVENING_SESSION;
+ END_EVENING_SESSION = END_EVENING_SESSION;
+
@Input('calendar$') calendar$: BehaviorSubject;
@Input('calendarTableContent$')
calendarTableContent$: BehaviorSubject;
@@ -46,17 +61,6 @@ export class CalendarComponent {
return 'evening';
}
- calendarBackgroundClass(session: number): string {
- switch (this.checkSession(session)) {
- case 'afternoon':
- return 'bg-secondary';
- case 'evening':
- return 'bg-neutral';
- default:
- return 'bg-accent';
- }
- }
-
processCalendarInDate(
date: number
): { start: number; end: number; defaultName: string }[][] {
diff --git a/src/app/app-calendar/class-info/class-info.component.html b/src/app/app-calendar/class-info/class-info.component.html
index 5ebb334..60e7843 100644
--- a/src/app/app-calendar/class-info/class-info.component.html
+++ b/src/app/app-calendar/class-info/class-info.component.html
@@ -2,6 +2,59 @@
id="class-info"
class="overflow-scroll overflow-x-hidden overflow-y-overlay max-h-[calc(100vh-18rem)]"
>
+
+
+
+
+
+
+
-
-
-
-
-
-
-
diff --git a/src/types/calendar.ts b/src/types/calendar.ts
index c2bd1a0..b25e9f9 100644
--- a/src/types/calendar.ts
+++ b/src/types/calendar.ts
@@ -94,8 +94,4 @@ export type AutoMode =
| 'refer-non-overlap-afternoon'
| 'refer-non-overlap-evening';
-export type CombinationCache = {
- [key: string]: ClassCombination[];
-};
-
export type ClassCombination = CalendarGroupByClassDetail[];
diff --git a/src/utils/calendar_overlap.ts b/src/utils/calendar_overlap.ts
index 96c6da3..2a320fc 100644
--- a/src/utils/calendar_overlap.ts
+++ b/src/utils/calendar_overlap.ts
@@ -2,7 +2,6 @@ import {
CalendarGroupByClassDetail,
CalendarGroupBySubjectName,
ClassCombination,
- CombinationCache,
} from '../types/calendar';
import { countSpecificDayOfWeek } from './date';
@@ -12,84 +11,33 @@ import { countSpecificDayOfWeek } from './date';
* @param selectedSubjects - Đối tượng chứa các môn học đã chọn, được nhóm theo tên môn học.
* @returns Một mảng các tổ hợp, mỗi tổ hợp là một mảng chứa các chi tiết lớp học.
*
- * Hàm này sử dụng phương pháp đệ quy để tạo ra tất cả các tổ hợp có thể có từ các môn học đã chọn.
- * - `subjectKeys` là mảng chứa các khóa của các môn học.
- * - `combinations` là mảng chứa các tổ hợp kết quả.
- * - Hàm `backtrack` được sử dụng để duyệt qua tất cả các tổ hợp có thể có.
- * - `index` là chỉ số hiện tại trong mảng `subjectKeys`.
- * - `currentCombination` là tổ hợp hiện tại đang được xây dựng.
- * - Nếu `index` bằng độ dài của `subjectKeys`, tổ hợp hiện tại đã hoàn thành và được thêm vào `combinations`.
- * - `subjectKey` là khóa của môn học hiện tại.
- * - `subjectData` là dữ liệu của môn học hiện tại.
- * - `classKeys` là mảng chứa các khóa của các lớp học trong môn học hiện tại.
- * - Với mỗi `classKey`, lớp học tương ứng được thêm vào `currentCombination` và hàm `backtrack` được gọi đệ quy với `index + 1`.
- * - Sau khi gọi đệ quy, lớp học được loại bỏ khỏi `currentCombination` để thử các tổ hợp khác.
*/
export function generateCombinations(
- selectedSubjects: CalendarGroupBySubjectName,
- cache?: CombinationCache
+ selectedSubjects: CalendarGroupBySubjectName
): ClassCombination[] {
const subjectKeys = Object.keys(selectedSubjects);
const combinations: ClassCombination[] = [];
+ const currentCombination: ClassCombination = [];
- function backtrack(
- index: number,
- currentCombination: ClassCombination
- ): ClassCombination[] {
- const subjectKey = subjectKeys[index];
+ function backtrack(index: number) {
+ if (index === subjectKeys.length) {
+ combinations.push([...currentCombination]);
+ return;
+ }
+ const subjectKey = subjectKeys[index];
const subjectData = selectedSubjects[subjectKey];
- if (!subjectData) return [];
-
- const subjectCache: ClassCombination[] = [];
- let remainSubjectsCache: ClassCombination[] = [];
- let needToCalculateRemainSubjectsCache = true;
- const remainSubjectKeys = subjectKeys.slice(index + 1);
+ if (!subjectData) return;
- if (!remainSubjectKeys.length) needToCalculateRemainSubjectsCache = false;
- else if (cache && cache[remainSubjectKeys.join('|')] != undefined) {
- remainSubjectsCache = cache[remainSubjectKeys.join('|')];
- needToCalculateRemainSubjectsCache = false;
- }
-
- Object.keys(subjectData.classes).forEach((classKey) => {
+ const classKeys = Object.keys(subjectData.classes);
+ for (const classKey of classKeys) {
currentCombination.push(subjectData.classes[classKey]);
-
- if (index === subjectKeys.length - 1) {
- const completedCombination = [...currentCombination];
- combinations.push(completedCombination); // clone currentCombination completed result to avoid .pop()
- subjectCache.push(completedCombination.slice(index));
- } else {
- if (needToCalculateRemainSubjectsCache) {
- remainSubjectsCache = backtrack(index + 1, currentCombination);
- needToCalculateRemainSubjectsCache = false;
- }
-
- // merge remain subject cache with current subject class
- for (const slicedClassCombination of remainSubjectsCache) {
- subjectCache.push([
- subjectData.classes[classKey],
- ...slicedClassCombination,
- ]);
- combinations.push([...currentCombination, ...slicedClassCombination]); // clone currentCombination completed result to avoid .pop()
- }
- }
-
+ backtrack(index + 1);
currentCombination.pop();
- });
-
- // cache
- if (cache && Object.keys(subjectCache).length) {
- const combinedSubjectKeys = [subjectKey, ...remainSubjectKeys].join('|');
- if (!cache[combinedSubjectKeys]) cache[combinedSubjectKeys] = [];
- cache[combinedSubjectKeys].push(...subjectCache);
}
-
- return subjectCache;
}
- backtrack(0, []);
-
+ backtrack(0);
return combinations;
}
@@ -113,52 +61,79 @@ export function generateCombinations(
* Kết quả cuối cùng là tổng số tiết học bị trùng lặp giữa tất cả các lớp học trong nhóm.
*/
export function calculateOverlap(
- combination: CalendarGroupByClassDetail[]
+ combination: CalendarGroupByClassDetail[],
+ cache?: {
+ [key: string]: number;
+ }
): number {
let overlap = 0;
+ let combinationCacheKey = combination
+ .map((cd) => [cd.majors[0], cd.subjectName, cd.subjectClassCode].join('-'))
+ .join('|');
+
+ if (cache && cache.hasOwnProperty(combinationCacheKey))
+ return cache[combinationCacheKey];
+
for (let i = 0; i < combination.length; i++)
for (let j = i + 1; j < combination.length; j++) {
const classDetail1 = combination[i];
const classDetail2 = combination[j];
- for (let session1 of classDetail1.details) {
- for (let session2 of classDetail2.details) {
- if (
- session1.startDate <= session2.endDate &&
- session1.endDate >= session2.startDate &&
- session1.startSession <= session2.endSession &&
- session1.endSession >= session2.startSession &&
- session1.dayOfWeek === session2.dayOfWeek
- ) {
- const conflictStartDate = Math.max(
- session1.startDate,
- session2.startDate
- ); // Ngày bắt đầu trùng
- const conflictEndDate = Math.min(
- session1.endDate,
- session2.endDate
- ); // Ngày kết thúc trùng
-
- const conflictStartSession = Math.max(
- session1.startSession,
- session2.startSession
- ); // Tiết bắt đầu trùng
- const conflictEndSession = Math.min(
- session1.endSession,
- session2.endSession
- ); // Tiết kết thúc trùng
-
- overlap +=
- countSpecificDayOfWeek(
- conflictStartDate,
- conflictEndDate,
- session1.dayOfWeek
- ) *
- (conflictEndSession - conflictStartSession + 1);
- }
- }
+
+ const pairCacheKey = [
+ classDetail1.majors[0],
+ classDetail1.subjectName,
+ classDetail1.subjectClassCode,
+ classDetail2.majors[0],
+ classDetail2.subjectName,
+ classDetail2.subjectClassCode,
+ ].join('|');
+
+ if (cache && cache.hasOwnProperty(pairCacheKey)) {
+ overlap += cache[pairCacheKey];
+ } else {
+ let classPairTotalOverlap = 0;
+ for (let session1 of classDetail1.details)
+ for (let session2 of classDetail2.details)
+ if (
+ session1.startDate <= session2.endDate &&
+ session1.endDate >= session2.startDate &&
+ session1.startSession <= session2.endSession &&
+ session1.endSession >= session2.startSession &&
+ session1.dayOfWeek === session2.dayOfWeek
+ ) {
+ const conflictStartDate = Math.max(
+ session1.startDate,
+ session2.startDate
+ ); // Ngày bắt đầu trùng
+ const conflictEndDate = Math.min(
+ session1.endDate,
+ session2.endDate
+ ); // Ngày kết thúc trùng
+
+ const conflictStartSession = Math.max(
+ session1.startSession,
+ session2.startSession
+ ); // Tiết bắt đầu trùng
+ const conflictEndSession = Math.min(
+ session1.endSession,
+ session2.endSession
+ ); // Tiết kết thúc trùng
+
+ classPairTotalOverlap +=
+ countSpecificDayOfWeek(
+ conflictStartDate,
+ conflictEndDate,
+ session1.dayOfWeek
+ ) *
+ (conflictEndSession - conflictStartSession + 1);
+ }
+ cache && (cache[pairCacheKey] = classPairTotalOverlap);
+ overlap += classPairTotalOverlap;
}
}
+ cache && (cache[combinationCacheKey] = overlap);
+
return overlap;
}
@@ -181,27 +156,58 @@ export function getOverlapRange(
return null;
}
-export function calculateTotalSessionsInSessionRangeOfCombination(
+export function calculateTotalSessionsInSessionRange(
combination: CalendarGroupByClassDetail[],
startShiftSession: number,
- endShiftSession: number
+ endShiftSession: number,
+ cache?: {
+ [key: string]: number;
+ }
): number {
- return combination.reduce((acc, classData) => {
- for (const sessionData of classData.details) {
- const overlapMorningSessionRange = getOverlapRange(
- [sessionData.startSession, sessionData.endSession],
- [startShiftSession, endShiftSession]
- );
- acc +=
- (overlapMorningSessionRange
- ? overlapMorningSessionRange[1] - overlapMorningSessionRange[0] + 1
- : 0) *
- countSpecificDayOfWeek(
- sessionData.startDate,
- sessionData.endDate,
- sessionData.dayOfWeek
+ const cacheKeyPrefix = `totalSessionsInSessionRange-${startShiftSession}-${endShiftSession}`;
+ const combinationCacheKey = [
+ cacheKeyPrefix,
+ ...combination.map((cd) =>
+ [cd.majors[0], cd.subjectName, cd.subjectClassCode].join('-')
+ ),
+ ].join('|');
+
+ if (cache && cache.hasOwnProperty(combinationCacheKey))
+ return cache[combinationCacheKey];
+
+ const result = combination.reduce((acc, classData) => {
+ const cacheKey = [
+ cacheKeyPrefix,
+ classData.majors[0],
+ classData.subjectName,
+ classData.subjectClassCode,
+ ].join('|');
+
+ if (cache && cache.hasOwnProperty(cacheKey)) acc += cache[cacheKey];
+ else {
+ let localAcc = 0;
+ for (const sessionData of classData.details) {
+ const overlapRange = getOverlapRange(
+ [sessionData.startSession, sessionData.endSession],
+ [startShiftSession, endShiftSession]
);
+ localAcc +=
+ (overlapRange ? overlapRange[1] - overlapRange[0] + 1 : 0) *
+ countSpecificDayOfWeek(
+ sessionData.startDate,
+ sessionData.endDate,
+ sessionData.dayOfWeek
+ );
+ }
+
+ cache && (cache[cacheKey] = localAcc);
+ acc += localAcc;
}
+
return acc;
}, 0);
+
+ cache && (cache[combinationCacheKey] = result);
+
+ return result;
}
diff --git a/src/workers/calendar.worker.ts b/src/workers/calendar.worker.ts
index 9b9772b..b2c3e66 100644
--- a/src/workers/calendar.worker.ts
+++ b/src/workers/calendar.worker.ts
@@ -9,20 +9,23 @@ import {
import {
AutoMode,
CalendarData,
- CalendarGroupByClassDetail,
CalendarGroupByMajor,
CalendarGroupBySessionDetail,
CalendarGroupBySubjectName,
CalendarTableContent,
- CombinationCache,
} from '../types/calendar';
import {
calculateOverlap,
- calculateTotalSessionsInSessionRangeOfCombination,
+ calculateTotalSessionsInSessionRange,
generateCombinations,
} from '../utils/calendar_overlap';
-const conbinationCache: CombinationCache = {};
+const overlapCache: {
+ [key: string]: number;
+} = {};
+const overlapSessionCache: {
+ [key: string]: number;
+} = {};
function workerCalculateCalendarTableContent(
calendarTableContent: CalendarTableContent,
@@ -142,29 +145,36 @@ export function workerAutoCalculateCalendarTableContent(
{}
);
- const combinations = generateCombinations(selectedSubjects, conbinationCache);
+ const start = performance.now();
+ const combinations = generateCombinations(selectedSubjects);
+ console.log('Generate combinations:', performance.now() - start);
+
+ const start2 = performance.now();
const combinationsOrderByOverlap = combinations
.map((combination) => ({
- overlap: calculateOverlap(combination),
+ overlap: calculateOverlap(combination, overlapCache),
totalSessionsInSessionRangeOfCombination: ((combination): number => {
switch (auto) {
case 'refer-non-overlap-morning':
- return calculateTotalSessionsInSessionRangeOfCombination(
+ return calculateTotalSessionsInSessionRange(
combination,
START_MORNING_SESSION,
- END_MORNING_SESSION
+ END_MORNING_SESSION,
+ overlapSessionCache
);
case 'refer-non-overlap-afternoon':
- return calculateTotalSessionsInSessionRangeOfCombination(
+ return calculateTotalSessionsInSessionRange(
combination,
START_AFTERNOON_SESSION,
- END_AFTERNOON_SESSION
+ END_AFTERNOON_SESSION,
+ overlapSessionCache
);
case 'refer-non-overlap-evening':
- return calculateTotalSessionsInSessionRangeOfCombination(
+ return calculateTotalSessionsInSessionRange(
combination,
START_EVENING_SESSION,
- END_EVENING_SESSION
+ END_EVENING_SESSION,
+ overlapSessionCache
);
}
return 0;
@@ -180,11 +190,13 @@ export function workerAutoCalculateCalendarTableContent(
);
return diff;
});
+ console.log('Calculate overlap:', performance.now() - start2);
const bestCombination = combinationsOrderByOverlap.length
? combinationsOrderByOverlap[autoTh % combinationsOrderByOverlap.length]
: undefined;
+ const start3 = performance.now();
const updatedCalendarGroupByMajor = bestCombination
? (() => {
const clonedCalendarGroupByMajor =
@@ -206,6 +218,7 @@ export function workerAutoCalculateCalendarTableContent(
sessions
).updatedCalendarTableContent
: calendarTableContent;
+ console.log('Update calendar:', performance.now() - start3);
return {
updatedCalendarTableContent,