Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Отчет по технологической практике.doc
Скачиваний:
3
Добавлен:
01.09.2019
Размер:
2 Мб
Скачать
☆

4 Разработка программного модуля

Используя знания об элементах языка VBA и объектной модели Excel, можно приступать к решению поставленной задачи – проектирование программного модуля, который будет выявлять накладки в расписании сессий.

В ходе разработки данного программного продукта были выделены этапы проектирования, которые формируют подробный фрагмент жизненного цикла, вплоть до реализации:

  1. Анализ системы, и анализ требований.

    1. Анализ системы, в которую будет внедрен модуль. Определение способа взаимодействия модуля с системой.

    2. Определение требований к программному продукту.

  2. Проектирование программного продукта.

    1. Разработка алгоритмов, определение используемых типов данных, переменных и констант, для процедур.

    2. Выделение компонентов программного продукта.

    3. Разработка пользовательского интерфейса.

  3. Кодирование.

  4. Тестирование.

    1. Тестирование процедур с помощью трассировки алгоритмов.

    2. Тестирование процедур с помощью испытаний.

  5. Сопровождение.

    1. Написание документации программы (которая включена в данный отчет).

    2. Написание руководства пользователя.

    3. Оценка модели заказчиком.

    4. Оценка эффективности программного продукта и рассмотрение перспектив модификаций.

Некоторые этапы могут быть объединены, другие – разделены, для более подробного описания, но, тем не менее, жизненный цикл требуемого программного продукта пройдет намеченный путь.

4.1 Анализ системы и требований

Согласно рисункам 1.1, 1.2 и 1.3 исходный xls файл имеет три вида таблиц – главная таблица, таблица сессий и таблица пересдач. Отсюда следует вывод, что, возможно, потребуется создать три процедуры, для каждой таблицы, ввиду того, что у таблиц разная структура, а для главной таблицы, возможно, понадобится элемент управления, который наполнит ее всеми записями из всех необходимых таблиц.

Исходя из требований к проекту, программа должна четко выявлять накладки, по разным критериям. То есть, надежная и безошибочная работа программы – основное требование, которое будет выполнено, путем разработки безошибочных алгоритмов.

4.2 Проектирование программного продукта

На данном этапе выполняется основная работа по созданию программного продукта – разработка архитектуры системы, выделение в ней процедур, вспомогательных функций и разработка алгоритмов.

4.2.1 Разработка алгоритмов, определение используемых типов данных, переменных и констант, для процедур

Как было упомянуто в пункте 4.1, вероятно, потребуется делать три процедуры, для разных видов таблиц. Однако данный подход противоречит хорошему стилю программирования – следует сделать одну параметризированную процедуру, которая может изменять свое поведение, в зависимости от параметров, а процедура главной таблицы должна будет вызывать эту процедуру с разными параметрами для своих подотчетных таблиц. Такой подход упростит отладку программы.

Итак, требуется разработать параметризированную процедуру выявления накладок для таблиц. Проще создать процедуру, которая будет заполнять главную таблицами записями определенной таблицы, чем создавать один большой алгоритм для проверки всех таблиц.

Разработанная процедура обладает следующим алгоритмом:

  1. Посчитать количество преподавателей в таблице «Всего по кафедрам».

  2. Для каждого преподавателя, в таблице «Всего по кафедрам» найти соответствие по ФИО в таблице, откуда вызвана процедура.

  3. В случае находки соответствия, запомнить дату проведения экзамена, и найти соответствие даты в таблице «Всего по кафедрам».

  4. Если соответствие найдено, то ищется свободная ячейка в соответствующем столбце, для соответствующего преподавателя, заодно проверяя, не занят ли преподаватель другим экзаменом в это же время. Если соответствия не найдено, то дата помечается красным, и алгоритм продолжает работу.

  5. Если свободной ячейки не найдено – генерируется ошибка, иначе свободная ячейка заполняется экзаменом.

  6. Проверяется весь столбец, на наличие коллизий, с помощью процедуры «ПроверкаНакладок».

Алгоритм процедуры проверки накладок имеет два входных параметра – это номер столбца, который нужно проверить и количество строк в таблице «Всего по кафедрам»:

  1. Посчитать количество непустых ячеек в столбце.

  2. Если количество непустых ячеек равно нулю, то выход из процедуры, иначе, выделяется память для массива пользовательского типа, с количеством ячеек равным количества непустых ячеек.

  3. Пользовательский массив заполняется данными из непустых ячеек.

  4. Находится минимальное и максимальное время проведения сессий.

  5. Итератор изменяется от минимального значения времени, до максимального, увеличиваясь на час в каждой итерации. Значение итератора сравнивается со значением времени каждого элемента, и в случае соответствия – элемент подвергается сортировке.

  6. В отсортированном, по времени, массиве, можно выделить группы однородных, по времени, объектов. Эти группы сортируются внутри себя, сначала по аудиториям, затем по потокам, и если внутри одной временной группы есть две одинаковые аудитории, или два одинаковых потока – происходит коллизия.

Вышеперечисленные алгоритмы детально описаны с помощью R-схем в приложении В.

4.2.2 Выделение компонентов программного продукта

Изначально, проект создавался в одном модуле, в котором были и процедуры, алгоритмы которых описаны выше, и константы, которые в них используются, и процедуры, которые используются для вызова этих процедур.

Однако этот подход превращает, даже такую скромную систему, в монолит. Стоит выделить в этой монолитной системе компоненты, что позволит упростить сопровождение продукта.

В результате анализа процедур, было выделено три модуля (рисунок 4.1):

- модуль процедур, выполняющих поставленную задачу (ядро);

- модуль процедур, вызывающих процедуры ядра (интерфейс);

- модуль констант, для настройки программы.

4.2.3 Разработка пользовательского интерфейса

Согласно требованиям заказчика, необходимо, чтоб заполнялась таблица «Всего по кафедрам» из остальных таблиц, и выявлялись накладки. Можно обеспечить выполнение соответствующих процедур двумя способами:

  1. Разместить на каждой таблице кнопку для заполнения данных из этой таблицы.

  2. Разместить на таблице «Всего по кафедрам» кнопку, которая заполнит эту таблицу всеми данными.

