Компания наша старается идти в ногу со временем, и поэтому вслед за тонким ангулярным клиентом возникла задача создания телеграм бота для нашей системы. В подробности его функционала вдаваться не буду, главное то, что поддержка уже написанного практиканткой пилота досталась мне, чему я в общем то рад - тема модная, молодежная :)
Чтобы лучше вникнуть в технологию, я решил написать свой простенький бот, решающий какую-то жизненную задачу. За такой задачей я обратился к своей девушке. Она к вопросу подошла обстоятельно; сразу вспомнила, чего ей не хватает в этой жизни для полного счастья… В общем, остановились на следующей задачке: бот должен принимать последовательность ингредиентов и возвращать рецепт, в котором они содержатся. При этом важное условие, что рецепты должны возвращаться в таком порядке, что других ингредиентов помимо введенных должно быть как можно меньше. Соответственно должна быть кнопка для следующего рецепта и для вывода всех ингредиентов.
По счастливой случайности у меня нашелся датасет с рецептами и ингредиентами) На самом деле, если бы это было не так, не думаю, что согласился бы на эту задачу (все-таки телеграм бот изучаю, а не big data). Однако, такой набор данных у меня был благодаря книге Основы Data Science и Big Data, где в одном из учебных примеров он был использован для описания графовой базы данных Neo4J. Именно ее я решил использовать как хранилище данных в моем небольшом боте, и в первой части я остановлюсь на ее описании. Подробности же написания самого бота будут в следующей части. Сразу скажу, что для работы с api использовалась C# библиотека TelegramBotCore, а ссылка на гитовый репозиторий находится в конце поста.
Что же такое графовая база данных? Думаю все знакомы с модным нынче направление NOSQL (не только сиквел). Так вот графовые базы - это одна из разновидностей таких БД. В основе таких баз данных лежат, как ни странно, узлы и связи. Они очень подходят для представления сильно связанных данных, где много отношений многие ко многим.
Neo4J является лидером среди таких БД. Чтобы развернуть ее у себя я воспользовался Docker (думаю про него будет много постов). Для этого введем в консоль следующую команду:
Теперь можно перейти на localhost:7474, ввести дефолтные логин и пароль (neo4j neo4j) и наслаждаться красотой интерфейса :) Этап проливки данных я пропущу. Сразу покажу результат простейшего запроса в графовом представлении. Зеленые кружочки - это рецепты, синие - ингредиенты. Все связи представляют собой ребра с меткой Содержит. Что же представляет из себя поисковый запрос в такой модели данных? Для таких нужд используется специальный язык запросов Cypher. Чтобы получить картинку выше, был выполнен следующий запрос:
Синтаксис достаточно очевидный: в круглых скобочках - узлы, в квадратных - связи. Внутри скобок можно ставить переменные, которые использовать далее в запросе. Ключевое слово LIMIT огранчивает количество выведенных результатов (если забудете его поставить, будьте готовы, что у Вас все повиснет при большом размере выдачи).
Теперь перейдем к нашей задаче и начнем писать первые запросы. Для начала найдем все рецепты, которые содержат переданные нами ингредиенты. Пусть для примера это будут Lemon, 7-Up и Ice (их мы можем увидеть на изображении выше). Запрос будет выглядеть следующим образом (представлена только часть результатов):
В результате выполнения мы получили все названия рецептов, в которых содержится хотя бы один из имеющихся у нас ингредиентов. Однако нам нужно выводить их в правильном порядке и кроме того все ингредиенты, которые они содержат. В данном запросе помимо названия рецепта, выведем все его составляющие:
Сначала мы нашли все рецепты, содержащие необходимые ингредиенты, а потом для этих рецептов сделали поиск всех их составляющих (здесь важно следить за правильным наименованием переменных: рецепты в обоих запросах одни и те же, однако связи и ингредиенты будут новыми). При выводе результатов использовалось ключевое слово collect для создания списка из набора элементов. Наконец напишем запрос, в результате которого будут получены рецепты вместе с количеством имеющихся одновременно и у нас и в рецепте продуктов, а также всех необходимых для приготовления:
Ключевое слово WITH необходимо для возможности обращения к переменным из предыдущего запроса. Чтобы вывести количество элементов так же как и в традиционном сиквеле используется функция count(). Cypher поддерживает множество не только аггрегационных функций, среди них и различные операции над строками, числами, списками. Все они подробно описаны в документации на официальном сайте. Тем временем единственное что нам осталось, это вывести результаты в порядке возрастания разницы между нужными и имеющимися ингредиентами. При этом в приоритете будут те блюда, в которых есть больше всего наших продуктов. Кроме того, добавим немножко регулярок) Благодаря функциям над списками и строками, можно включить в результаты также те рецепты которые начинаются с тех же символов, что ввели мы, но не обязательно совпадают. Такая потребность возникает, например при вводе в бот строки Potato, хотя в базе хранится Potatoes. Да, такое решение далеко от идеального (у нас есть яблоки, а будет предложен рецепт с яблочным сидром), но это ведь всего лишь учебный пример :)
Именно так выглядит окончательный вариант запроса. Как можно увидеть по тем немногим результатам, скрины которых я обозначил, качество датасета оставляет желать лучшего (чтоб Вы понимали: количество блюд, состоящих только из льда и лимона, равно 18!!!). Поэтому и телеграм бот имеет право на жизнь только в рамках изучения технологий. В следующей части займемся непосредственно написанием бота. Что же касается Neo4J, это классная графовая БД, использование которой оставляет только приятные впечатления.
Written on September 9th, 2017 by Alexey Kalina