Можно прямо сейчас по ссылке, а можно локально:
- Склонировать репозиторий:
git clone https://github.com/cromtus/jb-preinternship-2020-callchain.git
- Скомпилировать в исполняемый jar:
./gradlew jar
и запустить собранный jar:java -jar build/libs/callchain.jar
.
или
- Вместо предыдущего запустить
./gradlew run
.
или
- Вместо предыдущего запустить Идею и в
src/main/kotlin/Main.kt
нажать зелёный треугольник рядом с методомmain
:)
Цепочка вызовов, подаваемая на вход, транслируется во внутреннее представление (Model
из пакета structures
) при помощи функции из пакета parser
. Во время парсинга может выброситься SyntaxError
или TypeError
. По идее, никаких других исключений парсер выбрасывать не может (если только все константные выражения помещаются в Int
).
Внутреннее представление допускает несоответствие типов (например, можно подставить булево выражение в map
), поскольку это допускает грамматика из условия задачи. Как уже было сказано, проверку типов делает парсер.
Для тестирования также создан пакет applicator
, который умеет применять цепочку вызовов из Model
к заданному массиву. Он тоже делает проверку типов, но выбрасывает RuntimeException
. Если Model
создана парсером, RuntimeException
невозможен.
Узкие места. Константные выражения хранятся в типе Int
, вычисления applicator
выполняет в нём же. Также не реализовано упрощение выражений. Все числовые выражения в данной грамматике — многочлены, поэтому, можно было бы, например, упрощать сравнения линейных функций (2 * x + 4 < x + 4 заменять на x < 0) и квадратичных (x * x - 2 * x + 1 > -3 заменять на 1 = 1). А на основе уже имеющегося кода можно было бы сделать вычисление константных выражений ((2+2) заменять на 4). Ещё в онлайн-демонстрации проблемы с русскими буквами: вместо SyntaxError
будет MalformedInputException
. Локально же всё работает хорошо.
Язык описания операций над целочисленными массивами задан следующей грамматикой:
<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>
, эквивалентные исходному. Решение должно принимать на стандартный поток ввода одну строку --- выражение описываемое правилом <call-chain>
и выводить строку с преобразованным выражением. В случае наличия синтаксической ошибки, решение должно вывести SYNTAX ERROR
, а если тип выражения не совпадает c ожидаемым TYPE ERROR
.
Дополнительно, к решению предъявляются следующие требования:
- решение должно быть выполнено на языке Java или Kotlin;
- решение должно быть оформлено в виде публичного git репозитория;
- код должен быть хорошо структурирован;
- решение должно включать в себя тесты;
Бонус:
- решение производит упрощение выражений;
Примеры ниже содержат один из корректных вариантов преобразования, ваш вариант не обязан совпадать с предложенным. Примеры:
In:
filter{(element>10)}%>%filter{(element<20)}
Out:
filter{(element>10)&(element<20)}%>%map{element}
In:
map{(element+10)}%>%filter{(element>10)}%>%map{(element*element)}
Out:
filter{((element+10)>10)}%>%map{((element+10)*(element+10))}
In:
map{(element+10)}%>%filter{(element>10)}%>%map{(element*element)}
Out:
filter{(element>0)}%>%map{((element*element)+((element*20)+100))}
In:
filter{(element>0)}%>%filter{(element<0)}%>%map{(element*element)
Out:
filter{(1=0)}%>%map{element}