Оба способа имеют свои достоинства и недостатки. Если разместить на каждой таблице кнопку для вызова процедуры, то это может быть неудобным, когда нужно заполнить таблицу «Всего по кафедрам» полностью.

Рисунок 4.1 – Диаграмма компонентов системы

С другой стороны, если поместить кнопку на таблице «Всего по кафедрам», тогда пользователь будет лишен возможности проверять накладки исключительно для определенной таблицы.

Исходя из этой дилеммы, было принято решение: поместить кнопку вызова процедуры на каждой таблице, в том числе и на таблице «Всего по кафедрам», с той разницей, что та кнопка, по очереди вызывает процедуры от остальных кнопок (рисунок 4.2)

Кнопки, расположенные в таблицах «Пересдача» и «Пересдача (комиссии)» вызывают процедуру из модуля интерфейсов:

Sub ВызовДиспетчераДляПересдач()

ГенерацияОтчета 1, 2, 6, 3, 4, 5, 6, 20

End Sub

А кнопка в таблице «Расписание» вызывает другую процедуру, которая, в свою очередь, вызывает процедуру из ядра, с указанием других параметров:

Sub ВызовДиспетчераДляСдач()

ГенерацияОтчета 6, 7, 6, 3, 4, 5, 2, 20

End Sub

Рисунок 4.2 – Кнопки, для заполнения таблицы «Всего по кафедрам» из текущей таблицы

На рисунке 4.3 показаны две кнопки: одна кнопка, вызывает процедуру заполнения для всех таблиц («Заполнить»), а другая – очищает цвет ячеек для всех таблиц, и очищает рабочее поле таблицы «Всего по кафедрам» («Очистить»).

Рисунок 4.3 – Кнопки в таблице «Всего по кафедрам»

4.3 Кодирование

Процесс кодирования тесно сплетен с процессом проектирования. Ввиду того, что на разработку проекта были поставлены малые сроки, активно применялись парадигмы экстремального программирования, что значительно ускорило процесс написания исходного кода, командой из двух человек: код постоянно перерабатывался (принцип «рефакторинг»), релизы были короткими – интервал: два дня (принцип коротких интервалов), тестирование происходило сразу после написания процедур.

4.4 Сопровождение

Теперь, когда проект готов, следует написать соответствующую документацию, которая включает в себя данный отчет и руководство пользователя.

Руководство пользователя написано в приложении А.

4.4.1 Оценка эффективности программного продукта и рассмотрение перспектив модификаций

Испытания процедур показали, что обход ячеек таблиц занимает длительное время, что снижает эффективность программы. В дальнейшем, возможно, полное копирование данных из таблиц в виртуальную память, для быстрого доступа к этим данным. А так же – активное использование свойств Offset и конструкций With –End With.

Более того, программе не хватает диалоговых окон для предварительной настройки программы:

- настройка цветов ошибок;

- настройка параметров функций ядра;

- улучшение алгоритмов, использование более эффективных методов сортировки.

ВЫВОДЫ

Одних познаний в объектной модели Excel и элементов языка VBA недостаточно, для осуществления проекта на профессиональном уровне. Полученная программа, является результатом некого симбиоза дисциплин, которые изучались, по сей день:

- познания в теории алгоритмов позволили быстро соорудить рабочий алгоритм, который выполняет поставленную задачу;

- программная инженерия обеспечивает правильный стиль разработки – программа, из монолитной системы, превратилась в компонентную, а стиль написания кода, в том числе и правила именований переменных и констант, позволили создать удобочитаемый и легкосопровождаемый код;

- знание инженерных принципов создания ПО обеспечили комфортное, как для разработчиков, так и для заказчика, взаимодействие. Более того, применение парадигм технологий конструирования ПО систематизировало процесс разработки проекта, и применение методологий экстремального программирование повысило эффективность работы команды разработчиков;

- с помощью теории баз данных было выполнено преобразование отношения исходного объекта в более удобное, для анализа, что привело к упрощению обработки существующей базы данных;

- юзабилити, как подраздел эргономики, обеспечило создание удобного, для пользователя, интерфейса;

- элементы системного анализа, которые преподаются в дисциплине «Алгоритмы и структуры данных» позволили оценить эффективность полученных алгоритмов, более того, рассмотреть перспективу модификации алгоритмов, с целью повышения скорости работы программы.

Исходя из вышеописанного, следует прийти к выводу, что в ходе обучения были приобретены ценные навыки и знания, которые позволяют создавать эффективное, удобное, востребованное и легкосопровождаемое ПО.

ПРИЛОЖЕНИЕ А. РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ

Данный документ описывает способы взаимодействия с созданным продуктом. Данный программный продукт имеет только одну функциональность – заполнение таблицы «Всего по кафедрам» с выявлением накладок.

Однако использовать результаты проекта можно разными способами.

Первый способ заключается в использовании кнопки «Заполнить» в таблице «Всего по кафедрам», что приведет к вызову процедур из таблиц «Расписание», «Пересдача» и «Пересдача (комиссии)», предварительно зачистив рабочие поля таблиц. Следует заметить, что в случае переименования таблиц, процедура, закрепленная за кнопкой «Всего по кафедрам» перестанет функционировать. С обратной стороны, переименование таблицы «Всего по кафедрам» приведет к неработоспособности процедур ядра разработанных модулей. Эти и некоторые другие вопросы обсуждаются в настоящем руководстве.

А.1 Запуск процедур

Для запуска процедур из файла xls, который содержится в поставке, достаточно нажать кнопку «Заполнить таблицу «Всего по кафедрам»» в любой из перечисленных таблиц: «Расписание», «Пересдача», «Пересдача (комиссии)» (рисунок А.3).

Эти кнопки заполняют таблицу «Всего по кафедрам», без предварительной очистки последней, данными из таблиц, в которой находилась кнопка, вызвавшая процедуру заполнения.

