Определить и найти. Разработка алгоритма поиска изменений с Мap-ами и хешами

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Что-то изменилось?
Что-то изменилось?

Вступление, постановка задачи

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

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

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

Первое решение

Для начала, стоило реализовать простое обновление. Загрузил, преобразовал, сохранил. Приложение делало запрос на сервер и получала данные. Принятый JSON быстро разбирала библиотека GSON. Алгоритм проходил по сущностям, попутно собирая главное расписание в массив по дням. Информация преобразовывалась в строку и сохранялась в SharedPreferences, доставалась она оттуда уже при входе.

SharedPreferences — это

класс хранилища на платформе Android, используемый для хранения различной информации о конфигурации приложения. В нём данные сохраняются в XML-файл, в виде пар «ключ-значение».

Когда логика загрузки и сохранения была готова, стоял вопрос проверки. Нужно было как-то понять расходятся ли данные на сервере и на устройстве. Расписание могло часто меняться, несколько раз в день, а доступа к серверу у меня не было.

Я изначально стал хранить обработанный JSON расписания пользователя в памяти телефона. Поэтому, берём новый с сервера и достаем локальный. Обрабатываем и ищем различия по сущностям. Алгоритм, брал занятия пользователя, искал группу. Проходясь по ней, сравнивал информацию о паре. Нашли несоответствия. Данные сохраняем, пользователя уведомляем. Сложность всех действий была O(n) по размеру массива сохраненных занятий и работала довольно быстро, не учитывая сортировку по дням недели и времени.

Изначальный алгоритм
Изначальный алгоритм

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

Хорошо. Расширяем алгоритм, применяя логику к вспомогательным, а это уже становится довольно проблемно, выгрузи - найди - сравни. Хранятся они обособленно друг от друга. Расписания постоянно достаются из внутреннего хранилища, когда пользователь его ищет в сохраненных. А при обновлении - постоянно, по нескольку раз. И так с каждым. Добавление новых всё усложняло.

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

Добавление структуры данных HashMap

На этом моменте, я отошел от хранения полученного расписания в массиве классов, формировал структуру данных HashMap. Ключом являлась группа, преподаватель или кабинет, а значением - информация о занятиях. Ключи готовы и открывали дверь в быстрый поиск, теперь не нужно было искать по всем сущностям. Это позволяло применять коллекцию не только к задаче нахождения изменений, но и к поиску дополнительного расписания.

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

Алгоритм после добавления Hash
Алгоритм после добавления HashMap

Окончательная версия, хеш-функция

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

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

Коллизией хеш-функции называются

два разных набора сообщений a,b после преобразования которых получается одинаковый хеш-код hash(a) = hash(b).

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

Я использовал MD5. Он имеет низкий шанс коллизии у случайно взятых данных и высокую скорость работы. Хеш-функция ставит в соответствие входной строке 128 битную, разделенную на 32 символа в 16-ричном формате. Производим вычисления и получаем, что вероятность совпадения хеш-кодов у двух последовательных разных сообщений равна 1/2^{128} или 1/10^{38}.

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

Окончательная версия алгоритма
Окончательная версия алгоритма

Заключение

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

На этом можно считать задачу решенной, а рассказ завершенным. Благодарю за внимание!

Код функции генерации хеша
public static String generateHash(String data) throws NoSuchAlgorithmException {

  //объявляем переменную которая будет хранить хеш-код
  String hashCode;

  // читаем строку как массив байт 
  byte[] byteArray = data.getBytes();

  // выбираем алгоритм шифрования,
  // передаем на вход массив байт исходной строки
  byte[] byteHash = MessageDigest.getInstance("SHA-256").digest(byteArray);

  // полученный хеш - код в виде массива байт,
  // переводим в строку длинной 32 знака в шестнадцатеричном формате.
  hashCode = HashMethods.bytesToHexString(byteHash);

  // возвращаем
  return hashCode;
 }

Вспомогательный метод перевода байтового представления в строчное

Источник: https://habr.com/ru/articles/745676/


Интересные статьи

Интересные статьи

В камине потрескивали дрова. За окном лил проливной дождь. Промокшие молодые гномы рассаживались по креслам вокруг явно более старого гнома. Вы же знаете как отличить молодых гномов от старых? Они тог...
Если вдруг у кого-то не хватает мотивации, или кажется, что для поиска хорошей работы нужно приложить невероятных усилий. Собрали для вас три истории людей — QA, дизайнера и девелопера, &md...
Всех приветствую! Меня зовут Сергей и я алкоголик практикующий психолог. После предыдущих статей на сугубо психологическую и околопсихологическую тематику мне задали вопрос "а что же ты сюда пришёл? В...
Привет, Хабр!На связи Паша Емельянов, тимлид в AGIMA. В этой статье расскажу, как на одном из проектов мы переписывали старый функционал, разработанный когда-то на PHP, на Golang, с какими проблемами ...
Приветствую, Хабр! Некоторое время назад, компания NXP представила линейку процессоров iMX8. Было бы странно упустить возможность и не разработать новый модуль. Кому интересны нюансы, прош...