123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- package schedule
- import (
- "os"
- "sort"
- "time"
- "errors"
- "math/rand"
- "encoding/json"
- )
- func init() {
- rand.Seed(time.Now().UnixNano())
- }
- func (gen *Generator) tryGenerate(
- subgroup SubgroupType,
- subjtype SubjectType,
- group *Group,
- subject *Subject,
- schedule *Schedule,
- countl *Subgroup,
- template []*Schedule,
- ) {
- flag := false
- startAgain:
- nextLesson: for lesson := uint(0); lesson < NUM_TABLES; lesson++ {
- // Если лимит пар на день окончен, тогда прекратить ставить пары группе.
- if gen.isLimitLessons(subgroup, countl) {
- break nextLesson
- }
- // Если это полная группа и у неё не осталось теоретических занятий, тогда пропустить этот предмет.
- if subjtype == THEORETICAL && !gen.haveTheoreticalLessons(subject) {
- break nextLesson
- }
- // Если это подгруппа и у неё не осталось практических занятий, тогда пропустить этот предмет.
- if subjtype == PRACTICAL && !gen.havePracticalLessons(subgroup, subject) {
- break nextLesson
- }
- // Если учитель заблокирован (не может присутствовать на занятиях) или
- // лимит пар на текущую неделю для предмета завершён, тогда пропустить этот предмет.
- if gen.inBlocked(subject.Teacher) || gen.notHaveWeekLessons(subgroup, subject) {
- break nextLesson
- }
- isAfter := false
- savedLesson := lesson
- // [ I ] Первая проверка.
- switch subgroup {
- case ALL:
- // Если две подгруппы стоят друг за другом, тогда исключить возможность добавления полной пары.
- for i := uint(0); i < NUM_TABLES-1; i++ {
- if gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) &&
- gen.cellIsReserved(B, schedule, i+1) && !gen.cellIsReserved(A, schedule, i+1) ||
- gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(A, schedule, i) &&
- gen.cellIsReserved(A, schedule, i+1) && !gen.cellIsReserved(B, schedule, i+1) {
- break nextLesson
- }
- }
- // Если между двумя разными подгруппами находятся окна, тогда посчитать сколько пустых окон.
- // Если их количество = 1, тогда попытаться подставить полную пару под это окно.
- // Если не получается, тогда не ставить полную пару.
- cellSubgroupReserved := false
- indexNotReserved := uint(0)
- cellIsNotReserved := 0
- for i := uint(0); i < NUM_TABLES; i++ {
- if gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) ||
- gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(A, schedule, i) {
- if cellIsNotReserved != 0 {
- if cellIsNotReserved == 1 {
- lesson = indexNotReserved
- goto tryAfter
- }
- break nextLesson
- }
- cellSubgroupReserved = true
- }
- if !gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) && cellSubgroupReserved {
- if cellIsNotReserved == 0 {
- indexNotReserved = i
- }
- cellIsNotReserved += 1
- }
- }
- switch {
- case lesson > 1 && (gen.cellIsReserved(A, schedule, lesson-1) || gen.cellIsReserved(B, schedule, lesson-1)) &&
- !(gen.cellIsReserved(A, schedule, lesson+1) || gen.cellIsReserved(B, schedule, lesson+1)): // pass
- default:
- // "Подтягивать" полные пары к уже существующим [перед].
- for i := uint(0); i < NUM_TABLES-1; i++ {
- if (gen.cellIsReserved(A, schedule, i+1) || gen.cellIsReserved(B, schedule, i+1)) &&
- (!gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i)) {
- lesson = i
- break
- }
- }
- }
- default:
- switch {
- case lesson > 1 && gen.cellIsReserved(subgroup, schedule, lesson-1) &&
- !gen.cellIsReserved(subgroup, schedule, lesson+1): // pass
- default:
- // "Подтягивать" неполные пары к уже существующим [перед].
- for i := uint(0); i < NUM_TABLES-1; i++ {
- if !gen.cellIsReserved(subgroup, schedule, i) && gen.cellIsReserved(subgroup, schedule, i+1) {
- lesson = i
- break
- }
- }
- }
- }
- tryAfter:
- if isAfter {
- switch subgroup {
- case ALL:
- // "Подтягивать" полные пары к уже существующим [после].
- for i := uint(0); i < NUM_TABLES-1; i++ {
- if (gen.cellIsReserved(A, schedule, i) || gen.cellIsReserved(B, schedule, i)) &&
- (!gen.cellIsReserved(A, schedule, i+1) && !gen.cellIsReserved(B, schedule, i+1)) {
- lesson = i+1
- break
- }
- }
- default:
- // "Подтягивать" неполные пары к уже существующим [после].
- for i := uint(0); i < NUM_TABLES-1; i++ {
- if (gen.cellIsReserved(ALL, schedule, i) || gen.cellIsReserved(subgroup, schedule, i)) &&
- !gen.cellIsReserved(subgroup, schedule, i+1) {
- lesson = i+1
- break
- }
- }
- }
- }
- var (
- cabinet = ""
- cabinet2 = ""
- )
- // Если ячейка уже зарезервирована или учитель занят, или кабинет зарезервирован, или
- // если это полная пара и ячейка занята либо первой, либо второй подгруппой, или
- // если это двойная пара и кабинеты второго учителя зарезервированы, или
- // если это двойная пара и второй учитель занят: попытаться сдвинуть пары к уже существующим.
- // Если не получается, тогда не ставить этот урок.
- if gen.cellIsReserved(subgroup, schedule, lesson) ||
- gen.teacherIsReserved(subject.Teacher, lesson) ||
- gen.cabinetIsReserved(subgroup, subject, subject.Teacher, lesson, &cabinet) ||
- (subgroup == ALL && (gen.cellIsReserved(A, schedule, lesson) || gen.cellIsReserved(B, schedule, lesson))) ||
- (gen.withSubgroups(group.Name) && gen.isDoubleLesson(group.Name, subject.Name) && gen.teacherIsReserved(subject.Teacher2, lesson)) ||
- (gen.withSubgroups(group.Name) && gen.isDoubleLesson(group.Name, subject.Name) && gen.cabinetIsReserved(subgroup, subject, subject.Teacher2, lesson, &cabinet2)) {
- if isAfter {
- break nextLesson
- }
- if lesson != savedLesson {
- isAfter = true
- goto tryAfter
- }
- continue nextLesson
- }
- // Если существует шаблон расписания, тогда придерживаться его структуры.
- if template != nil {
- for _, sch := range template {
- if sch.Group == group.Name {
- if sch.Table[lesson].Subject[A] == "" && sch.Table[lesson].Subject[B] == "" {
- if (gen.cellIsReserved(A, schedule, lesson) && !gen.cellIsReserved(B, schedule, lesson)) ||
- (gen.cellIsReserved(B, schedule, lesson) && !gen.cellIsReserved(A, schedule, lesson)) {
- break
- }
- lesson = savedLesson
- continue nextLesson
- }
- break
- }
- }
- }
- // Полный день - максимум 7 пар.
- // lesson начинается с нуля!
- if (gen.Day != WEDNESDAY && gen.Day != SATURDAY) && lesson >= 7 {
- break nextLesson
- }
- // В среду и субботу - 5-6 пары должны быть свободны.
- // lesson начинается с нуля!
- if (gen.Day == WEDNESDAY || gen.Day == SATURDAY) && (lesson == 4 || lesson == 5) {
- lesson = savedLesson // Возобновить сохранённое занятие.
- continue nextLesson // Перейти к следующей ячейке.
- }
- // [ II ] Вторая проверка.
- switch subgroup {
- case ALL:
- // Если уже существует полная пара, которая стоит за парами с подгруппами, тогда
- // перейти на новую ячейку расписания группы.
- for i := lesson; i < NUM_TABLES-1; i++ {
- if gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) && gen.cellIsReserved(ALL, schedule, i+1) ||
- gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(A, schedule, i) && gen.cellIsReserved(ALL, schedule, i+1) {
- lesson = savedLesson
- continue nextLesson
- }
- }
- default:
- // Если у одной подгруппы уже имеется пара, а у второй стоит пара
- // в это же время, тогда пропустить проверку пустых окон.
- if gen.cellIsReserved(A, schedule, lesson) && A != subgroup ||
- gen.cellIsReserved(B, schedule, lesson) && B != subgroup {
- goto passcheck
- }
- // Если стоит полная пара, а за ней идёт подгруппа неравная проверяемой, тогда
- // прекратить ставить пары у проверяемой подгруппы.
- fullLessons := false
- for i := uint(0); i < lesson; i++ {
- if gen.cellIsReserved(ALL, schedule, i) {
- fullLessons = true
- continue
- }
- if (fullLessons && gen.cellIsReserved(A, schedule, i) && A != subgroup) ||
- (fullLessons && gen.cellIsReserved(B, schedule, i) && B != subgroup) {
- break nextLesson
- }
- }
- }
- passcheck:
- // [ III ] Третья проверка.
- // Если нет возможности добавить новые пары без создания окон, тогда не ставить пары.
- if lesson > 1 {
- switch subgroup {
- case ALL:
- for i := uint(0); i < lesson-1; i++ {
- if (gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(A, schedule, lesson-1)) ||
- (gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(B, schedule, lesson-1)) {
- break nextLesson
- }
- }
- default:
- for i := uint(0); i < lesson-1; i++ {
- if gen.cellIsReserved(subgroup, schedule, i) && !gen.cellIsReserved(subgroup, schedule, lesson-1) {
- break nextLesson
- }
- }
- }
- }
- gen.Reserved.Teachers[subject.Teacher][lesson] = true
- gen.Reserved.Cabinets[cabinet][lesson] = true
- switch subgroup {
- case A:
- gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.A -= 1
- gen.Groups[group.Name].Subjects[subject.Name].Practice.A -= 1
- countl.A += 1
- case B:
- gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.B -= 1
- gen.Groups[group.Name].Subjects[subject.Name].Practice.B -= 1
- countl.B += 1
- case ALL:
- gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.A -= 1
- gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.B -= 1
- countl.A += 1
- countl.B += 1
- switch subjtype {
- case THEORETICAL:
- gen.Groups[group.Name].Subjects[subject.Name].Theory -= 1
- case PRACTICAL:
- gen.Groups[group.Name].Subjects[subject.Name].Practice.A -= 1
- gen.Groups[group.Name].Subjects[subject.Name].Practice.B -= 1
- }
- }
- if subgroup == ALL {
- schedule.Table[lesson].Teacher = [ALL]string{
- subject.Teacher,
- subject.Teacher,
- }
- schedule.Table[lesson].Subject = [ALL]string{
- subject.Name,
- subject.Name,
- }
- schedule.Table[lesson].Cabinet = [ALL]string{
- cabinet,
- cabinet,
- }
- // Если это двойная пара и группа делимая, тогда поставить пару с разными преподавателями.
- if gen.isDoubleLesson(group.Name, subject.Name) && gen.withSubgroups(group.Name) {
- gen.Reserved.Teachers[subject.Teacher2][lesson] = true
- gen.Reserved.Cabinets[cabinet2][lesson] = true
- schedule.Table[lesson].Teacher[B] = subject.Teacher2
- schedule.Table[lesson].Cabinet[B] = cabinet2
- }
- lesson = savedLesson
- continue nextLesson
- }
- schedule.Table[lesson].Teacher[subgroup] = subject.Teacher
- schedule.Table[lesson].Subject[subgroup] = subject.Name
- schedule.Table[lesson].Cabinet[subgroup] = cabinet
- lesson = savedLesson
- }
- if template != nil && !flag {
- flag = true
- goto startAgain
- }
- }
- func (gen *Generator) isLimitLessons(subgroup SubgroupType, countl *Subgroup) bool {
- switch subgroup {
- case ALL:
- if countl.A >= MAX_COUNT_LESSONS_IN_DAY && countl.B >= MAX_COUNT_LESSONS_IN_DAY {
- return true
- }
- case A:
- if countl.A >= MAX_COUNT_LESSONS_IN_DAY {
- return true
- }
- case B:
- if countl.B >= MAX_COUNT_LESSONS_IN_DAY {
- return true
- }
- }
- return false
- }
- func (gen *Generator) withSubgroups(group string) bool {
- if gen.Groups[group].Quantity > MAX_COUNT_WITHOUT_SUBGROUPS {
- return true
- }
- return false
- }
- func (gen *Generator) blockTeacher(teacher string) error {
- if !gen.inTeachers(teacher) {
- return errors.New("teacher undefined")
- }
- gen.Blocked[teacher] = true
- return nil
- }
- func (gen *Generator) inBlocked(teacher string) bool {
- if _, ok := gen.Blocked[teacher]; !ok {
- return false
- }
- return true
- }
- func (gen *Generator) inGroups(group string) bool {
- if _, ok := gen.Groups[group]; !ok {
- return false
- }
- return true
- }
- func (gen *Generator) inTeachers(teacher string) bool {
- if _, ok := gen.Teachers[teacher]; !ok {
- return false
- }
- return true
- }
- func (gen *Generator) unblockTeacher(teacher string) error {
- if !gen.inBlocked(teacher) {
- return errors.New("teacher undefined")
- }
- delete(gen.Blocked, teacher)
- return nil
- }
- func packJSON(data interface{}) []byte {
- jsonData, err := json.MarshalIndent(data, "", "\t")
- if err != nil {
- return nil
- }
- return jsonData
- }
- func unpackJSON(data []byte, output interface{}) error {
- err := json.Unmarshal(data, output)
- return err
- }
- func writeJSON(filename string, data interface{}) error {
- return writeFile(filename, string(packJSON(data)))
- }
- func (gen *Generator) subjectInGroup(subject string, group string) bool {
- if !gen.inGroups(group) {
- return false
- }
- if _, ok := gen.Groups[group].Subjects[subject]; !ok {
- return false
- }
- return true
- }
- func (gen *Generator) isDoubleLesson(group string, subject string) bool {
- if !gen.inGroups(group) {
- return false
- }
- if _, ok := gen.Groups[group].Subjects[subject]; !ok {
- return false
- }
- if gen.Groups[group].Subjects[subject].Teacher2 == "" {
- return false
- }
- return true
- }
- func shuffle(slice interface{}) interface{}{
- switch slice.(type) {
- case []*Group:
- result := slice.([]*Group)
- for i := len(result)-1; i > 0; i-- {
- j := rand.Intn(i+1)
- result[i], result[j] = result[j], result[i]
- }
- return result
- case []*Subject:
- result := slice.([]*Subject)
- for i := len(result)-1; i > 0; i-- {
- j := rand.Intn(i+1)
- result[i], result[j] = result[j], result[i]
- }
- return result
- }
- return nil
- }
- func (gen *Generator) cellIsReserved(subgroup SubgroupType, schedule *Schedule, lesson uint) bool {
- if lesson >= NUM_TABLES {
- return false
- }
- switch subgroup {
- case A:
- if schedule.Table[lesson].Subject[A] != "" {
- return true
- }
- case B:
- if schedule.Table[lesson].Subject[B] != "" {
- return true
- }
- case ALL:
- if schedule.Table[lesson].Subject[A] != "" && schedule.Table[lesson].Subject[B] != "" {
- return true
- }
- }
- return false
- }
- func (gen *Generator) cabinetIsReserved(subgroup SubgroupType, subject *Subject, teacher string, lesson uint, cabinet *string) bool {
- var result = true
- if !gen.inTeachers(teacher) {
- return result
- }
- for _, cab := range gen.Teachers[teacher].Cabinets {
- gen.cabinetToReserved(cab.Name)
- // Если это не компьютерный кабинет, а предмет предполагает практику в компьютерных кабинетах и идёт время практики,
- // тогда посмотреть другой кабинет преподавателя.
- if subject.IsComputer && !cab.IsComputer &&
- gen.havePracticalLessons(subgroup, subject) &&
- !gen.haveTheoreticalLessons(subject) {
- continue
- }
- // Если кабинет не зарезирвирован, тогда занять кабинет.
- if _, ok := gen.Reserved.Cabinets[cab.Name]; ok && !gen.Reserved.Cabinets[cab.Name][lesson] {
- *cabinet = cab.Name
- return false
- }
- }
- return result
- }
- func (gen *Generator) teacherIsReserved(teacher string, lesson uint) bool {
- gen.teacherToReserved(teacher)
- if value, ok := gen.Reserved.Teachers[teacher]; ok {
- return value[lesson] == true
- }
- return false
- }
- func (gen *Generator) teacherToReserved(teacher string) {
- if _, ok := gen.Reserved.Teachers[teacher]; ok {
- return
- }
- gen.Reserved.Teachers[teacher] = make([]bool, NUM_TABLES)
- }
- func (gen *Generator) cabinetToReserved(cabnum string) {
- if _, ok := gen.Reserved.Cabinets[cabnum]; ok {
- return
- }
- gen.Reserved.Cabinets[cabnum] = make([]bool, NUM_TABLES)
- }
- func (gen *Generator) notHaveWeekLessons(subgroup SubgroupType, subject *Subject) bool {
- switch subgroup {
- case A:
- if subject.WeekLessons.A == 0 {
- return true
- }
- case B:
- if subject.WeekLessons.B == 0 {
- return true
- }
- case ALL:
- if subject.WeekLessons.A == 0 && subject.WeekLessons.B == 0 {
- return true
- }
- }
- return false
- }
- func (gen *Generator) haveTheoreticalLessons(subject *Subject) bool {
- if subject.Theory == 0 {
- return false
- }
- return true
- }
- func (gen *Generator) havePracticalLessons(subgroup SubgroupType, subject *Subject) bool {
- switch subgroup {
- case A:
- if subject.Practice.A == 0 {
- return false
- }
- case B:
- if subject.Practice.B == 0 {
- return false
- }
- case ALL:
- if subject.Practice.A == 0 && subject.Practice.B == 0 {
- return false
- }
- }
- return true
- }
- func getGroups(groups map[string]*Group) []*Group {
- var list []*Group
- for _, group := range groups {
- list = append(list, group)
- }
- return shuffle(list).([]*Group)
- }
- func getSubjects(subjects map[string]*Subject) []*Subject {
- var list []*Subject
- for _, subject := range subjects {
- list = append(list, subject)
- }
- return shuffle(list).([]*Subject)
- }
- func sortSchedule(schedule []*Schedule) []*Schedule {
- sort.SliceStable(schedule, func(i, j int) bool {
- return schedule[i].Group < schedule[j].Group
- })
- return schedule
- }
- func colWidthForCabinets(index int) (int, int, float64) {
- var col = (index+1)*3+1
- return col, col, COL_W_CAB
- }
- // Returns [min:max] value.
- func random(min, max int) int {
- return rand.Intn(max - min + 1) + min
- }
- func readFile(filename string) string {
- file, err := os.Open(filename)
- if err != nil {
- return ""
- }
- defer file.Close()
- var (
- buffer []byte = make([]byte, BUFFER)
- data string
- )
- for {
- length, err := file.Read(buffer)
- if length == 0 || err != nil { break }
- data += string(buffer[:length])
- }
- return data
- }
- func writeFile(filename, data string) error {
- file, err := os.Create(filename)
- if err != nil {
- return err
- }
- file.WriteString(data)
- file.Close()
- return nil
- }
|