Факт того, что кнопки не очищают таблицу «Всего по кафедрам» указывает на то, что можно дважды вызвать процедуру заполнения таблицы «Всего по кафедрам», и тогда возникнет столько накладок, столько записей в таблице, в которой дважды вызвана процедура заполнения таблицы минус количество преподавателей, которые есть в графике сессий, но нету в таблице «Всего по кафедрам» (рисунок А.1, А.2). Этот прием позволяет выявить тех преподавателей, которые не указаны в таблице «Всего по кафедрам» или преподавателей, ФИО которых написано некорректно.

Рисунок А.1 – Двойной вызов процедуры заполнения выявил, что доц.Ходикіна В.В. отсутствует в таблице «Всего по кафедрам»

Рисунок А.2 – Встроенный фильтр MS Excel подтвердил результат

Рисунок А.3 – Кнопки, для заполнения таблицы «Всего по кафедрам» из текущей таблицы

Также предоставляется возможность сразу заполнить таблицу «Всего по кафедрам», с помощью кнопки «Заполнить» в таблице «Всего по кафедрам» (рисунок А.4). Сначала очищается рабочее поле таблицы «Всего по кафедрам», затем очищается цвет ячеек всех таблиц, и поочередно вызываются процедуры для таблиц «Расписание», «Пересдача» и «Пересдача (комиссии)».

Рисунок А.4 – Элементы управления таблицы «Всего по кафедрам»

Кнопка «Очистить», очищает рабочее поле таблицы «Всего по кафедрам», и очищает цвета ячеек всех таблиц.

А.2 Возможные ошибки, и их исправление

Так как данный проект представляет собой первую версию программы, существуют ситуации, которые могут привести к сбою программы, и лишить ее работоспособности. Данный раздел описывает возможные ситуации, которые приведут к сбоям, и способы их решения.

А.2.1 Переименование таблиц

Как было упомянуто выше, в случае переименования таблиц, процедура, прикрепленная к кнопке «Заполнить» перестанет функционировать.

На рисунке А.5 продемонстрирован результат нажатия кнопки «Заполнить», при переименовании таблицы «Расписание» в «Расписание1».

В текущей поставке отсутствует возможность настройки программы с помощью оконного интерфейса, следовательно, для исправления этой ошибки, следует нажать Debug, и в выделенной строке переименовать таблицу (Рисунок А.6). После этого, следует нажать квадрат, на панели вверху (стоп), что остановит выполнение кода, и закрыть редактор кода.

После приведенных операций, можно снова запустить процедуру, и убедиться, что все работает (Рисунок А.7).

Рисунок А.5 – Ошибка процедуры, в случае переименования рабочих таблиц

→

Рисунок А.6 – Исправление ошибки неправильного имени таблицы

Рисунок А.7 – Программа работает корректно

А.2.2 Некорректное выделение полей

В результате корректировки структур таблиц, может произойти ситуация, изображенная на рисунке А.8.

Рисунок А.8 –Некорректное выделение полей

В таком случае необходимо обратиться к модулю интерфейсов, и изменить параметры вызова процедур (рисунок А.9).

→

Рисунок А.9 – Смена некорректных параметров вызова процедуры

В результате этих действий, программа снова работает корректно (рисунок А.10).

Рисунок А.10 – Результат корректировки параметров

Следует отметить, что некорректное задание полей приводит к некорректному анализу накладок, следовательно, приводит к ошибочной работе программы.

Так же, можно создать собственную кнопку, для вызова разработанной процедуры обработки специфической таблицы. Пусть в таблице будет 20 полей, если среди них будут корректные поля: ФИО, дата, время, аудитория, вид экзамена, дисциплина, то можно написать новый интерфейс, задав корректные параметры вызова, и процедура ядра будет правильно функционировать.

ПРИЛОЖЕНИЕ Б. ЛИСТИНГ МОДУЛЕЙ

Модуль мИнтерфейсы

'Этот модуль содержит процедуры, для вызова процедур обработки данных, в модулях, в котором выполняется сортировка, поиск накладок.

Option Explicit

Sub ВызовДиспетчераДляПересдач()

ГенерацияОтчета 1, 2, 3, 4, 5, 6, 6, 20

End Sub

Sub ВызовДиспетчераДляСдач()

ГенерацияОтчета 6, 7, 6, 3, 4, 5, 2, 20

End Sub

Sub ВызовСортировка()

Сортировка 6, 100

End Sub

Модуль мКонстанты

' Этот модуль содержит константы, для модулей:

' Константы, для настройки программы, в случае изменения структуры таблиц.

Public Const GLOBAL_FIO_ROW = 2 ' Строка, в которой записан первый преподаватель, в глобальной таблице.

Public Const GLOBAL_FIO_COLLUMN = 2 ' Столбец, в котором находятся преподаватели.

Public Const GLOBAL_STEP = 4 ' Максимальное количество экзаменов в день.

Public Const GLOBAL_TAB_NAME = "Всего по кафедрам" ' Имя главной таблицы.

Public Const TRACE_TAB_NAME = "Trace" ' Имя таблицы, в которой будет отображен результат работы алгоритма проверки на накладки. Позволяет, по желанию пользователя, отследить работу алгоритма на таблице.

Public Const GLOBAL_DATE_COLLUMN = 4 ' Столбец, с которого начинаются даты.

Public Const GLOBAL_DATE_ROW = 1 ' Строка, с которой начинаются даты.

Public Const max_holes = 50 ' Максимальное количество пустых строк между групп.

Модуль-ядро мCollisioner

'Option Explicit

Type TSession

iRow As Integer

strTime As String * 5

strRoom As String * 5

strGroup As String * 8

End Type

Sub ГенерацияОтчета(LOCAL_SUBJECT_COLLUMN As Integer, _

LOCAL_FIO_COLLUMN As Integer, _

LOCAL_DATE_COLLUMN As Integer, _

LOCAL_TIME_COLLUMN As Integer, _

LOCAL_ROOM_COLLUMN As Integer, _

LOCAL_TYPE_COLLUMN As Integer, _

LOCAL_START_ROW As Integer, _

LOCAL_MAX_HOLES As Integer)

