Covariance / CallChainSimplifier

Call-Chain simplifier for "Lightweight plugin API for IntelliJ" project JetBrains internship 2020.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CallChainSimplifier

Условия

Язык описания операций над целочисленными массивами задан следующей грамматикой:

<digit>   ::= “0” | “1" | “2” | “3" | “4” | “5" | “6” | “7" | “8” | “9"
<number> ::= <digit> | <digit> <number>
<operation> ::= “+” | “-” | “*” | “>” | “<” | “=” | “&” | “|”
<constant-expression> ::= “-” <number> | <number>
<binary-expression> ::= “(” <expression> <operation> <expression> “)”
<expression> ::= “element” | <constant-expression> | <binary-expression>
<map-call> ::= “map{” <expression> “}”
<filter-call> ::= “filter{” <expression> “}”
<call> ::= <map-call> | <filter-call>
<call-chain> ::= <call> | <call> “%>%” <call-chain>

Арифметические операции имеют стандартную семантику. Операция “&” это логическое “и”, операция “|” --- логическое “или“. Бинарные выражения с операторам “&”, “|” , “=”, “>”, “<” имеют булевый тип, а с операторами “+”, “-”, “*” --- арифметический. Операнды арифметических операций должны иметь целочисленный тип, а операнды логических --- булевый. Вызов функции map заменяет каждый элемент массива на результат вычисления переданного арифметического выражения, в котором вместо element подставляется значение текущего элемента. Вызов функции filter оставляет в массиве только элементы, для которых переданное выражение истинно.

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

Необходимо написать преобразователь выражений описываемых правилом <call-chain> в выражения вида <filter-call> “%>%” <map-call>, эквивалентные исходному. Решение должно принимать на стандартный поток ввода одну строку --- выражение описываемое правилом и выводить строку с преобразованным выражением. В случае наличия синтаксической ошибки, решение должно вывести SYNTAX ERROR, а если тип выражения не совпадает c ожидаемым TYPE ERROR.

Дополнительно, к решению предъявляются следующие требования:

  • решение должно быть выполнено на языке Java или Kotlin;
  • решение должно быть оформлено в виде публичного git репозитория;
  • код должен быть хорошо структурирован;
  • решение должно включать в себя тесты;

Бонус:

  • решение производит упрощение выражений;

Запуск решения

Есть два пути для запуска решения:

    1. Установить Apache Maven (или открыть проект в IntelliJ IDEA как Maven проект)
    2. Выполнить команду mvn -q compile exec:java 2>/dev/null (STDERR можно и не перенаправлять, но при определённых условиях Maven может выдавать WARNING: An illegal reflective access operation has occurred).
    3. Также можно выполнить встроенные тесты при помощи команды mvn test.
  1. Запустить скомпилированный файл Solution при помощи команды java -jar Solution.

Описание решения

Решение стоит разбить на две основных части: ru.covariance.jbintern.parser и simplifier.

Parser

Парсинг входного выражения осуществляется методом рекурсивного спуска. Проверка на соответствие типов выражений осуществляется здесь же, во время их разбора. Для отслеживания ошибок во время парсинга существует класс ParserException, имеющий двух наследников — TypeMismatchException и SyntaxException, отвечающих за соответствующий тип ошибок разбора выражения.

Simplifier

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

Второй этап упрощения представляет из себя свертку всей цепи вызовов в filter{...}%>%map{...}. Это делается за один линейный проход по цепи, который "собирает" все изменения element из мапперов и подставляет в последующие фильтры, формируя набор условий, которые должны выполнятся для начальных элементов. После этого прохода создаётся конъюнкция всех этих условий, которая и является единственным начальным фильтром, а начальным маппером становится композиция всех мапперов в цепи.

Третьим этапом упрощения является метод toMiniString(), который переопределяет вывод полиномов, упрощая их представление благодаря тому, что лишние нули в сложении и единицы в умножении не выводятся.

Возможные улучшения

Оба этапа решения могут быть до некоторой степени улучшены.

Parser

Единственное узкое место парсера — подсчёт значений полиномов, входящих в арифметические выражения. И если сложение и вычитание в нём реализованы за линейное время, что оптимально, то умножение работает за O(n2). Эту ассимптотику можно существенно улучшить при помощи использования FFT, которое позволяет перемножать многочлены за O(n·log(n)).

Simplifier

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

  • Разворота выражений в СДНФ или СКНФ с последующим удалением ненужных термов.
  • K-maps для упрощения булевых выражений, с возможным последующим применением алгоритма Куайна-МакКласки для упрощения поиска покрытий.

Для свёртки тоже существует ряд возможных оптимизаций:

  • Временная ассимптотика композиции многочленов зависит от временной ассимптотики умножения, которая, как описано выше, может быть существенно улучшена.
  • Чтобы не упрощать огромное булево выражение в фильтре после свёртки, можно упрощать их по отдельности, а только затем собирать в конъюнкцию (не реализовано, так как булевы выражения не упрощаются).

About

Call-Chain simplifier for "Lightweight plugin API for IntelliJ" project JetBrains internship 2020.


Languages

Language:Java 100.0%