-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathScheduleEvaluator.ts
164 lines (148 loc) · 4.92 KB
/
ScheduleEvaluator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* @module src/algorithm
* @author Hanzhi Zhou, Kaiying Shan
*/
/**
*
*/
import GeneratedSchedule from '../models/GeneratedSchedule';
import Event from '../models/Event';
import { RawAlgoCourse } from './ScheduleGenerator';
// eslint-disable-next-line @typescript-eslint/no-use-before-define
export type SortFunctionNames =
| 'variance'
| 'compactness'
| 'lunchTime'
| 'noEarly'
| 'distance'
| 'similarity'
| 'IamFeelingLucky';
/**
* representation of a single sort option
* corresponds to the SortOption struct in ScheduleGenerator.cpp
*/
export interface SortOption {
/** name of this sort option*/
readonly name: SortFunctionNames;
/** whether or not this option is enabled */
enabled: boolean;
/** whether to sort in reverse */
reverse: boolean;
/**
* the index into the sortFunctions array.
* Used to get the sort function corresponding to this option
*/
readonly idx: number;
/** the weight of this sort option, used by the combined sort mode only */
weight: number;
}
/** enum for the two sort modes */
export enum SortMode {
fallback = 0,
combined = 1
}
/** options for the schedule evaluator */
export interface EvaluatorOptions {
readonly sortBy: readonly SortOption[];
mode: SortMode;
}
/**
* The goal of the schedule evaluator is to efficiently sort the generated schedules
* according to the set of the rules defined by the user
*/
class ScheduleEvaluator {
private _refSchedule!: GeneratedSchedule['All'];
/**
* @param options
* @param events the array of events kept, use to construct generated schedules
* @param classList the array of (combined) sections
*/
constructor(
public options: Readonly<EvaluatorOptions> = { sortBy: [], mode: 0 },
private readonly events: Event[] = [],
private readonly classList: RawAlgoCourse[] = [],
private readonly secLens: number[] = [],
refSchedule: GeneratedSchedule['All'] = {},
private readonly Module?: typeof window.NativeModule
) {
this.refSchedule = refSchedule;
}
get size() {
if (!this.Module) return 0;
return this.Module._size();
}
get refSchedule() {
return this._refSchedule;
}
set refSchedule(refSchedule: GeneratedSchedule['All']) {
this._refSchedule = refSchedule;
// don't do anything if this is an empty evaluator or the ref schedule is set to empty
if (!this.Module || Object.keys(refSchedule).length === 0) return;
const numCourses = this.secLens.length - 1;
const ptr = this.Module!._malloc(numCourses * 2);
const refScheduleEncoded = this.Module.HEAPU16.subarray(ptr / 2, ptr / 2 + numCourses).fill(
65535
);
for (const key in refSchedule) {
// all section ids of the course with key=key in the reference schedule
const refSecs = refSchedule[key].reduce<number[]>((acc, x) => {
for (const id of x) acc.push(id);
return acc;
}, []);
for (let i = 0; i < numCourses; i++) {
for (let j = this.secLens[i]; j < this.secLens[i + 1]; j++) {
const secs = this.classList[j];
if (secs[0] === key && refSecs.some(id => secs[1].includes(id))) {
refScheduleEncoded[i] = j;
break;
}
}
}
}
this.Module._setRefSchedule(ptr);
}
public sort({ newOptions }: { newOptions?: EvaluatorOptions } = {}) {
if (!this.Module) return;
console.time('sort');
if (newOptions) this.options = newOptions;
this.Module!._setSortMode(this.options.mode);
// keep the order (!important!)
for (let i = 0; i < this.options.sortBy.length; i++) {
const option = this.options.sortBy[i];
this.Module!._setSortOption(
i,
+option.enabled,
+option.reverse,
option.idx,
option.weight || 0.0
);
}
this.Module!._sort();
console.timeEnd('sort');
}
/**
* Get a `Schedule` object at idx
*/
public getSchedule(idx: number) {
const Module = this.Module!;
const ptr = Module._getSchedule(idx) / 2;
return new GeneratedSchedule(
Array.from(Module.HEAPU16.subarray(ptr, ptr + this.secLens.length - 1)).map(
choice => this.classList[choice]
),
this.events
);
}
/**
* whether this evaluator contains an empty array of schedules
*/
public empty() {
return this.size === 0;
}
public getRange(opt: SortOption) {
if (!this.Module) return 1.0;
if (opt.name == 'IamFeelingLucky') return 1.0;
return this.Module!._getRange(opt.idx);
}
}
export default ScheduleEvaluator;