' Основные переменные:

Dim nGlobalEmptyCount As Integer ' Счетчик пустых ячеек в таблице по кафедрам. Делает цикл обхода по глобальной таблице гибким. Цикл завершает работу, когда обнаруживаются четыре неиспользованные ячейки (для надежности).

Dim nLocalEmptyCount As Integer ' Счетчик пустых ячеек в текущей таблице.

Dim iGlobalTabWorker As Integer ' Переменная запоминает строку, в которой находится нужный сотрудник, в глобальной таблице.

Dim nGlobalRows As Integer 'Kоличество строк в таблице "Всего по кафедрам".

Dim nGlobalCollumns As Integer ' Kоличество столбцов в таблице "Всего по кафедрам".

Dim iLocalTabWorker As Integer ' Переменная запоминает строку, в которой находится нужный сотрудник, в локальной таблице (таблица экзаменов).

Dim iCount As Integer ' Счетчик ячеек, выделенных преподавателю.

Dim GlobalTime As Integer ' Время из ячейки глобальной таблицы, для поиска коллизий.

' Временные строки созданы для придания гибкости алгоритму,т.к. некоторые дисциплины, ФИО и т.п. растягиваются на две ячейки, при этом считается, что данные записаны только в первой ячейке, а остальные- пустые.

Dim strTmpFio As String ' Временная строка, для хранения фио преподавателя.

Dim strTmpGroup As String ' Временная строка, для хранения группы (последней попавшейся в таблице, используя свойство, описанное выше).

Dim strTmp() As String ' Временные строки, для получения времени, аудитории и группы, с помощью функции Split.

Dim bOccupied As Boolean ' Флаг, который указывает, что все ячейки преподавателя заняты.

Dim bCollision As Boolean ' Флаг, который указывает, что произошла накладка.

nGlobalEmptyCount = 0

nLocalEmptyCount = 0

nGlobalRows = GLOBAL_FIO_ROW

nGlobalCollumns = 4

iGlobalTabWorker = GLOBAL_FIO_ROW

iLocalTabWorker = LOCAL_START_ROW

CurrentSheet = ActiveSheet.Name

Application.StatusBar = "Начало работы"

If CurrentSheet = GLOBAL_TAB_NAME Then

' Если как-то получилось, что активная таблица - главная, то это ошибка и функция завершит свою работу.

Application.StatusBar = "Ошибка."

Exit Sub

End If

' В цикле выполняется подсчет ячеек преподавателей.

Application.StatusBar = "Подсчет ячеек."

Do While (nGlobalEmptyCount < 4)

If Worksheets(GLOBAL_TAB_NAME).Cells(nGlobalRows, GLOBAL_FIO_COLLUMN) = Empty Then

nGlobalEmptyCount = nGlobalEmptyCount + 1

Else:

nGlobalEmptyCount = 0

End If

nGlobalRows = nGlobalRows + GLOBAL_STEP

Loop

' Убирается лишний запас, ввиду особенности алгоритма.

nGlobalRows = nGlobalRows - GLOBAL_STEP * 4

' В цикле выполняется подсчет ячеек с датами.

Do While Worksheets(GLOBAL_TAB_NAME).Cells(GLOBAL_DATE_ROW, nGlobalCollumns) <> Empty

nGlobalCollumns = nGlobalCollumns + 1

Loop

nGlobalCollumns = nGlobalCollumns - 1

' Вход в основной цикл.

Application.StatusBar = "Обработка данных. Поиск накладок."

' Обход по рядам глобальной таблицы, начиная от первого преподавателя, с шагом 4 (т.е по каждому преподавателю проходим).

For iGlobalRows = GLOBAL_FIO_ROW To nGlobalRows Step 4

' Начинается обход по строкам с некой стартовой позиции. Для каждой таблицы приходится указывать свою стартовую строку. Это необходимо, чтоб обработать единый массив данных, т.к. обработка завершается, если есть пустые ячейки.

iLocalTabWorker = LOCAL_START_ROW

nLocalEmptyCount = 0

' Обход локальной таблицы для каждого преподавателя из внешнего цикла.

Do While nLocalEmptyCount < max_holes

Application.StatusBar = VBA.CStr(iGlobalTabWorker) + " " + Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker, GLOBAL_FIO_COLLUMN)

' Если первая ячейка пустует, значит, вся строка пустая, т.к. согласно свойствам таблиц - в первой ячейке всегда есть какое-то значение: толи это группа (как озаглавие группы записей, толи какая-то информация.

If Worksheets(CurrentSheet).Cells(iLocalTabWorker, 1) = Empty Then

nLocalEmptyCount = nLocalEmptyCount + 1

Else:

nLocalEmptyCount = 0

End If

' Если ячейка с фамилией не пуста - сохранить ее в временной переменной. Это необходимо, т.к. зачастую фамилии растягиваются на две строки, но при этом, в программе считается, что вторая строка пуста.

If Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_FIO_COLLUMN) <> Empty Then

strTmpFio = Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_FIO_COLLUMN)

End If

' Произвольно выбранный элемент сообщает своей пустотой, что текущая строка отображает группу. Ее тоже полезно запомнить.

If Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_DATE_COLLUMN) = Empty Then

strTmpGroup = Worksheets(CurrentSheet).Cells(iLocalTabWorker, 1)

End If

' Если ФИО преподавателя в локальной таблице совпадает с ФИО преподавателем в глобальной таблице, значит, пора искать соответствующий стоблец даты.

If Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker, GLOBAL_FIO_COLLUMN) = strTmpFio Then

Dim iDateCollumn As Integer

' Поиск соответствующей даты.

For iDateCollumn = GLOBAL_DATE_COLLUMN To nGlobalCollumns

If Worksheets(GLOBAL_TAB_NAME).Cells(GLOBAL_DATE_ROW, iDateCollumn) = Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_DATE_COLLUMN) Then

