-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.js
395 lines (360 loc) · 12.9 KB
/
main.js
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/**
* Convierte strings como "07:00" a objectos Date con la misma fecha
* @param {String} strHora El string de la forma "07:00"
* @returns {Date} Obj. Date con la hora.
*/
function strToDateHora(strHora){
return new Date('1970-01-01T'+strHora +'-06:00');
}
/**
* Lee y construye una clase en las clases de data.js
* @param {String} clave La clave de la clase a cargar.
* @returns {Clase} La clase cargada como obj. Clase.
*/
function loadClase(clave){
let clase=clases[clave];
let c=new Clase(
clase['nombre'],
clave,
loadGrupos(clase['grupos'],clave)
);
return c;
}
/**
* Dado el dict. de 'grupos' de una clase (data.js), construye sus objectos los regresa en lista.
* @param {*} gruposDict
* @param {String} claveClase
* @returns {[Grupo]} Lista de objs. Grupo.
*/
function loadGrupos(gruposDict,claveClase){
let out={};
for(grupo of gruposDict){
let inicio=grupo['inicio'];
let dtInicio=strToDateHora(inicio);
let fin=grupo['fin'];
let dtFin=strToDateHora(fin);
g=new Grupo(
grupo['grupo'],
claveClase,
loadProfesor(grupo['profesor']),
grupo['salon'],
grupo['dias'],
inicio,
fin,
dtInicio,
dtFin,
grupo['horario']
);
out[grupo['grupo']]=g;
}
return out;
}
/**
* Lee datos (data.js) y construye obj. de Profesor dado su nombre.
* Si no se encuentra al profesor en misProfesData, se asignan NaNs a
* 'general','n' y 'link'.
* @param {String} nombreProfesor
* @returns {Profesor} Obj. del profesor.
*/
function loadProfesor(nombreProfesor){
if(nombreProfesor in misProfesData)
return new Profesor(
nombreProfesor,
misProfesData[nombreProfesor]["general"],
misProfesData[nombreProfesor]["n"],
misProfesData[nombreProfesor]["link"]
);
return new Profesor(
nombreProfesor,
NaN,
NaN,
NaN,
);
}
// UTILS
/**
* Regresa un diccionario de la forma {'LU':[grupos con clases en lunes], etc.}
* NOTA: Solo incluye dias que tienen alguna clase.
* @param {[Grupo]} grupos Lista de objs. Grupo.
* @returns {Map<String,[Grupo]>} Dict. dia -> [objs. grupo]
*/
function gruposEnDias(grupos){
let out={};
for(let grupo of grupos){
for(let dia of grupo.dias){
if(!(dia in out)){
out[dia]=[];
}
out[dia].push(grupo);
}
}
return out;
}
/**
* Regresa el numero de empalmes de una lista de grupos ASUMIENDO que comparten dia.
* (Es decir, todos los grupos se asumen son del mismo dia)
* Usado como subrutina en empalmes().
* @param {[Grupo]} grupos Lista de objs. Grupo.
* @returns {Int} El no. de empalmes detectados.
*/
function empalmesMismoDia(grupos){ //TODO no toma en cuenta dias!!!!
// Hacemos copia local
let gruposOrdenados=grupos.slice();
// Ordena los grupos por hora de inicio
gruposOrdenados.sort((a,b)=> a.dtInicio.getTime()-b.dtInicio.getTime());
let count=0;
// Recorremos la lista ordenada
for(let i=0;i<grupos.length-1;i++){
// Si se cumple que la i-esima clase termina despues del inicio de la (i+1)-esima hay empalme.
if(gruposOrdenados[i].dtFin.getTime()>gruposOrdenados[i+1].dtInicio.getTime()){
count++;
}
}
return count; // TODO podriamos regresar cuales empalman para feedback al usuario.
}
/**
* Regresa el numero de empalmes entre una lista de grupos.
* @param {[Grupo]} grupos Lista de objs. Grupo.
* @returns {Int} El no. de empalmes detectados.
*/
function empalmes(grupos){
let count=0;
let gruposDias=gruposEnDias(grupos);
for(let dia in gruposDias){
let gruposEnDia=gruposDias[dia];
count+=empalmesMismoDia(gruposEnDia);
}
return count;
}
// ----FUNCION DE EVALUACION DE HORARIO-----
/**
* NOTA: Todas las componentes de la f. evaluadora deben regresar valores
* en [0,1] y entre mas alto mejor.
*/
/**
* Regresa el promedio normalizado [0-1] de las evaluaciones de los profesores de los grupos en el horario.
* Mayor promedio general de profesores implica mayor puntaje.
* @param {Horario} horario Obj. Horario al que calificar
* @returns {Float} Puntaje correspondiente
*/
function misProfesPuntaje(horario){
let suma=0;
let conGeneral=0;
for(let grupo of horario.grupos){
let general=grupo.profesor.misProfesGeneral;
if(!isNaN(general)){
suma+=general;
conGeneral+=1;
}
}
if(conGeneral>0){
let promedio=suma/conGeneral; // Vive en 0-10
return promedio/10; // Normalizamos
}else{
return 0;
}
}
/**
* Penaliza de acuerdo a la proporcion de clases en 'dia'.
* @param {Horario} horario Obj. Horario al que calificar
* @param {String} dia Dia para evaluar
* @returns {Float} Puntaje correspondiente
*/
function diaConMenosPuntaje(horario,dia){
let gruposDias=gruposEnDias(horario.grupos);
let gruposEnDia=0;
if(dia in gruposDias)
gruposEnDia=gruposDias[dia].length;
let totalGrupos=horario.grupos.length;
return 1-(gruposEnDia/totalGrupos);
}
/**
* Penaliza de acuerdo a la proporcion de dias que tiene que asistir.
* @param {Horario} horario Obj. Horario a evaluar.
* @returns {Float} Puntaje correspondiente.
*/
function menosDiasPuntaje(horario){
// Numero de dias que tiene que ir
let nDias=Object.keys(gruposEnDias(horario.grupos)).length;
return 1-(nDias/6);
}
/**
* Regresa la proporcion de grupos que caen dentro el rango indicado por el usuario.
* @param {Horario} horario Obj. Horario a evaluar
* @param {String} inicioRango Inicio del rango '07:00'
* @param {String} finRango Fin del rando '09:00'
* @returns {Float} Puntaje correspondiente
*/
function rangoPuntaje(horario,inicioRango,finRango){
// Convertimos a objs. Date
let inicio=strToDateHora(inicioRango);
let fin=strToDateHora(finRango);
// Calculamos cuantos caen dentro del rango
let caenEnRango=0;
for(let grupo of horario.grupos){
// Cae dentro del rango si su hora de inicio es mayor a la del rango
// y si su hora de fin es menor a la del rango.
if(inicio.getTime()<=grupo.dtInicio.getTime() && grupo.dtFin.getTime()<=fin.getTime())
caenEnRango+=1;
}
// Calculamos la proporcion
let totalGrupos=horario.grupos.length;
if(totalGrupos>0)
return caenEnRango/totalGrupos;
return 0;
}
/**
* Regresa el tiempo entre clases total en horas.
* Para cada dia se checan los grupos del dia y calcula el tiempo entre clases.
* Usado como subrutina en puntajeJuntasSeparadas().
* @param {Horario} horario Obj. Horario que contiene los grupos.
* @returns {Float} El no. de horas entre clases.
*/
function tiempoEntreClases(horario){
// Dia -> [Grupos que tienen clase]
let gruposDia=gruposEnDias(horario.grupos);
// Dia -> hrs entre clases
let tiempo={};
let total=0;
// Para cada dia
for(let dia in gruposDia){
let grupos=gruposDia[dia];
if(!(dia in tiempo))
tiempo[dia]=0;
// Ordenamos los grupos del dia por hr de inicio
grupos.sort((a,b)=> a.dtInicio.getTime()-b.dtInicio.getTime());
// Sumamos la diferencia en horas entre el comienzo de la (i+1)-esima clase y el fin de la i-esima.
for(let i=0;i<grupos.length-1;i++){
// Para convertir de milisegundos a hrs divide entre 60*60*1000=36e5
let dif=(grupos[i+1].dtInicio-grupos[i].dtFin)/36e5;
tiempo[dia]+=dif;
total+=dif;
}
}
return total;
}
/**
* TODO
* @param {*} horario
* @param {*} juntas
* @returns
*/
function horasMuertasPuntaje(horario){
let tiempo=tiempoEntreClases(horario);
return 1-(tiempo/(1+tiempo));
}
/**
* Calcula la evaluacion total de un horario usando las preferencias del usuario.
* Es un peso ponderado usando los pesos en preferencias.
* El resultado vive en [0,100].
* @param {Horario} horario Obj. Horario a evaluar
* @param {Preferencias} preferencias Obj. Preferencias que corresponden al usuario.
* @returns {Float} El puntaje correspondiente en [0,100].
*/
function evaluaHorario(horario,preferencias){
// Promedio de las evaluaciones de los profesores de los grupos en el horario
let promedioMisProfes=misProfesPuntaje(horario)*preferencias.misProfesPeso;
// Penaliza entre mas dias de la semana se tenga que atender
let menosDias=menosDiasPuntaje(horario)*preferencias.menosDiasPeso;
// Penaliza entre mas horas muertas entre clase
let horasMuertas=horasMuertasPuntaje(horario)*preferencias.horasMuertasPeso
// Rango horario
let rangoHorario=rangoPuntaje(horario,preferencias.rangoStart,preferencias.rangoEnd)*preferencias.rangoPeso;
// console.log(promedioMisProfes);
// console.log(menosDias);
// console.log(horasMuertas);
// console.log(rangoHorario);
let sumaPesos=preferencias.misProfesPeso+preferencias.menosDiasPeso+preferencias.horasMuertasPeso+preferencias.rangoPeso;
if(sumaPesos>0){
// Vive en [0,100]
let sumaPonderada=(promedioMisProfes+menosDias+horasMuertas+rangoHorario)/sumaPesos;
return sumaPonderada*100;
}else{
return 0;
}
}
// GENERADORES
// Ayudador recursivo
/**
* Genera horario recursivamente. Usado como subrutina en generarTodosHorarios().
* Guarda los horarios generados en la lista referenciada 'horarios'.
*
* NOTA: Asume que listaDeClases es tal que grupos con -LAB aparecen despues de su
* grupo de teoria.
*
* @param {[Clase]} listaDeClases
* @param {Int} i
* @param {Horario} horarioTemp
* @param {[Horario]} horarios
* @param {Bool} mismoGrupo Corresponde a preferencias.mismoGrupo.
* Indica si tomar el mismo grupo de teoria y laboratorio en caso de tener.
* @returns {null}
*/
function _generarTodosHorarios(listaDeClases,i,horarioTemp,horarios,mismoGrupo){
// Si ya recorrimos toda la lista
if(i>=listaDeClases.length){
// Y no hay empalmes en el horario que armamos
if(empalmes(horarioTemp.grupos)==0){
// Guardamos el horario
horarios.push(horarioTemp);
}
return;
}
// Si clase es lab y mismoGrupo=true agregamos el lab y recursamos
if(mismoGrupo && listaDeClases[i].nombre.indexOf('-LAB')>=0){
// Buscamos su grupo de teoria
for(let numeroGrupo in horarioTemp.grupos){
// Si la clave de la clase actual empieza con la clave del grupo
if(listaDeClases[i].clave.startsWith(horarioTemp.grupos[numeroGrupo].claveClase)){
// El numero de grupo con L al final para distinguirlo como LAB.
let n=horarioTemp.grupos[numeroGrupo].numero+'L';
let grupo=listaDeClases[i].grupos[n];
// Igual que normal (ver for de abajo)
let nuevosGrupos=horarioTemp.grupos.slice();
nuevosGrupos.push(grupo);
let h=new Horario(nuevosGrupos,0);
_generarTodosHorarios(listaDeClases,i+1,h,horarios,mismoGrupo);
return;
}else{
console.log("Todavia no hay teoria");
}
}
}
// Para cada grupo en la clase actual
for(let numeroGrupo in listaDeClases[i].grupos){
let grupo=listaDeClases[i].grupos[numeroGrupo];
// Le hacemos una copia a horarioTemp.grupos
let nuevosGrupos=horarioTemp.grupos.slice();
// A la cual le agregamos el grupo
nuevosGrupos.push(grupo);
// Creamos nuevo horarioTemp con nuevosGrupos
let h=new Horario(nuevosGrupos,0);
// Llamada recursiva
_generarTodosHorarios(listaDeClases,i+1,h,horarios,mismoGrupo);
}
}
/**
* Genera todas las combinaciones de grupos que no se empalman y regresa una lista
* de objectos Horario ordenada descendientemente por su puntaje.
*
* @param {Map<String,Clase>} clasesSeleccionadas Las clases seleccionadas por el usuario
* @param {Preferencias} preferencias Obj. de Preferencias correspondiente al usuario.
* @returns {[Horario]} Lista de horarios ordenada descendientemente por puntaje.
*/
function generarTodosHorarios(clasesSeleccionadas,preferencias){
// Convertimos clasesSeleccionadas (dict) a lista para usar en recursion
let listaDeClases=[];
for(let clave in clasesSeleccionadas)
listaDeClases.push(clasesSeleccionadas[clave]);
// Ordenamos para que labs siempre esten despues de teoria
listaDeClases.sort((a,b)=> a.clave.indexOf('LAB')-b.clave.indexOf('LAB'));
// Horarios generados
let horarios=[];
// Llamamos al generador recursivo
_generarTodosHorarios(listaDeClases,0,new Horario([],0),horarios,preferencias.mismoGrupo);
// Evaluamos y ordenamos descendientemente
for(let horario of horarios)
horario.puntaje=evaluaHorario(horario,preferencias);
horarios.sort((a,b)=> b.puntaje-a.puntaje);
return horarios;
}