[Из песочницы] Визуализация алгоритмов сортировки обменом на JavaScript
Доброго времени суток всем читателям и авторам habrahabr.ru. Речь в данной статье будет идти о визуализации простейших алгоритмов сортировки.
На выполнение данной работы меня вдохновил Timo Bingmann — аспирант из Института теоретической информатики и алгоритмов при Технологическом институте Карлсруэ (Германия) [1]. Тимом была написана отличная статья, где можно почитать немного о истории визуализаций и аудификаций алгоритмов [2]. Программисты, как никто знают, как тяжело идет процесс понимания абстрактных сущностей, и как сильно в этом помогают метафоры и методы визуализации. Когда какому-либо объекту из реальной жизни аналогично присваиваются свойства и методы виртуальных объектов.
Выбор алгоритмов
Признаюсь читателю, что программирование на сегодняшний день не является моей профессией. Разумеется, какие-то наработанные навыки у меня уже были. Это BASIC в 2007 г. (тогда я постиг ветвление, типы данных, массивы, каверзность оператора GOTO, удобство подпрограмм и впервые написал программу эмулирующую идеальный газ в закрытом сосуде. Если вы не знаете или не помните, в стандартном пакете BASIC, как базовая, была программа визуализации сортировок [3]). Еще из опыта был Pascal в 2012 г. В институте, в рамках изучения дискретной математики (писали последовательность Фибоначчи, операции со стеками и базовые сортировки). Исходя из моего опыта было принято решение начать с сортировок обменом как с чего-то знакомого и простого. Ниже я еще немного повествую, с какими сложностями мне пришлось столкнуться при визуализации иных типов сортировок.
Выбор инструментов
Ниже представлены инструменты, используемые для создания визуализации вышеупомянутых алгоритмов.
Язык программирования — JavaScript.
«Довольно необычный выбор!» — скажете вы и будете абсолютны правы. Действительно, на сегодняшний день существует множество языков программирования и библиотеки к ним, которые подходят для поставленной задачи гораздо лучше. Например C++. С применением C++ можно осуществлять управление памятью, а также рисовать графику на экране с использованием значительно меньших ресурсов вычислительной машины. Я не буду сильно углубляться в причины того, что моя жизненная ситуация требует моей быстрейшей переквалификации из исследователя естественных наук в знатока Web tech., в которых JavaScript используется повсеместно, потому что статья в целом получится слишком грустной и даст ненужные поводы для дискуссий в комментариях. Было принято решение совместить приятное с полезным, освежить свои академические знания и слегка подучить вышеупомянутый язык сценариев для web-страниц. И все же, для объективности, мы перечислим положительные и отрицательные стороны выбора:
Плюсы:
- Первый и весьма очевидный плюс — доступный всем пользователям интерпретатор. Если для просмотра работы Timo Bingmann нужно скачивать исполняемый файл на машину [4. Раздел Downloads / Open source]. То для исполнения программы на языке JavaScript достаточно установленного браузера в вашем электронном устройстве.
- Легко делиться своим творчеством с друзьями (достаточно скинуть им ссылку на демо в соц.сетях)
- Легкое оформление UI посредством HTML5 / CSS3. Данная связка быстро и надежно позволяет разместить элементы управления программой.
Минусы:
- Первый плюс несет в себе и отрицательные черты — интерпретация и исполнение кода на машине клиента ведет за собой нагрузку на CPU и RAM клиента. Для исполнения программы RAM требуется не так уж и много (около 1,5 мб). Но CPU i5 от Intel машины вашего автора, на самой высокой скорости при тестах был загружен на 35%. Следовательно, мы не исключаем тормозов и подлагиваний на слабых машинах и android-устройствах с малой производительностью.
- Поскольку CANVAS использует аппаратное ускорение [5] не исключаем нагрузку и на GPU.
Визуализация и отображение на экране — элемент HTML5: CANVAS.
CANVAS — это холст, на котором можно рисовать объекты посредством команд из js-сценария. У этого метода есть свои минусы, как например плохая масштабируемость при смене масштаба в браузере, в отличии от векторного SVG (который всегда идеален при любых масштабах). Но поскольку в нашем случае вся логика программы находится в js-файле — сложно найти элемент более подходящий для нашей задачи. Так же у CANVAS есть очевидные плюсы, например — масштабирование через CSS, которое автоматически так же масштабирует и систему координат, что позволяет нам осуществлять некоторую адаптивность без потери качества, с помощью media запросов. Уже неплохо! Так же, элемент CANVAS можно сохранить со страницы, нажав по холсту правой кнопкой мыши и выбрав в контекстном меню «сохранить». На этом мы завершим разговор о инструментах и переходим, собственно, к самой программе.
Псевдокод программы:
1. Объявить все глобальные переменные.
2. Подключить CANVAS для визуализации.
3. Подключить Web Audio API. и AudioContext () для аудификации.
4. Получить начальные значения от элементов управления в HTML разметке и записать их в переменные.
5. Исходя из начальных значений генерировать массив элементов и ожидать команд от пользователя.
6. При нажатии кнопки «Reset» стереть холст и посчитать новый массив элементов. Отобразить на экране новый массив.
7. При нажатии кнопки «Play» запустить сортировку по типу, указанному в панели настроек. Изменить вид кнопки на «Pause». При нажатии кнопки «Pause» приостановить сортировку.и изменить вид кнопки на «Play».
8. При изменении значений ползунка Change Speed изменить скорость сортировки.
9. При смене типа сортировки на панели настроек приостановить сортировку, если процесс сортировки инициирован.
10. При смене вида отображения сортировки изменить последовательность отрисовки в CANVAS. Не останавливая процесс сортировки.
11. Если сортировка завершена, сбросить все счетчики, кнопку «Pause» изменить на «Play», пройтись еще раз по массиву зеленым цветом визуально подтверждающим завершение сортировки и ожидать дальнейших действий от пользователя. (По факту в этот момент сортировка уже завершена. Этот пункт сделан чисто из эстетических соображений и со скидкой на человеческое восприятие)
UI
Как уже упоминалось нами выше, для создания UI в браузере мы будем использовать HTML5 и CSS3, нам понадобятся следующие элементы:
- Заголовок. Nuff said [6]
- Панель управления.
Тут будут размещен блок с ползунками настроек и выпадающий список для выбора типа сортировки. Сразу привяжем события к нашим ползункам и списку, чтобы обеспечить вызов необходимых функций в JavaScript-коде:HTML
Sphere Radius & Lines height / Brightness
Number of elements
Speed
Volume
- Кнопки старта, сброса и вида отображения.
Мы будем использовать три вида отображения сортировки на нашем холсте, помимо смещения порядка столбиков по длине (что уже стало классикой) это будет градиент от темного к светлому по степени интенсивности и окружность со смещением порядка столбиков по длине. Это будет небольшой новый вклад в визуализацию сортировок. Очевидно нам понадобятся кнопки, чтобы изменять тип отображения, и нам понадобятся кнопки управления «Play» и «Reset». Большинство классов в нижестоящем коде используются просто для оформления:
HTML
Transition это уникальное свойство добавленное в CSS3 позволяющее осуществлять плавное изменение между двумя состояниями элемента. Я не буду описывать очевидное оформление в этой статье, поскольку это не представляет никакого интереса, уделив внимание только transition. Класс transition выглядит так:.transition { transition: 0.4s; }
Ну и из интересного, это конечно масштабирование холста CANVAS в CSS3. Как уже упоминалось выше — вместе с элементом масштабируется система координат, что позволяет нам создавать какие угодно большие или малые отображения холста без всяких проблем с качеством и координатами. Например, порадуем владельцев HD мониторов:@media screen and (max-width: 2000px) { canvas { width: 1000px; height: 1000px; } ... }
Для мобайла был установлен размер холста в 800 px. - Собственно, сам холст.
- Подвал. Nuff Said
JavaScript-код
Сразу расстрою многих читателей. Я знаю, что на Хабрахабре любят ООП. Однако, программа была написана в процедурном стиле, поскольку на момент написания визуализации мой опыт был ограничен самостоятельным ковырянием в BASIC и решении некоторых задач по дискретной математике на Pascal. Более десяти лет назад я активно использовал подпрограммы в BASIC, и вот две недели назад изучив функции в JavaScript почувствовал ностальгию. Ведь основные задачи у подпрограмм и функций очень похожие, а именно сократить количество кода в программе обернув повторяющийся код в функцию/подпрограмму. Я не помню можно ли было отправлять в подпрограмму аргументы, если вы помните, то обязательно напишите это в комментариях под статьей. С ООП стилем я познакомился уже после того, как написал половину кода для визуализации. Мне очень понравится этот подход. ООП это определенно хорошо, но переписывать код я пожалуй не буду (к тому же ООП в JavaScript имеет свою специфику, кто знает — тот поймет).
Начнем мы по классике с блок-схемы, чтобы иметь общее представление о архитектуре программы:
Если Хабрахабр не умеет в масштабирование по клику — вот ссылка на вектор и растр.
Я не буду копировать сюда весь код из программы. Если кому-либо станет действительно интересно загляните в репозиторий на GitHub и ужасайтесь уже там. Пойдем с вами прямо по псевдокоду:
1. Объявить все глобальные переменные.
Ну тут все довольно просто. Программа содержит множество функций, зачастую использующих одинаковые переменные. Особенность JavaScript в том, что переменная объявленная внутри функции доступна только, собственно, внутри функции [7]. В то время, как переменная объявленная в глобальном пространстве видна везде. Это первая сложность, с которой я столкнулся. Пришлось немного почитать. Разумеется, по возможности мы будем объявлять переменные внутри функций. Итак, глобальными у нас будут переменные отвечающие за:
Процесс сортировки (сортировка в процессе/сортировка остановлена):
// sort state
var playing = false;
Значение радиуса окружности при загрузке страницы, используемого для отображения с окружностью (необходимо для функции изменения радиуса с совместной корректировкой размеров элемента массива, если этого не делать при увеличении радиуса столбики элементов массива будут выходить за холст):
//sphere radius global vars for RoundView
var radiusValue = +document.getElementById("sphereRadius").value,
diffRadiusValue = 0;
Массив с хекс цветами, к которому мы будем обращаться при раскраске холста. Использовался чтобы легко подстроить приятные цвета на холсте, этакая палитра. Так же переменная colorPick используется для того чтобы при сканировании массива выделить цветами сравниваемые элементы. Светло-зеленым будут выделены элементы которые стоят на своих местах, а светло-красным если элементы меняются местами:
var colors = ["#839192", "#80ffbf", "#566573", "#ff8080", "#00cc00", "#006600", "#c2d6d6"],
colorPick = 3;
2. Подключить CANVAS для холста и соединить его с HTML разметкой.
3. Подключить Audio API. AudioContext () для аудификации.
Эти два пункта псевдокода мы объединим, т.к. эти переменные можно логично объявить одним блоком. И в этом блоке сразу задаем значение переменной canvasSize (которая задействована при генерации длины элементов массива, нахождении координат середины холста, отрисовки окружности с отображением с окружностью) и мы сразу привязываем контроль громкости к нашему ползунку громкости из разметки:
//canvas pics & audio prep.
var audio = new AudioContext(),
volControl = document.getElementById("volume"),
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
canvasSize = 900;
Далее в коде объявляем пустой массив и вспомогательные глобальные переменные. Которые устанавливают стартовые настройки и используются в функциях сортировок, например, переменная отвечающая за направление движения для шейкерной сортировки, счетчики которые отслеживают завершенность массива и отвечают за завершение сортировки, шаг для сортировки расческой и пр. за все это в псевдокоде отвечает пункт 4. Задать начальные значения для элементов управления в HTML разметке.
5. Исходя из начальных значений сгенерировать массив элементов и ожидать команд от пользователя
Вызов функции:
calcNewArray();
Весь оставшийся код в файле .js представляет из себя декларирование функций, рассмотрим некоторые из них по мере необходимости, некоторые очевидные получат просто описание. Если вам интересно вы всегда можете посмотреть их в репозитории.
Итак, функция calcNewArray собирающая новый массив, берет значение количества элементов из ползунка в HTML разметке, значение размера холста и радиуса окружности для соответствующего вида вычитается из максимального размера элементов (чтобы при изменении радиуса элементы не выходили за пределы холста). Унарный »+» перед значениями получаемые от ползунков в разметке ставится обязательно, чтобы произвести преобразование типа данных «String» → «Number». Первый цикл for заполняет наш массив элементами с максимально допустимой длиной (по холсту), второй цикл равномерно распределяет длину элементов. В завершении функции сбрасываются все дополнительные параметры для сортировок и вызываются функции которые перемешивают массив и обновляют холст:
function calcNewArray() {
array=[];
for (var i = 0; i < +document.getElementById("elementsNumb").value; i++) {
array.push(Math.round(( (canvasSize/2) - (+document.getElementById("sphereRadius").value) - 60)));
}
for (i = 0; i < array.length; i++) {
array[i] = array[i] * ((i)*((0.64/array.length))) + 90;
}
step = array.length-2;
time = 0;
direction = true;
compareRandom(array);
resetImage();
}
}
Изначально я просто заполнял массив случайными элементами в промежутке от 50 до длины холста с вычетом радиуса, однако случайная генерация не дает такой ровный и красивый массив!
Далее опишем функцию startOsc отвечающую за звук при проверке элементов. Функция принимает на вход параметр частоты и представляет из себя осциллятор, последовательно подключенный к гейну. Чтобы звук сильно не резал слух при обрыве осциллятора, мы указываем время нарастания (атаки) звуковой волны и затухания.
Кто хоть раз подключал электрогитару — тот примерно поймет принцип работы WebAudioApi, те, кто не подключал, может прочитать об этом примерно тут [8] и более простым языком здесь [9] и для маньяков, которые хотят писать музыку есть фундаментальная книга «Web audio api» издательства O’Reilly:
function startOsc(freq) {
var attack = 25,
decay = 60,
gain = audio.createGain(),
osc = audio.createOscillator();
gain.connect(audio.destination);
gain.gain.setValueAtTime(0, audio.currentTime);
gain.gain.linearRampToValueAtTime(volControl.value, audio.currentTime + attack / 700);
gain.gain.linearRampToValueAtTime(0, audio.currentTime + decay / 700);
osc.frequency.value = (freq+100)*1.4;
osc.type = "square";
osc.connect(gain);
osc.start(0);
setTimeout(function() {
osc.stop(0);
osc.disconnect(gain);
gain.disconnect(audio.destination);
}, decay);
}
Далее по псевдокоду: 6. При нажатии кнопки «Reset» стереть холст, посчитать новый массив элементов
Здесь все довольно просто, в HTML разметке внутри блока нами указывается вызов функции при обработке события onclick:
Заключение
В целях изучения HTML5, CSS3, JavaScript, элемента CANVAS, WebAudio API, нами была разработана страница, которая визуализирует простейшие алгоритмы сортировки обменом. Такие сортировки, как расческа, шейкерная сортировка и сортировка пузырьком. Во время разработки у автора возникло много вопросов, т.к. автор еще неопытен. Количество строк многострадального кода ≈500. Количество затраченного времени ≈2 недели в неспешном темпе. Количество полученного удовольствия от достигнутого результата — не исчисляется цифрами. Спасибо Вам, что прочитали мою первую статью.
Ссылка на демо (бесплатный хостинг на bitbucket, не уверен в корректной работе при большом количестве посетителей.
Ссылка на YouTube плейлист с демонстрацией визуализации
Ссылки на используемую литературу и источники указанные в статье
Репозиторий с кодом из статьи на моем GitHub | Блок-схема в растровом изображении
[1] GitHub Timo Bingmann
[2] Институт теоретической информатики и алгоритмов при Технологическом институте Карлсруэ
[3] Ссылка на видео демонстрации программы визуализации сортировок, которая входила в стандартный пакет BASIC
[4] Статья Timo Bingmann о визуализации алгоритмов, можно скачивать уже скомпилированый исполняемый файл на машину в разделе Downloads / Open source
[5] Статья на ixbt о аппаратном ускорении в браузерах, в частности о CANVAS / Игорь Дериев — 2011
[6] Тут мне нечего добавить
[7] Хорошая статья на Хабрахабр о области видимости переменных в JavaScript и не только / Алексей — 2011
[8] Спецификация Web Audio API на сайте фаерфокса
[9] Начальное руководство и первые шаги в Web Audio API / Boris Smus — последнее обновление 2013
[10] Хорошая статья на Хабрахабре «CANVAS шаг за шагом: Основы» / Alexander Shpak — 2011
[11] Подробная, качественная и годная спецификация по CANVAS от w3s
[12] Фундаментальная серия томов по алгоритмам «The Art of Computer Programming (TAOCP)» / Donald Knuth — выпускается с 1968
Комментарии (3)
26 февраля 2017 в 17:14 (комментарий был изменён)
+1↑
↓
Вот ещё ссылок в тему:- https://bost.ocks.org/mike/algorithms/ очень клёвый.
- А https://www.youtube.com/watch? v=ywWBy6J5gz8 весёлый: Quicksort
26 февраля 2017 в 17:35
+1↑
↓
Венгерский народный танец нам ещё на первом семестре показывали :)
26 февраля 2017 в 17:30
+1↑
↓
«Аудификация» в данном случае каким образом способствует «процессу понимания абстрактных сущностей»?