Exit For

End If

Next iDateCollumn

' Если соответсвующая дата не найдена, то

If iDateCollumn - 1 = nGlobalCollumns Then

' Перекрашиваем в цвет ошибки выхода за пределы дат.

Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_DATE_COLLUMN).Interior.Color = RGB(255, 100, 100)

' Вместо Continue в С

GoTo NextIteration

End If

' Далее, в указанном столбце дат, ищется свободная ячейка для преподавателя, и выполняется локальная проверка на накладки (то есть проверяется нагрузка на преподавателя).

bOccupied = True

bCollision = False

For iCount = 0 To 3

' Если есть пустая ячейка.

If Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker + iCount, iDateCollumn) = Empty Then

' То есть место, куда можно записать данные, следовательно, можно добавить новую нагрузку.

bOccupied = False

Exit For

Else: ' Ячейка занята другим экзаменом.

' Нужно проанализировать время экзамена в ячейке, и то, которое будет введено.

strTmp = Split(Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker + iCount, iDateCollumn))

Dim str As String

' Если новое время экзамена совпадает со временем, указанным в занятой ячейке, значит преподаватель, согласно этим данным, должен проводить два экзамена в одно время.

If strTmp(2) = Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_TIME_COLLUMN) Then

bCollision = True

' Если накладка есть, то время закрашивается в таблице, откуда взяты данные.

Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_TIME_COLLUMN).Interior.Color = RGB(255, 100, 100)

End If

End If

Next iCount

If bOccupied = True And iCount = 3 Then

MsgBox "Преподаватель" + Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_FIO_COLLUMN) + "перегружен.", vbOKCancel, "Предупреждение"

Else:

' VBA.CStr - решение против ошибки приведения типа. Присваивание не срабатывает, если в куче заносятся и числа и строки.

Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker + iCount, iDateCollumn) = strTmpGroup + " " + vbCrLf + "в " + CStr(Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_TIME_COLLUMN)) + " " + vbCrLf + "ауд " + CStr(Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_ROOM_COLLUMN)) + " - " + CStr(Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_TYPE_COLLUMN))

End If

End If ' end If Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker, GLOBAL_FIO_COLLUMN) = strTmpFio

' Вместо Continue в С

NextIteration:

iLocalTabWorker = iLocalTabWorker + 1

Loop ' end Do While nLocalEmptyCount < MAX_HOLES

' End If 'If Worksheets(GLOBAL_TAB_NAME).Cells(iGlobalTabWorker, GLOBAL_START) = Empty Then

iGlobalTabWorker = iGlobalTabWorker + GLOBAL_STEP

Next iGlobalRows

' После заполнения всей таблицы - вызываются процедуры выявления накладок.

For iGlobalCollumns = GLOBAL_DATE_COLLUMN To nGlobalCollumns

Application.StatusBar = CDate(Worksheets(GLOBAL_TAB_NAME).Cells(GLOBAL_DATE_ROW, iGlobalCollumns))

ПроверкаНакладок iGlobalCollumns, nGlobalRows

Next iGlobalCollumns

Application.StatusBar = "Готово."

End Sub

Function Парсер(Data As String) As Integer

' Функция, которая получает из строки две первые и две последние цифры. Функция строго заточена под получение дат.

Dim Answer As Integer

Dim tmpData As String

tmpData = Data

Answer = Val(tmpData)

Answer = Answer * 100

tmpData = Right(tmpData, 2)

Answer = Answer + Val(tmpData)

Парсер = Answer

End Function

Sub ПроверкаНакладок(iCollumn, nRows As Integer)

' Первое, что нужно сделать - преобразовать кортежи таким образом, чтоб с ними можно было работать. Данные будут храниться в оперативной памяти, в виде пользовательской структуры, следующего вида:

' <Данные> => <НомерСтроки,ВремяСдачи,Аудитория,Группа>.

' Далее, выполняется сортировка по времени сдачи.

' В результате сортировки - все данные разгруппируются, и внутри каждой группы выполняется сортировка по аудиториям, для выяснения накладок по аудитории, и тоже самое с группами.

Dim nSession As Integer ' Количество строк в новой таблице.

Dim pstrParser() As String ' Используется, как хранилище, для результатов работы функции Split.

Dim Session() As TSession ' Пустой массив, динамически определяется его размер.

Dim iSession As Integer ' Индексатор, для массива Сессия.

Dim bTab As Boolean ' Переменная, отвечающая за доступность дополнительной, трассировочной таблицы.

Dim iRows As Integer

' Внимание. Следующие константы работают только в текущей конфигурации записи в ячейку.

Const SPLIT_GROUP = 0 ' Индекс строки в массиве строк, полученных с помощью функции Сплит, который указывает на группу.

Const SPLIT_TIME = 2 ' Индекс времени.

Const SPLIT_ROOM = 4 ' Индекс аудитории.

bTab = False

' Поиск трассирочной таблицы.

For Each Item In Worksheets

If Item.Name = TRACE_TAB_NAME Then

bTab = True

' В случае нахождения трассировочной таблицы - очистить ее содержимое.

Item.Range("A1:av1000").Clear

End If

Next Item

If bTab = False Then

' Если таблицы нет - активировать глобальную таблицу.

Worksheets(GLOBAL_TAB_NAME).Activate

End If

' Подсчет непустых ячеек указанного столбца главной таблицы.

nSession = 0

With Worksheets(GLOBAL_TAB_NAME)

For iRows = GLOBAL_FIO_ROW To nRows

If .Cells(iRows, iCollumn) <> Empty Then

nSession = nSession + 1

End If

Next iRows

' Если в столбце нет данных - выход из функции.

If nSession = 0 Then

Exit Sub

End If

' Выделяется память под массив объектов Сессия и заполняется данными из таблицы.

ReDim Session(nSession)

For iRows = GLOBAL_FIO_ROW To nRows

If .Cells(iRows, iCollumn) <> Empty Then

pstrParser = Split(.Cells(iRows, iCollumn))

