localdata.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. package schedule
  2. import (
  3. "os"
  4. "sort"
  5. "time"
  6. "errors"
  7. "math/rand"
  8. "encoding/json"
  9. )
  10. func init() {
  11. rand.Seed(time.Now().UnixNano())
  12. }
  13. func (gen *Generator) tryGenerate(
  14. subgroup SubgroupType,
  15. subjtype SubjectType,
  16. group *Group,
  17. subject *Subject,
  18. schedule *Schedule,
  19. countl *Subgroup,
  20. template []*Schedule,
  21. ) {
  22. flag := false
  23. startAgain:
  24. nextLesson: for lesson := uint(0); lesson < NUM_TABLES; lesson++ {
  25. // Если лимит пар на день окончен, тогда прекратить ставить пары группе.
  26. if gen.isLimitLessons(subgroup, countl) {
  27. break nextLesson
  28. }
  29. // Если это полная группа и у неё не осталось теоретических занятий, тогда пропустить этот предмет.
  30. if subjtype == THEORETICAL && !gen.haveTheoreticalLessons(subject) {
  31. break nextLesson
  32. }
  33. // Если это подгруппа и у неё не осталось практических занятий, тогда пропустить этот предмет.
  34. if subjtype == PRACTICAL && !gen.havePracticalLessons(subgroup, subject) {
  35. break nextLesson
  36. }
  37. // Если учитель заблокирован (не может присутствовать на занятиях) или
  38. // лимит пар на текущую неделю для предмета завершён, тогда пропустить этот предмет.
  39. if gen.inBlocked(subject.Teacher) || gen.notHaveWeekLessons(subgroup, subject) {
  40. break nextLesson
  41. }
  42. isAfter := false
  43. savedLesson := lesson
  44. // [ I ] Первая проверка.
  45. switch subgroup {
  46. case ALL:
  47. // Если две подгруппы стоят друг за другом, тогда исключить возможность добавления полной пары.
  48. for i := uint(0); i < NUM_TABLES-1; i++ {
  49. if gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) &&
  50. gen.cellIsReserved(B, schedule, i+1) && !gen.cellIsReserved(A, schedule, i+1) ||
  51. gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(A, schedule, i) &&
  52. gen.cellIsReserved(A, schedule, i+1) && !gen.cellIsReserved(B, schedule, i+1) {
  53. break nextLesson
  54. }
  55. }
  56. // Если между двумя разными подгруппами находятся окна, тогда посчитать сколько пустых окон.
  57. // Если их количество = 1, тогда попытаться подставить полную пару под это окно.
  58. // Если не получается, тогда не ставить полную пару.
  59. cellSubgroupReserved := false
  60. indexNotReserved := uint(0)
  61. cellIsNotReserved := 0
  62. for i := uint(0); i < NUM_TABLES; i++ {
  63. if gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) ||
  64. gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(A, schedule, i) {
  65. if cellIsNotReserved != 0 {
  66. if cellIsNotReserved == 1 {
  67. lesson = indexNotReserved
  68. goto tryAfter
  69. }
  70. break nextLesson
  71. }
  72. cellSubgroupReserved = true
  73. }
  74. if !gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) && cellSubgroupReserved {
  75. if cellIsNotReserved == 0 {
  76. indexNotReserved = i
  77. }
  78. cellIsNotReserved += 1
  79. }
  80. }
  81. switch {
  82. case lesson > 1 && (gen.cellIsReserved(A, schedule, lesson-1) || gen.cellIsReserved(B, schedule, lesson-1)) &&
  83. !(gen.cellIsReserved(A, schedule, lesson+1) || gen.cellIsReserved(B, schedule, lesson+1)): // pass
  84. default:
  85. // "Подтягивать" полные пары к уже существующим [перед].
  86. for i := uint(0); i < NUM_TABLES-1; i++ {
  87. if (gen.cellIsReserved(A, schedule, i+1) || gen.cellIsReserved(B, schedule, i+1)) &&
  88. (!gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i)) {
  89. lesson = i
  90. break
  91. }
  92. }
  93. }
  94. default:
  95. switch {
  96. case lesson > 1 && gen.cellIsReserved(subgroup, schedule, lesson-1) &&
  97. !gen.cellIsReserved(subgroup, schedule, lesson+1): // pass
  98. default:
  99. // "Подтягивать" неполные пары к уже существующим [перед].
  100. for i := uint(0); i < NUM_TABLES-1; i++ {
  101. if !gen.cellIsReserved(subgroup, schedule, i) && gen.cellIsReserved(subgroup, schedule, i+1) {
  102. lesson = i
  103. break
  104. }
  105. }
  106. }
  107. }
  108. tryAfter:
  109. if isAfter {
  110. switch subgroup {
  111. case ALL:
  112. // "Подтягивать" полные пары к уже существующим [после].
  113. for i := uint(0); i < NUM_TABLES-1; i++ {
  114. if (gen.cellIsReserved(A, schedule, i) || gen.cellIsReserved(B, schedule, i)) &&
  115. (!gen.cellIsReserved(A, schedule, i+1) && !gen.cellIsReserved(B, schedule, i+1)) {
  116. lesson = i+1
  117. break
  118. }
  119. }
  120. default:
  121. // "Подтягивать" неполные пары к уже существующим [после].
  122. for i := uint(0); i < NUM_TABLES-1; i++ {
  123. if (gen.cellIsReserved(ALL, schedule, i) || gen.cellIsReserved(subgroup, schedule, i)) &&
  124. !gen.cellIsReserved(subgroup, schedule, i+1) {
  125. lesson = i+1
  126. break
  127. }
  128. }
  129. }
  130. }
  131. var (
  132. cabinet = ""
  133. cabinet2 = ""
  134. )
  135. // Если ячейка уже зарезервирована или учитель занят, или кабинет зарезервирован, или
  136. // если это полная пара и ячейка занята либо первой, либо второй подгруппой, или
  137. // если это двойная пара и кабинеты второго учителя зарезервированы, или
  138. // если это двойная пара и второй учитель занят: попытаться сдвинуть пары к уже существующим.
  139. // Если не получается, тогда не ставить этот урок.
  140. if gen.cellIsReserved(subgroup, schedule, lesson) ||
  141. gen.teacherIsReserved(subject.Teacher, lesson) ||
  142. gen.cabinetIsReserved(subgroup, subject, subject.Teacher, lesson, &cabinet) ||
  143. (subgroup == ALL && (gen.cellIsReserved(A, schedule, lesson) || gen.cellIsReserved(B, schedule, lesson))) ||
  144. (gen.withSubgroups(group.Name) && gen.isDoubleLesson(group.Name, subject.Name) && gen.teacherIsReserved(subject.Teacher2, lesson)) ||
  145. (gen.withSubgroups(group.Name) && gen.isDoubleLesson(group.Name, subject.Name) && gen.cabinetIsReserved(subgroup, subject, subject.Teacher2, lesson, &cabinet2)) {
  146. if isAfter {
  147. break nextLesson
  148. }
  149. if lesson != savedLesson {
  150. isAfter = true
  151. goto tryAfter
  152. }
  153. continue nextLesson
  154. }
  155. // Если существует шаблон расписания, тогда придерживаться его структуры.
  156. if template != nil {
  157. for _, sch := range template {
  158. if sch.Group == group.Name {
  159. if sch.Table[lesson].Subject[A] == "" && sch.Table[lesson].Subject[B] == "" {
  160. if (gen.cellIsReserved(A, schedule, lesson) && !gen.cellIsReserved(B, schedule, lesson)) ||
  161. (gen.cellIsReserved(B, schedule, lesson) && !gen.cellIsReserved(A, schedule, lesson)) {
  162. break
  163. }
  164. lesson = savedLesson
  165. continue nextLesson
  166. }
  167. break
  168. }
  169. }
  170. }
  171. // Полный день - максимум 7 пар.
  172. // lesson начинается с нуля!
  173. if (gen.Day != WEDNESDAY && gen.Day != SATURDAY) && lesson >= 7 {
  174. break nextLesson
  175. }
  176. // В среду и субботу - 5-6 пары должны быть свободны.
  177. // lesson начинается с нуля!
  178. if (gen.Day == WEDNESDAY || gen.Day == SATURDAY) && (lesson == 4 || lesson == 5) {
  179. lesson = savedLesson // Возобновить сохранённое занятие.
  180. continue nextLesson // Перейти к следующей ячейке.
  181. }
  182. // [ II ] Вторая проверка.
  183. switch subgroup {
  184. case ALL:
  185. // Если уже существует полная пара, которая стоит за парами с подгруппами, тогда
  186. // перейти на новую ячейку расписания группы.
  187. for i := lesson; i < NUM_TABLES-1; i++ {
  188. if gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(B, schedule, i) && gen.cellIsReserved(ALL, schedule, i+1) ||
  189. gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(A, schedule, i) && gen.cellIsReserved(ALL, schedule, i+1) {
  190. lesson = savedLesson
  191. continue nextLesson
  192. }
  193. }
  194. default:
  195. // Если у одной подгруппы уже имеется пара, а у второй стоит пара
  196. // в это же время, тогда пропустить проверку пустых окон.
  197. if gen.cellIsReserved(A, schedule, lesson) && A != subgroup ||
  198. gen.cellIsReserved(B, schedule, lesson) && B != subgroup {
  199. goto passcheck
  200. }
  201. // Если стоит полная пара, а за ней идёт подгруппа неравная проверяемой, тогда
  202. // прекратить ставить пары у проверяемой подгруппы.
  203. fullLessons := false
  204. for i := uint(0); i < lesson; i++ {
  205. if gen.cellIsReserved(ALL, schedule, i) {
  206. fullLessons = true
  207. continue
  208. }
  209. if (fullLessons && gen.cellIsReserved(A, schedule, i) && A != subgroup) ||
  210. (fullLessons && gen.cellIsReserved(B, schedule, i) && B != subgroup) {
  211. break nextLesson
  212. }
  213. }
  214. }
  215. passcheck:
  216. // [ III ] Третья проверка.
  217. // Если нет возможности добавить новые пары без создания окон, тогда не ставить пары.
  218. if lesson > 1 {
  219. switch subgroup {
  220. case ALL:
  221. for i := uint(0); i < lesson-1; i++ {
  222. if (gen.cellIsReserved(A, schedule, i) && !gen.cellIsReserved(A, schedule, lesson-1)) ||
  223. (gen.cellIsReserved(B, schedule, i) && !gen.cellIsReserved(B, schedule, lesson-1)) {
  224. break nextLesson
  225. }
  226. }
  227. default:
  228. for i := uint(0); i < lesson-1; i++ {
  229. if gen.cellIsReserved(subgroup, schedule, i) && !gen.cellIsReserved(subgroup, schedule, lesson-1) {
  230. break nextLesson
  231. }
  232. }
  233. }
  234. }
  235. gen.Reserved.Teachers[subject.Teacher][lesson] = true
  236. gen.Reserved.Cabinets[cabinet][lesson] = true
  237. switch subgroup {
  238. case A:
  239. gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.A -= 1
  240. gen.Groups[group.Name].Subjects[subject.Name].Practice.A -= 1
  241. countl.A += 1
  242. case B:
  243. gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.B -= 1
  244. gen.Groups[group.Name].Subjects[subject.Name].Practice.B -= 1
  245. countl.B += 1
  246. case ALL:
  247. gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.A -= 1
  248. gen.Groups[group.Name].Subjects[subject.Name].WeekLessons.B -= 1
  249. countl.A += 1
  250. countl.B += 1
  251. switch subjtype {
  252. case THEORETICAL:
  253. gen.Groups[group.Name].Subjects[subject.Name].Theory -= 1
  254. case PRACTICAL:
  255. gen.Groups[group.Name].Subjects[subject.Name].Practice.A -= 1
  256. gen.Groups[group.Name].Subjects[subject.Name].Practice.B -= 1
  257. }
  258. }
  259. if subgroup == ALL {
  260. schedule.Table[lesson].Teacher = [ALL]string{
  261. subject.Teacher,
  262. subject.Teacher,
  263. }
  264. schedule.Table[lesson].Subject = [ALL]string{
  265. subject.Name,
  266. subject.Name,
  267. }
  268. schedule.Table[lesson].Cabinet = [ALL]string{
  269. cabinet,
  270. cabinet,
  271. }
  272. // Если это двойная пара и группа делимая, тогда поставить пару с разными преподавателями.
  273. if gen.isDoubleLesson(group.Name, subject.Name) && gen.withSubgroups(group.Name) {
  274. gen.Reserved.Teachers[subject.Teacher2][lesson] = true
  275. gen.Reserved.Cabinets[cabinet2][lesson] = true
  276. schedule.Table[lesson].Teacher[B] = subject.Teacher2
  277. schedule.Table[lesson].Cabinet[B] = cabinet2
  278. }
  279. lesson = savedLesson
  280. continue nextLesson
  281. }
  282. schedule.Table[lesson].Teacher[subgroup] = subject.Teacher
  283. schedule.Table[lesson].Subject[subgroup] = subject.Name
  284. schedule.Table[lesson].Cabinet[subgroup] = cabinet
  285. lesson = savedLesson
  286. }
  287. if template != nil && !flag {
  288. flag = true
  289. goto startAgain
  290. }
  291. }
  292. func (gen *Generator) isLimitLessons(subgroup SubgroupType, countl *Subgroup) bool {
  293. switch subgroup {
  294. case ALL:
  295. if countl.A >= MAX_COUNT_LESSONS_IN_DAY && countl.B >= MAX_COUNT_LESSONS_IN_DAY {
  296. return true
  297. }
  298. case A:
  299. if countl.A >= MAX_COUNT_LESSONS_IN_DAY {
  300. return true
  301. }
  302. case B:
  303. if countl.B >= MAX_COUNT_LESSONS_IN_DAY {
  304. return true
  305. }
  306. }
  307. return false
  308. }
  309. func (gen *Generator) withSubgroups(group string) bool {
  310. if gen.Groups[group].Quantity > MAX_COUNT_WITHOUT_SUBGROUPS {
  311. return true
  312. }
  313. return false
  314. }
  315. func (gen *Generator) blockTeacher(teacher string) error {
  316. if !gen.inTeachers(teacher) {
  317. return errors.New("teacher undefined")
  318. }
  319. gen.Blocked[teacher] = true
  320. return nil
  321. }
  322. func (gen *Generator) inBlocked(teacher string) bool {
  323. if _, ok := gen.Blocked[teacher]; !ok {
  324. return false
  325. }
  326. return true
  327. }
  328. func (gen *Generator) inGroups(group string) bool {
  329. if _, ok := gen.Groups[group]; !ok {
  330. return false
  331. }
  332. return true
  333. }
  334. func (gen *Generator) inTeachers(teacher string) bool {
  335. if _, ok := gen.Teachers[teacher]; !ok {
  336. return false
  337. }
  338. return true
  339. }
  340. func (gen *Generator) unblockTeacher(teacher string) error {
  341. if !gen.inBlocked(teacher) {
  342. return errors.New("teacher undefined")
  343. }
  344. delete(gen.Blocked, teacher)
  345. return nil
  346. }
  347. func packJSON(data interface{}) []byte {
  348. jsonData, err := json.MarshalIndent(data, "", "\t")
  349. if err != nil {
  350. return nil
  351. }
  352. return jsonData
  353. }
  354. func unpackJSON(data []byte, output interface{}) error {
  355. err := json.Unmarshal(data, output)
  356. return err
  357. }
  358. func writeJSON(filename string, data interface{}) error {
  359. return writeFile(filename, string(packJSON(data)))
  360. }
  361. func (gen *Generator) subjectInGroup(subject string, group string) bool {
  362. if !gen.inGroups(group) {
  363. return false
  364. }
  365. if _, ok := gen.Groups[group].Subjects[subject]; !ok {
  366. return false
  367. }
  368. return true
  369. }
  370. func (gen *Generator) isDoubleLesson(group string, subject string) bool {
  371. if !gen.inGroups(group) {
  372. return false
  373. }
  374. if _, ok := gen.Groups[group].Subjects[subject]; !ok {
  375. return false
  376. }
  377. if gen.Groups[group].Subjects[subject].Teacher2 == "" {
  378. return false
  379. }
  380. return true
  381. }
  382. func shuffle(slice interface{}) interface{}{
  383. switch slice.(type) {
  384. case []*Group:
  385. result := slice.([]*Group)
  386. for i := len(result)-1; i > 0; i-- {
  387. j := rand.Intn(i+1)
  388. result[i], result[j] = result[j], result[i]
  389. }
  390. return result
  391. case []*Subject:
  392. result := slice.([]*Subject)
  393. for i := len(result)-1; i > 0; i-- {
  394. j := rand.Intn(i+1)
  395. result[i], result[j] = result[j], result[i]
  396. }
  397. return result
  398. }
  399. return nil
  400. }
  401. func (gen *Generator) cellIsReserved(subgroup SubgroupType, schedule *Schedule, lesson uint) bool {
  402. if lesson >= NUM_TABLES {
  403. return false
  404. }
  405. switch subgroup {
  406. case A:
  407. if schedule.Table[lesson].Subject[A] != "" {
  408. return true
  409. }
  410. case B:
  411. if schedule.Table[lesson].Subject[B] != "" {
  412. return true
  413. }
  414. case ALL:
  415. if schedule.Table[lesson].Subject[A] != "" && schedule.Table[lesson].Subject[B] != "" {
  416. return true
  417. }
  418. }
  419. return false
  420. }
  421. func (gen *Generator) cabinetIsReserved(subgroup SubgroupType, subject *Subject, teacher string, lesson uint, cabinet *string) bool {
  422. var result = true
  423. if !gen.inTeachers(teacher) {
  424. return result
  425. }
  426. for _, cab := range gen.Teachers[teacher].Cabinets {
  427. gen.cabinetToReserved(cab.Name)
  428. // Если это не компьютерный кабинет, а предмет предполагает практику в компьютерных кабинетах и идёт время практики,
  429. // тогда посмотреть другой кабинет преподавателя.
  430. if subject.IsComputer && !cab.IsComputer &&
  431. gen.havePracticalLessons(subgroup, subject) &&
  432. !gen.haveTheoreticalLessons(subject) {
  433. continue
  434. }
  435. // Если кабинет не зарезирвирован, тогда занять кабинет.
  436. if _, ok := gen.Reserved.Cabinets[cab.Name]; ok && !gen.Reserved.Cabinets[cab.Name][lesson] {
  437. *cabinet = cab.Name
  438. return false
  439. }
  440. }
  441. return result
  442. }
  443. func (gen *Generator) teacherIsReserved(teacher string, lesson uint) bool {
  444. gen.teacherToReserved(teacher)
  445. if value, ok := gen.Reserved.Teachers[teacher]; ok {
  446. return value[lesson] == true
  447. }
  448. return false
  449. }
  450. func (gen *Generator) teacherToReserved(teacher string) {
  451. if _, ok := gen.Reserved.Teachers[teacher]; ok {
  452. return
  453. }
  454. gen.Reserved.Teachers[teacher] = make([]bool, NUM_TABLES)
  455. }
  456. func (gen *Generator) cabinetToReserved(cabnum string) {
  457. if _, ok := gen.Reserved.Cabinets[cabnum]; ok {
  458. return
  459. }
  460. gen.Reserved.Cabinets[cabnum] = make([]bool, NUM_TABLES)
  461. }
  462. func (gen *Generator) notHaveWeekLessons(subgroup SubgroupType, subject *Subject) bool {
  463. switch subgroup {
  464. case A:
  465. if subject.WeekLessons.A == 0 {
  466. return true
  467. }
  468. case B:
  469. if subject.WeekLessons.B == 0 {
  470. return true
  471. }
  472. case ALL:
  473. if subject.WeekLessons.A == 0 && subject.WeekLessons.B == 0 {
  474. return true
  475. }
  476. }
  477. return false
  478. }
  479. func (gen *Generator) haveTheoreticalLessons(subject *Subject) bool {
  480. if subject.Theory == 0 {
  481. return false
  482. }
  483. return true
  484. }
  485. func (gen *Generator) havePracticalLessons(subgroup SubgroupType, subject *Subject) bool {
  486. switch subgroup {
  487. case A:
  488. if subject.Practice.A == 0 {
  489. return false
  490. }
  491. case B:
  492. if subject.Practice.B == 0 {
  493. return false
  494. }
  495. case ALL:
  496. if subject.Practice.A == 0 && subject.Practice.B == 0 {
  497. return false
  498. }
  499. }
  500. return true
  501. }
  502. func getGroups(groups map[string]*Group) []*Group {
  503. var list []*Group
  504. for _, group := range groups {
  505. list = append(list, group)
  506. }
  507. return shuffle(list).([]*Group)
  508. }
  509. func getSubjects(subjects map[string]*Subject) []*Subject {
  510. var list []*Subject
  511. for _, subject := range subjects {
  512. list = append(list, subject)
  513. }
  514. return shuffle(list).([]*Subject)
  515. }
  516. func sortSchedule(schedule []*Schedule) []*Schedule {
  517. sort.SliceStable(schedule, func(i, j int) bool {
  518. return schedule[i].Group < schedule[j].Group
  519. })
  520. return schedule
  521. }
  522. func colWidthForCabinets(index int) (int, int, float64) {
  523. var col = (index+1)*3+1
  524. return col, col, COL_W_CAB
  525. }
  526. // Returns [min:max] value.
  527. func random(min, max int) int {
  528. return rand.Intn(max - min + 1) + min
  529. }
  530. func readFile(filename string) string {
  531. file, err := os.Open(filename)
  532. if err != nil {
  533. return ""
  534. }
  535. defer file.Close()
  536. var (
  537. buffer []byte = make([]byte, BUFFER)
  538. data string
  539. )
  540. for {
  541. length, err := file.Read(buffer)
  542. if length == 0 || err != nil { break }
  543. data += string(buffer[:length])
  544. }
  545. return data
  546. }
  547. func writeFile(filename, data string) error {
  548. file, err := os.Create(filename)
  549. if err != nil {
  550. return err
  551. }
  552. file.WriteString(data)
  553. file.Close()
  554. return nil
  555. }