Session(iSession).iRow = iRows

Session(iSession).strRoom = pstrParser(SPLIT_ROOM)

Session(iSession).strTime = pstrParser(SPLIT_TIME)

Session(iSession).strGroup = pstrParser(SPLIT_GROUP)

iSession = iSession + 1

End If

Next iRows

End With

' Заполнение трассировочной таблицы.

If bTab = True Then

iRows = GLOBAL_FIO_ROW

With Worksheets(TRACE_TAB_NAME)

For iSession = 0 To nSession - 1

.Cells(iRows, 1) = Session(iSession).iRow

.Cells(iRows, 2) = Session(iSession).strRoom

.Cells(iRows, 3) = Session(iSession).strTime

.Cells(iRows, 4) = Session(iSession).strGroup

iRows = iRows + 1

Next iSession

.Cells(1, 1) = "Исходные данные"

End With

End If

' Сортировка данных по времени, с помощью функции Парсер.

Dim Min As Integer

Dim Max As Integer

Dim iSort As Integer

Dim iTime As Integer

Min = 9999

Max = 0

For iSession = 0 To nSession - 1

If Парсер(Session(iSession).strTime) < Min Then

Min = Парсер(Session(iSession).strTime)

End If

If Парсер(Session(iSession).strTime) > Max Then

Max = Парсер(Session(iSession).strTime)

End If

Next iSession

iSort = 0

Dim Tmp As TSession

' Поиск экзаменов, начиная от минимального времени, увеличиая образец на час. Например 9.00 10.00 11.00. Алгоритм для альфа-релиза.

For iTime = Min To Max Step 100

' Для образчика времени, выполняется проход по всему массиву.

For iSession = 0 To nSession - 1

' Если значение в ячейке совпадает с образцом, то

If Парсер(Session(iSession).strTime) = iTime Then

' скопировать в временную переменную текущую ячейку, в алгоритме сортировки

Tmp = Session(iSort)

' в текущую ячейку занести подходящую ячейку

Session(iSort) = Session(iSession)

' А в найденную ячейку заносится та ячейка, которая находилась в текущей ячейке алгоритма

Session(iSession) = Tmp

iSort = iSort + 1

End If

Next iSession

Next iTime

' Таким образом, ячейки постепенно выстраиваются, подобно сортировке игральных карт в руке.

If bTab = True Then

iRows = GLOBAL_FIO_ROW

With Worksheets(TRACE_TAB_NAME)

For iSession = 0 To nSession - 1

.Cells(iRows, 6) = CStr(Session(iSession).iRow)

.Cells(iRows, 7) = Session(iSession).strRoom

.Cells(iRows, 8) = Session(iSession).strTime

.Cells(iRows, 9) = Session(iSession).strGroup

iRows = iRows + 1

Next iSession

.Cells(1, 6) = "Сортировка по времени"

End With

End If

' Сортировка по группам. Ищется блок (группировка) по времени, и по этому блоку происходит сортировка по потокам.

Dim iLowBorder As Integer

Dim iHighBorder As Integer

iSession = 0

Do

' Выделение группы

iLowBorder = iSession

Do

iSession = iSession + 1

Loop While Session(iSession).strTime = Session(iSession - 1).strTime

iHighBorder = iSession - 1

' Обход по группе. Далее, аналогично с предыдущим алгоритмом.

Do While iLowBorder < iHighBorder

For iSort = iLowBorder + 1 To iHighBorder

If Session(iSort).strGroup = Session(iLowBorder).strGroup Then

iLowBorder = iLowBorder + 1

Tmp = Session(iSort)

Session(iSort) = Session(iLowBorder)

Session(iLowBorder) = Tmp

End If

Next iSort

iLowBorder = iLowBorder + 1

Loop

Loop While iSession < nSession

' Отсортировав по потокам внутри временных групп - выявляем накладки - в одной временной группе каждая группа не может встречаться больше одного раза.

iSession = 0

Do

iLowBorder = iSession

Do

iSession = iSession + 1

Loop While Session(iSession).strTime = Session(iSession - 1).strTime

iHighBorder = iSession - 1

Do While iLowBorder < iHighBorder

iLowBorder = iLowBorder + 1

If Session(iLowBorder).strGroup = Session(iLowBorder - 1).strGroup Then

' Красим клетку

Worksheets(GLOBAL_TAB_NAME).Cells(Session(iLowBorder).iRow, iCollumn).Interior.Color = RGB(100, 100, 100)

End If

Loop

Loop While iSession < nSession

If bTab = True Then

' Демонстрируем на таблице правильность сортировки

iRows = GLOBAL_FIO_ROW

With Worksheets(TRACE_TAB_NAME)

For iSession = 0 To nSession - 1

.Cells(iRows, 11) = CStr(Session(iSession).iRow)

.Cells(iRows, 12) = Session(iSession).strRoom

.Cells(iRows, 13) = Session(iSession).strTime

.Cells(iRows, 14) = Session(iSession).strGroup

iRows = iRows + 1

Next iSession

.Cells(1, 11) = "Сортировка по потокам, внутри группировки по времени."

End With

End If

' Всё повторяется, но для аудитории. Поскольку аудитория рассматривается как строка, алгоритм не изменится.

iSession = 0

Do

iLowBorder = iSession

Do

iSession = iSession + 1

Loop While Session(iSession).strTime = Session(iSession - 1).strTime

iHighBorder = iSession - 1

Do While iLowBorder < iHighBorder

For iSort = iLowBorder + 1 To iHighBorder

If Session(iSort).strRoom = Session(iLowBorder).strRoom Then

iLowBorder = iLowBorder + 1

Tmp = Session(iSort)

Session(iSort) = Session(iLowBorder)

Session(iLowBorder) = Tmp

End If

Next iSort

iLowBorder = iLowBorder + 1

Loop

Loop While iSession < nSession

' Отсортировав по потокам внутри временных групп - выявляем накладки - в одной временной группе каждая группа не может встречаться больше одного раза.

iSession = 0

Do

iLowBorder = iSession

Do

iSession = iSession + 1

Loop While Session(iSession).strTime = Session(iSession - 1).strTime

iHighBorder = iSession - 1

Do While iLowBorder < iHighBorder

iLowBorder = iLowBorder + 1

If Session(iLowBorder).strRoom = Session(iLowBorder - 1).strRoom Then

' Красим клетку

Worksheets(GLOBAL_TAB_NAME).Cells(Session(iLowBorder).iRow, iCollumn).Interior.Color = RGB(0, 255, 0)

End If

Loop

Loop While iSession < nSession

If bTab = True Then

' Демонстрируем на таблице правильность сортировки

iRows = GLOBAL_FIO_ROW

With Worksheets(TRACE_TAB_NAME)

For iSession = 0 To nSession - 1

.Cells(iRows, 16) = CStr(Session(iSession).iRow)

.Cells(iRows, 17) = Session(iSession).strRoom

.Cells(iRows, 18) = Session(iSession).strTime

.Cells(iRows, 19) = Session(iSession).strGroup

iRows = iRows + 1

Next iSession

.Cells(1, 16) = "Сортировка по аудиториям, внутри группировки по времени."

.Activate

End With

End If

If bTab Then

Application.StatusBar = "Проверка накладок завершена. Таблица отладки - доступна."

Else:

Application.StatusBar = "Проверка накладок завершена. Таблица отладки - недоступна."

End If

End Sub

ПРИЛОЖЕНИЕ В. АЛГОРИТМЫ

В.1 Алгоритм процедуры записи данных из текущей таблицы в главную, с выявлением накладок

Входные данные:

Имя

Тип

Назначение

LOCAL_SUBJECT_COLLUMN

Целочисленный

Обозначает столбец, в котором указаны дисциплины

LOCAL_FIO

_COLLUMN

Обозначает столбец, в котором указаны фамилии преподавателей.

LOCAL_DATE

_COLLUMN

Обозначает столбец, в котором указаны даты проведения сессий.

LOCAL_TIME

_COLLUMN

Обозначает столбец, в котором указано время начала каждой сессии.

LOCAL_ROOM

_COLLUMN

Обозначает столбец, в котором указаны аудитории, где проводятся сессии.

LOCAL_TYPE

_COLLUMN

Обозначает столбец, в котором указан тип сессии.

LOCAL_START

_ROW

Обозначает строку, в которой находятся первые записи в таблице.

LOCAL_MAX

_HOLES

Обозначает максимальное количество пустых строк, между двумя группами. Если количество пустых строк, для некой группы, превышает указанное число, то алгоритм остановит работу на этой группе. Параметр введен в связи с тем, что в таблице могут существовать группы, для которых еще не заполнены сессии.

Выходные данные: Процедура изменяет цвет ячеек, которые вызывают накладки.

Алгоритм:

Переменные:

Имя

Тип

Назначение

nLocalEmptyCount

Целочисленный

Счетчик пустых ячеек в текущей таблице.

nGlobalEmptyCount

Счетчик пустых ячеек в таблице по кафедрам. Делает цикл обхода по глобальной таблице гибким.

iGlobalTabWorker

Переменная запоминает строку, в которой находится нужный сотрудник, в глобальной таблице.

nGlobalRows

Kоличество строк в таблице "Всего по кафедрам".

nGlobalCollumns

Kоличество столбцов в таблице "Всего по кафедрам".

iLocalTabWorker

Переменная запоминает строку, в которой находится нужный сотрудник, в локальной таблице (таблица экзаменов).

iCount

Счетчик ячеек, выделенных преподавателю.

GlobalTime

Время из ячейки глобальной таблицы, для поиска коллизий.

strTmpFio

Строко-вый

Временная строка, для хранения фио преподавателя.

strTmpGroup

Временная строка, для хранения группы (последней попавшейся в таблице, используя свойство, описанное выше).

strTmp()

Указа-тель строко-вого типа

Временные строки, для получения времени, аудитории и группы, с помощью функции Split.

bOccupied

Логический

Флаг, который указывает, что все ячейки преподавателя заняты.

bCollision

Флаг, который указывает, что произошла накладка.

nGlobalEmptyCount = 0

nLocalEmptyCount = 0

nGlobalRows = GLOBAL_FIO_ROW

nGlobalCollumns = 4

iGlobalTabWorker = GLOBAL_FIO_ROW

iLocalTabWorker = LOCAL_FIO_ROW

CurrentSheet = ActiveSheet.Name

Пока

nGlobalEmptyCount < 4

Если

ГлавнаяТаблица.Cells(nGlobalRows, GLOBAL_FIO_COLLUMN) = 0

То

nGlobalEmptyCount ++

Иначе

nGlobalEmptyCount = 0

nGlobalRows = nGlobalRows + GLOBAL_STEP

Для

iGlobalRows = GLOBAL_FIO_ROW

До

nGlobalRows Шаг 4

iLocalTabWorker = LOCAL_START_ROW

nLocalEmptyCount = 0

Пока

nLocalEmptyCount< LOCAL_MAX_HOLES

Если

ТекущийЛист.Cells(iLocalTabWorker,1) = Empty

То

nLocalEmptyCount ++

Иначе

nLocalEmptyCount = 0

Если

ТекущийЛист.Cells(iLocalTabWorker,LOCAL_FIO_COLLUMN) ≠Empty

То

strTmpFio = ТекущийЛист.Cells(iLocalTabWorker, LOCAL_FIO_COLLUMN)

Если

ТекущийЛист.Cells(iLocalTabWorker,LOCAL_DATE_COLLUMN)

=Empty

То

strTmpGroup = ТекущийЛист.Cells(iLocalTabWorker, 1)

Если

ГлавныйЛист.Cells(iGlobalTabWorker,GLOBAL_FIO_COLLUMN)=

strTmpFio

То

Пер: iDateCollumn – Целочисленный

Для

iDateCollumn

= GLOBAL_DATE_COLLUMN

До

nGlobalCollumns

Если

ГлавныйЛист.Cells(GLOBAL_DATE_ROW, iDateCollumn)

= ТекущийЛист.Cells(iLocalTabWorker, LOCAL_DATE_COLLUMN)

То

Exit для

Если

iDateCollumn-1 = nGlobalCollumns

То

ТекущийЛист.Cells(iLocalTabWorket,LOCAL_DATE_COLLUMN)

=RGB(255,100,100)

след.итерация

Для

iCount = 0

До

3

Если

ТекущаяЯчейка.Cells(iGlobalTabWorker,

iDateCollumn)

= Empty

То

bOccupied = False

Exit для

Иначе

strTmp = Split(ГлавныйЛист).Cells(iGlobalTabWorker + iCount, iDateCollumn)

пер: str – строка

Если

strTmp[2] = ТекущийЛист.Cells( iLocalTabWorker, LOCAL_TIME_COLLUMN)

То

bCollision = True

ТекущийЛист.Cells(

iLocalTabWorker, LOCAL_TIME_COLLUMN) = RGB(255,100,100)

Если

bOccupied = True And iCount = 3

То

MsgBox “Преподаватель” + ТекущийЛист.Cells( iLocalTabWorker, LOCAL_FIO_COLLUMN) + “Перегружен”

Иначе

ГлавныйЛист.Cells(iGlobalTabWorker + iCount, iDateCollumn) =

strTmpGroup + " " + vbCrLf + "в " + CStr(Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_TIME_COLLUMN)) + " " + vbCrLf + "ауд " + CStr(Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_ROOM_COLLUMN)) + " - " + CStr(Worksheets(CurrentSheet).Cells(iLocalTabWorker, LOCAL_TYPE_COLLUMN))

iLocalTabWorker++

iGlobalTabWorker++

Для

iGlobalCollumns = GLOBAL_DATE_COLLUMN

До

nGlobalCollumns

ПроверкаНакладок (iGlobalCollumns,nGlobalRows

В.2 Алгоритм процедуры проверки накладок в главной таблице, в указанном столбце

Входные данные:

Имя

Тип

Назначение

iCollumn

Целочисленный

Указывает на столбец дат.

nRows

Вспомогательный параметр, который указывает на количество строк в таблице

Вспомогательная функция – Парсер – возвращает числовой эквивалент строки, которая обозначает время.

nSession = 0

пер: iRows – Целочисленный

Для

iRows = GLOBAL_FIO_ROW

До

nRows

Если

ГлавнаяТаблица.Cells(iRows,iCollumn) ≠ Empty

То

nSession ++

Если

nSession = 0

То

Exit Процедура

Выделение памяти для Session[nSession]

Для

iRows = GLOBAL_FIO_ROW

До

nRows

Если

ГлавнаяТаблица.Cells(iRows,iCollumn) ≠ Empty

То

Пер: pstrParser – указатель строкового типа

pstrParser = Split(ГлавнаяТаблица. Cells(iRows,iCollumn)

Session[iSession].iRow = iRows

Session[iSession].strRoom = pstrParser[SPLIT_ROOM]

Session[iSession].strTime = pstrParser [SPLIT_TIME]

Session[iSession].strGroup= pstrParser [SPLIT_GROUP]

iSession ++

Пер: Min,Max,iSort,iTime – Целочисленный

Для

iSession =0

До

iSession -1

Если

Парсер(Session[iSession].strTime <Min

То

Min = Парсер(Session [iSession].strTime

Если

Парсер(Session[iSession].strTime >Max

То

Max = Парсер(Session [iSession].strTime

iSort = 0

Пер: Tmp – Tsession

Пер: iTime – Целочисл

Для

iTime = Min

До

Max шаг 100

Для

iSession = 0

До

nSession -1

Если

Парсер (Session[iSession].strTime) = iTime

То

Tmp = Session [iSort]

Session [iSort] = Session [iSession]

Session[iSession] = Tmp

iSort ++

Пер: iLowBorder, iHighBorder – целочисл

iSession = 0

iLowBorder = iSession

iSession ++

Пока

Session[iSession].strTime = Session[iSession -1].strTime

iHighBorder = iSession -1

Пока

iLowBorder < iHighBorder

Для

iSort = iLowBorder + 1

До

iHighBorder

Если

Session[iSort].strGroup = Session[iLowBorder].strGroup

То

iLowBorder ++

Tmp = Session [iSort]

Session[iSort] = Session [iLowBorder]

Session[iLowBorder] = Tmp

Пока

iSession<nSession

iLowBorder = iSession

iSession ++

Пока

Session[iSession].strTime = Session[iSession -1].strTime

iHighBorder = iSession -1

Пока

iLowBorder < iHighBorder

iLowBorder ++

Если

Session [iLowBorder].strGroup = Session[iLowBorder – 1]. strGroup

То

ГлавнаяТаблица. Cells(Session(iLowBorder).iRow, iCollumn).Interior.Color = RGB(100, 100, 100)

Пока

iSession<nSession

iSession = 0

iLowBorder = iSession

iSession ++

Пока

Session[iSession].strTime = Session[iSession -1].strTime

iHighBorder = iSession -1

Пока

iLowBorder < iHighBorder

Для

iSort = iLowBorder + 1

До

iHighBorder

Если

Session[iSort].strRoom = Session[iLowBorder].strRoom

То

iLowBorder ++

Tmp = Session [iSort]

Session[iSort] = Session [iLowBorder]

Session[iLowBorder] = Tmp

Пока

iSession<nSession

iLowBorder = iSession

iSession ++

Пока

Session[iSession].strTime = Session[iSession -1].strTime

iHighBorder = iSession -1

Пока

iLowBorder < iHighBorder

iLowBorder ++

Если

Session [iLowBorder].strRoom = Session[iLowBorder – 1]. strRoom

То

ГлавнаяТаблица. Cells(Session(iLowBorder).iRow, iCollumn).Interior.Color = RGB(0,255, 0)

Пока

iSession<nSession

ВВЕДЕНИЕ 7