migration
This commit is contained in:
458
README.md
Normal file
458
README.md
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
# Тесты к курсу «Парадигмы программирования»
|
||||||
|
|
||||||
|
[Условия домашних заданий](https://www.kgeorgiy.info/courses/paradigms/homeworks.html)
|
||||||
|
|
||||||
|
## Домашнее задание 9. Линейная алгебра на Clojure [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Базовая* ✅
|
||||||
|
* Код должен находиться в файле `clojure-solutions/linear.clj`.
|
||||||
|
* [Исходный код тестов](clojure/cljtest/linear/LinearTest.java)
|
||||||
|
* Запускать с указанием сложности (`easy` или `hard`) и модификации.
|
||||||
|
|
||||||
|
|
||||||
|
## Исходный код к лекциям по Clojure
|
||||||
|
|
||||||
|
Документация
|
||||||
|
* [Clojure Reference](https://clojure.org/reference/documentation)
|
||||||
|
* [Clojure Cheat Sheet](https://clojure.org/api/cheatsheet)
|
||||||
|
|
||||||
|
Запуск Clojure
|
||||||
|
* Консоль: [Windows](clojure/RunClojure.cmd), [*nix](clojure/RunClojure.sh)
|
||||||
|
* Интерактивный: `RunClojure`
|
||||||
|
* С выражением: `RunClojure --eval "<выражение>"`
|
||||||
|
* Скрипт: `RunClojure <файл скрипта>`
|
||||||
|
* Справка: `RunClojure --help`
|
||||||
|
* IDE
|
||||||
|
* IntelliJ Idea: [плагин Cursive](https://cursive-ide.com/userguide/)
|
||||||
|
* VSCode: [плагин Calva](https://calva.io/)
|
||||||
|
|
||||||
|
[Скрипт со всеми примерами](clojure/examples.clj)
|
||||||
|
|
||||||
|
Лекция 1. Функции
|
||||||
|
* [Введение](clojure/examples/1_1_intro.clj)
|
||||||
|
* [Функции](clojure/examples/1_2_functions.clj)
|
||||||
|
* [Списки](clojure/examples/1_3_lists.clj)
|
||||||
|
* [Вектора](clojure/examples/1_4_vectors.clj)
|
||||||
|
* [Функции высшего порядка](clojure/examples/1_5_functions-2.clj)
|
||||||
|
|
||||||
|
Лекция 2. Внешний мир
|
||||||
|
* [Ввод-вывод](clojure/examples/2_1_io.clj)
|
||||||
|
* [Разбор и гомоиконность](clojure/examples/2_2_read.clj)
|
||||||
|
* [Порядки вычислений](clojure/examples/2_3_evaluation-orders.clj)
|
||||||
|
* [Потоки](clojure/examples/2_4_streams.clj)
|
||||||
|
* [Отображения и множества](clojure/examples/2_5_maps.clj)
|
||||||
|
|
||||||
|
|
||||||
|
## Тестовое задание на Clojure [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Это задание предназначено для проверки правильности настройки Clojure.
|
||||||
|
Вам надо убедиться, что оно успешно проверяется на вашем компьютере.
|
||||||
|
|
||||||
|
Для запуска тестов используются скрипты
|
||||||
|
[TestClojure.cmd](clojure/TestClojure.cmd) и [TestClojure.sh](clojure/TestClojure.sh)
|
||||||
|
* Репозиторий должен быть скачан целиком.
|
||||||
|
* Скрипты должны находиться в каталоге `clojure`
|
||||||
|
(их нельзя перемещать, но можно вызывать из других каталогов).
|
||||||
|
* Тестируемое решение должно находиться в текущем каталоге.
|
||||||
|
* В качестве аргументов командной строки указывается
|
||||||
|
полное имя класса теста, сложность и модификация,
|
||||||
|
например, `cljtest.example.ExampleTest hard base`.
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *base* ✅
|
||||||
|
* Код решения `clojure-solutions/example.clj` в
|
||||||
|
[репозитории решений](https://www.kgeorgiy.info/git/geo/paradigms-2026-students/).
|
||||||
|
Если всё настроено верно, то вам достаточно сделать
|
||||||
|
`git pull source main` в своём репозитории,
|
||||||
|
чтобы получить решение.
|
||||||
|
* [Исходный код тестов](clojure/cljtest/example/ExampleTest.java)
|
||||||
|
* Запускать с аргументом `hard` или `easy`.
|
||||||
|
|
||||||
|
|
||||||
|
## Домашнее задание 8. Обработка ошибок на JavaScript [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Base* ✅
|
||||||
|
* Код должен находиться в файле `javascript-solutions/objectExpression.js`.
|
||||||
|
* [Исходный код тестов](javascript/jstest/prefix/ParserTest.java)
|
||||||
|
* Запускать с указанием модификации и сложности (`easy` или `hard`).
|
||||||
|
* *Postfix*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* Выражений в постфиксной записи:
|
||||||
|
* `(2 3 +)` равно 5
|
||||||
|
* функция `parsePostfix`
|
||||||
|
* метод `postfix`
|
||||||
|
* [Исходный код тестов](javascript/jstest/prefix/PostfixTest.java)
|
||||||
|
* Запускать с указанием модификации и сложности (`easy` или `hard`).
|
||||||
|
* *3637*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* постфиксной записи;
|
||||||
|
* разных видов скобок: квадратных (`[]`), фигурных (`{}`), угловых (`<>`);
|
||||||
|
* операций произвольного числа аргументов:
|
||||||
|
* `SumExp` (`sumExp`) – сумма экспонент,
|
||||||
|
`(sumExp 2 3 16)` примерно равно 8886137;
|
||||||
|
* `Lse` (`lse`) – [LogSumExp](https://en.wikipedia.org/wiki/LogSumExp),
|
||||||
|
`(lse 2 3 16)` примерно равно 16.
|
||||||
|
* *3839*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* постфиксной записи;
|
||||||
|
* разных видов скобок: квадратных (`[]`), фигурных (`{}`), угловых (`<>`).
|
||||||
|
* операций произвольного числа аргументов:
|
||||||
|
* `SumExp` (`sumExp`) – сумма экспонент,
|
||||||
|
`(sumExp 2 3 16)` примерно равно 8886137;
|
||||||
|
* `Lme` (`lme`) – логарифма среднего экспонент,
|
||||||
|
`(lme 2 7 9)` примерно равно 8.
|
||||||
|
* *3435*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* разных видов скобок: квадратных (`[]`), фигурных (`{}`), угловых (`<>`), французские кавычки (`«»`).
|
||||||
|
* операций произвольного числа аргументов:
|
||||||
|
* `MeanExp` (`meanExp`) – среднее экспонент,
|
||||||
|
`(meanExp 2 4 15)` примерно равно 1089693.
|
||||||
|
* *3233*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* разных видов скобок: квадратных (`[]`), фигурных (`{}`), угловых (`<>`), французские кавычки (`«»`).
|
||||||
|
* операций одного или двух аргументов:
|
||||||
|
* `ArcTan12` (`atan12`) – арктангенс,
|
||||||
|
`(atan12 1256)` примерно равно 1.57,
|
||||||
|
`(atan12 841 540)` примерно равно 1.
|
||||||
|
|
||||||
|
## Домашнее задание 7. Объектные выражения на JavaScript [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Base* ✅
|
||||||
|
* Код должен находиться в файле `javascript-solutions/objectExpression.js`.
|
||||||
|
* [Исходный код тестов](javascript/jstest/object/ObjectTest.java)
|
||||||
|
* Запускать с указанием модификации и сложности (`easy`, `hard` или `bonus`).
|
||||||
|
* *Simplify*. ✅ С проверкой упрощений.
|
||||||
|
* *3637*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* бинарных функций:
|
||||||
|
* `Power` (`pow`) – возведение в степень, `2 3 pow` равно 8;
|
||||||
|
* `Log` (`log`) – логарифм абсолютного значения аргумента
|
||||||
|
по абсолютному значению основания `-2 -8 log` равно 3.
|
||||||
|
* функций от `N` аргументов для `N=1..5`:
|
||||||
|
* `SumN` (`sumN`) – сумма аргументов,
|
||||||
|
`1 2 3 4 5 sum5` равно 15.
|
||||||
|
* *3839*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* функций:
|
||||||
|
* `Gauss` (`gauss`) – [функция Гаусса](https://ru.wikipedia.org/wiki/%D0%93%D0%B0%D1%83%D1%81%D1%81%D0%BE%D0%B2%D0%B0_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F);
|
||||||
|
от четырех аргументов: `a`, `b`, `c`, `x`.
|
||||||
|
* функций от `N` аргументов для `N=1..5`:
|
||||||
|
* `SumN` (`sumN`) – сумма аргументов,
|
||||||
|
`1 2 3 4 5 sum5` равно 15.
|
||||||
|
* `AvgN` (`avgN`) – арифметическое среднее аргументов,
|
||||||
|
`1 2 3 4 5 avg5` равно 3.
|
||||||
|
* *3435*. ✅ Дополнительно реализовать поддержку функций:
|
||||||
|
* `Wrap` (`wrap`) – функции [wrap(_x_, _min_, _max_)](https://en.wikipedia.org/wiki/Wrapping_(graphics)),
|
||||||
|
`3 5 8 wrap` равно 6;
|
||||||
|
* `SoftWrap` (`softWrap`) – сглаженного аналога `wrap`:\
|
||||||
|
softWrap(_x_, _min_, _max_, _λ_) = arcsin(cos(_a_) * tanh(_λ_ * -sin(_a_))) * (_max_ - _min_) / π + (_max_ + _min_) / 2,\
|
||||||
|
где _a_ = π * (_x_ - _min_) / (_max_ - _min_)
|
||||||
|
`3 5 8 0.5 softWrap` примерно равно 6.3.
|
||||||
|
* *3233*. ✅ Дополнительно реализовать поддержку функций:
|
||||||
|
* `Wrap` (`wrap`) – функции [wrap(_x_, _min_, _max_)](https://en.wikipedia.org/wiki/Wrapping_(graphics)),
|
||||||
|
`3 5 8 wrap` равно 6.
|
||||||
|
|
||||||
|
## Домашнее задание 6. Функциональные выражения на JavaScript [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Базовая* ✅
|
||||||
|
* Код должен находиться в файле `javascript-solutions/functionalExpression.js`.
|
||||||
|
* [Исходный код тестов](javascript/jstest/functional/FunctionalTest.java)
|
||||||
|
* Запускать с аргументом `hard` или `easy`.
|
||||||
|
* *3637*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* переменных: `y`, `z`;
|
||||||
|
* констант: `one` – 1, `two` – 2, `three` – 3;
|
||||||
|
* операций:
|
||||||
|
* `clamp` – функции [clamp(_x_, _min_, _max_)](https://en.wikipedia.org/wiki/Clamp_(function)),
|
||||||
|
`3 5 8 clamp` равно 5;
|
||||||
|
* `wrap` – функции [wrap(_x_, _min_, _max_)](https://en.wikipedia.org/wiki/Wrapping_(graphics)),
|
||||||
|
`3 5 8 wrap` равно 6.
|
||||||
|
* `argMin3` – индекс минимального из трёх аргументов, `3 4 1 argMin3` равно 2;
|
||||||
|
* `argMax3` – индекс максимального из трёх аргументов, `3 4 1 argMax3` равно 1;
|
||||||
|
* `argMin5` – индекс минимального из пяти аргументов, `3 4 1 5 6 argMin5` равно 2;
|
||||||
|
* `argMax5` – индекс максимального из пяти аргументов, `3 4 10 5 6 argMax5` равно 2.
|
||||||
|
* *3839*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* переменных: `y`, `z`;
|
||||||
|
* констант: `one` – 1, `two` – 2, `three` – 3;
|
||||||
|
* операций:
|
||||||
|
* `clamp` – функции [clamp(_x_, _min_, _max_)](https://en.wikipedia.org/wiki/Clamp_(function)),
|
||||||
|
`3 5 8 clamp` равно 5;
|
||||||
|
* `softClamp` – сглаженного аналога `clamp`:\
|
||||||
|
softClamp(_x_, _min_, _max_, _λ_) = _min_ + (_max_ - _min_) / (1 + exp(_λ_((_max_ + _min_) / 2 - _x_))),\
|
||||||
|
`3 5 8 0.2 softClamp` примерно равно 6.
|
||||||
|
* `argMin3` – индекс минимального из трёх аргументов, `3 4 1 argMin3` равно 2;
|
||||||
|
* `argMax3` – индекс максимального из трёх аргументов, `3 4 1 argMax3` равно 1;
|
||||||
|
* `argMin5` – индекс минимального из пяти аргументов, `3 4 1 5 6 argMin5` равно 2;
|
||||||
|
* `argMax5` – индекс максимального из пяти аргументов, `3 4 10 5 6 argMax5` равно 2.
|
||||||
|
* *3435*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* переменных: `y`, `z`;
|
||||||
|
* констант: `one` – 1, `two` – 2, `three` – 3;
|
||||||
|
* операций:
|
||||||
|
* `arcTan` (`atan`) – унарный арктангенс,
|
||||||
|
`1256 atan` примерно равно 1.57;
|
||||||
|
* `arcTan2` (`atan2`) – бинарный арктангенс,
|
||||||
|
`841 540 atan2` примерно равно 1.
|
||||||
|
* *3233*. ✅ Дополнительно реализовать поддержку:
|
||||||
|
* переменных: `y`, `z`;
|
||||||
|
* констант: `one` – 1, `two` – 2, `three` – 3;
|
||||||
|
* операций:
|
||||||
|
* `sin` – синус, `3.14159265 sin` примерно равно 0;
|
||||||
|
* `cos` – косинус, `3.14159265 cos` примерно равно -1.
|
||||||
|
|
||||||
|
## Исходный код к лекциям по JavaScript
|
||||||
|
|
||||||
|
[Скрипт с примерами](javascript/examples.js)
|
||||||
|
|
||||||
|
Запуск примеров
|
||||||
|
* [В браузере](javascript/RunJS.html)
|
||||||
|
* Из консоли
|
||||||
|
* [на Java](javascript/RunJS.java): [RunJS.cmd](javascript/RunJS.cmd), [RunJS.sh](javascript/RunJS.sh)
|
||||||
|
* [на node.js](javascript/RunJS.node.js): `node RunJS.node.js`
|
||||||
|
|
||||||
|
Лекция 1. Типы и функции
|
||||||
|
* [Типы](javascript/examples/1_1_types.js)
|
||||||
|
* [Массивы](javascript/examples/1_2_arrays.js).
|
||||||
|
Обратите внимание, у массивов есть много
|
||||||
|
[других методов](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
|
||||||
|
* [Функции](javascript/examples/1_3_functions.js)
|
||||||
|
* [Функции высшего порядка](javascript/examples/1_4_functions-hi.js).
|
||||||
|
Обратите внимание на реализацию функции `mCurry`.
|
||||||
|
Обратите внимание, что функции `array.map` и
|
||||||
|
`array.reduce` (аналог `leftFold`) входят в стандартную библиотеку.
|
||||||
|
* [Пример: вектора и матрицы](javascript/examples/1_5_vectors.js).
|
||||||
|
|
||||||
|
|
||||||
|
## Тестовое задание на JavaScript [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Это задание предназначено для проверки правильности настройки
|
||||||
|
[JavaScript](https://ecma-international.org/publications-and-standards/standards/ecma-262/).
|
||||||
|
Вам надо проверить, что оно успешно проверяется на вашем компьютере.
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *base* ✅
|
||||||
|
* Код решения `javascript-solutions/example.js` в
|
||||||
|
[репозитории решений](https://www.kgeorgiy.info/git/geo/paradigms-2026-students/).
|
||||||
|
Если всё настроено верно, то вам достаточно сделать `git pull --rebase` в своём репозитории,
|
||||||
|
чтобы получить решение.
|
||||||
|
* [Исходный код тестов](javascript/jstest/example/ExampleTest.java)
|
||||||
|
* Запускать с аргументом `hard` или `easy`.
|
||||||
|
|
||||||
|
Запуск тестов
|
||||||
|
* Для запуска тестов используется [GraalJS](https://github.com/graalvm/graaljs)
|
||||||
|
(часть проекта [GraalVM](https://www.graalvm.org/)), но вам не требуется их скачивать.
|
||||||
|
* Для запуска тестов рекомендуется использовать скрипты
|
||||||
|
[TestJS.cmd](javascript/TestJS.cmd) и [TestJS.sh](javascript/TestJS.sh)
|
||||||
|
* Репозиторий должен быть скачан целиком.
|
||||||
|
* Скрипты должны находиться в каталоге `javascript` (их нельзя перемещать, но можно вызывать из других каталогов).
|
||||||
|
* В качестве аргументов командной строки указывается полное имя класса теста и модификация,
|
||||||
|
например `jstest.example.ExampleTest hard base`.
|
||||||
|
* Для самостоятельного запуска из консоли необходимо использовать командную строку вида:
|
||||||
|
`java -ea --module-path=<js>/graal --class-path <js> jstest.example.ExampleTest {hard|easy} <variant>`, где
|
||||||
|
* `-ea` – включение проверок времени исполнения;
|
||||||
|
* `--module-path=<js>/graal` путь к модулям Graal (здесь и далее `<js>` путь к каталогу `javascript` этого репозитория);
|
||||||
|
* `--class-path <js>` путь к откомпилированным тестам;
|
||||||
|
* `{hard|easy}` указание тестируемой сложности;
|
||||||
|
* `<variant>` указание тестируемой модификации.
|
||||||
|
* При запуске из IDE, обычно не требуется указывать `--class-path`, так как он формируется автоматически.
|
||||||
|
Остальные опции всё равно необходимо указать.
|
||||||
|
* Troubleshooting
|
||||||
|
* `Error occurred during initialization of boot layer java.lang.module.FindException: Module org.graalvm.truffle not found, required by jdk.internal.vm.compiler`
|
||||||
|
– неверно указан `--module-path`;
|
||||||
|
* `Graal.js not found` – неверно указаны `--module-path`
|
||||||
|
* `Error: Could not find or load main class jstest.example.ExampleTest`
|
||||||
|
– неверно указан `--class-path`;
|
||||||
|
* `Exception in thread "main" java.lang.AssertionError: You should enable assertions by running 'java -ea jstest.functional.FunctionalExpressionTest'`
|
||||||
|
– не указана опция `-ea`;
|
||||||
|
* `Exception in thread "main" jstest.EngineException: Script 'example.js' not found`
|
||||||
|
– в текущем каталоге отсутствует решение (`example.js`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Домашнее задание 5. Вычисления в различных типах [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Base* ✅
|
||||||
|
* Класс `GenericTabulator` должен реализовывать интерфейс
|
||||||
|
[Tabulator](java/expression/generic/Tabulator.java) и
|
||||||
|
строить трёхмерную таблицу значений заданного выражения.
|
||||||
|
* `mode` – режим вычислений:
|
||||||
|
* `i` – вычисления в `int` с проверкой на переполнение;
|
||||||
|
* `d` – вычисления в `double` без проверки на переполнение;
|
||||||
|
* `bi` – вычисления в `BigInteger`.
|
||||||
|
* `expression` – выражение, для которого надо построить таблицу;
|
||||||
|
* `x1`, `x2` – минимальное и максимальное значения переменной `x` (включительно)
|
||||||
|
* `y1`, `y2`, `z1`, `z2` – аналогично для `y` и `z`.
|
||||||
|
* Результат: элемент `result[i][j][k]` должен содержать
|
||||||
|
значение выражения для `x = x1 + i`, `y = y1 + j`, `z = z1 + k`.
|
||||||
|
Если значение не определено (например, по причине переполнения),
|
||||||
|
то соответствующий элемент должен быть равен `null`.
|
||||||
|
* [Исходный код тестов](java/expression/generic/GenericTest.java)
|
||||||
|
* Первый аргумент: `easy` или `hard`
|
||||||
|
* Последующие аргументы: модификации
|
||||||
|
* *3637* ✅ Дополнительно реализуйте:
|
||||||
|
* Унарные операции:
|
||||||
|
* `count` – число установленных битов, `count 5` равно 2.
|
||||||
|
* Бираные операции (минимальный приоритет):
|
||||||
|
* `min` – минимум, `2 min 3` равно 2;
|
||||||
|
* `max` – максимум, `2 max 3` равно 3.
|
||||||
|
* Поддержку режимов:
|
||||||
|
* `u` – вычисления в `int` без проверки на переполнение;
|
||||||
|
* `s` – вычисления в `short` без проверки на переполнение;
|
||||||
|
* `f` – вычисления в `float` без проверки на переполнение.
|
||||||
|
* *3839* ✅ Дополнительно реализуйте:
|
||||||
|
* Унарные операции:
|
||||||
|
* `count` – число установленных битов, `count 5` равно 2.
|
||||||
|
* Бинарные операции (минимальный приоритет):
|
||||||
|
* `min` – минимум, `2 min 3` равно 2;
|
||||||
|
* `max` – максимум, `2 max 3` равно 3.
|
||||||
|
* Поддержку режимов:
|
||||||
|
* `u` – вычисления в `int` без проверки на переполнение;
|
||||||
|
* `s` – вычисления в `short` без проверки на переполнение;
|
||||||
|
* `t` – вычисления в `int` без проверки на переполнение
|
||||||
|
с отбрасыванием остатка от деления на 10.
|
||||||
|
* *3435* ✅ Дополнительно реализуйте:
|
||||||
|
* Унарные операции:
|
||||||
|
* `count` – число установленных битов, `count 5` равно 2.
|
||||||
|
* Бинарные операции (минимальный приоритет):
|
||||||
|
* `min` – минимум, `2 min 3` равно 2;
|
||||||
|
* `max` – максимум, `2 max 3` равно 3.
|
||||||
|
* Поддержку режимов:
|
||||||
|
* `u` – вычисления в `int` без проверки на переполнение;
|
||||||
|
* *3233* ✅ Дополнительно реализуйте поддержку режимов:
|
||||||
|
* `u` – вычисления в `int` без проверки на переполнение;
|
||||||
|
* `s` – вычисления в `short` без проверки на переполнение;
|
||||||
|
* `f` – вычисления в `float` без проверки на переполнение.
|
||||||
|
|
||||||
|
|
||||||
|
## Домашнее задание 4. Очереди [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Базовая* ✅
|
||||||
|
* [Исходный код тестов](java/queue/QueueTest.java)
|
||||||
|
* [Откомпилированные тесты](artifacts/queue/QueueTest.jar)
|
||||||
|
* Для работы тестов необходимо добавить опцию JVM `--add-opens java.base/java.util=ALL-UNNAMED`
|
||||||
|
* *3637* ✅
|
||||||
|
* Добавить в интерфейс очереди и реализовать методы
|
||||||
|
* `contains(element)` – проверяет, содержится ли элемент в очереди
|
||||||
|
* `removeFirst(element)` – удаляет первое вхождение элемента в очередь
|
||||||
|
и возвращает было ли такое
|
||||||
|
* Дублирования кода быть не должно
|
||||||
|
* *3839* ✅
|
||||||
|
* Добавить в интерфейс очереди и реализовать методы
|
||||||
|
* `getNth(n)` – создать очередь, содержащую каждый n-й элемент, считая с 1
|
||||||
|
* `removeNth(n)` – создать очередь, содержащую каждый n-й элемент, и удалить их из исходной очереди
|
||||||
|
* `dropNth(n)` – удалить каждый n-й элемент из исходной очереди
|
||||||
|
* Тип возвращаемой очереди должен соответствовать типу исходной очереди
|
||||||
|
* Дублирования кода быть не должно
|
||||||
|
* *3435* ✅
|
||||||
|
* Добавить в интерфейс очереди и реализовать методы
|
||||||
|
* `removeIf(predicate)` – удалить элементы, удовлетворяющие
|
||||||
|
[предикату](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/function/Predicate.html)
|
||||||
|
* `retainIf(predicate)` – удалить элементы, не удовлетворяющие
|
||||||
|
[предикату](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/function/Predicate.html)
|
||||||
|
* Взаимный порядок элементов должен сохраняться
|
||||||
|
* Дублирования кода быть не должно
|
||||||
|
* *3233* ✅
|
||||||
|
* Добавить в интерфейс очереди и реализовать методы
|
||||||
|
* `removeAll(element)` – удалить все элементы равные заданному
|
||||||
|
* `retainAll(predicate)` – оставить только элементы равные заданному
|
||||||
|
* Взаимный порядок элементов должен сохраняться
|
||||||
|
* Дублирования кода быть не должно
|
||||||
|
|
||||||
|
## Домашнее задание 3. Очередь на массиве [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Базовая* ✅
|
||||||
|
* Классы должны находиться в пакете `queue`
|
||||||
|
* [Исходный код тестов](java/queue/ArrayQueueTest.java)
|
||||||
|
* [Откомпилированные тесты](artifacts/queue/ArrayQueueTest.jar)
|
||||||
|
* Для работы тестов необходимо добавить опцию JVM `--add-opens java.base/java.util=ALL-UNNAMED`
|
||||||
|
* *3637*. Дополнительно реализовать методы: ✅
|
||||||
|
* `push` – добавить элемент в начало очереди;
|
||||||
|
* `peek` – вернуть последний элемент в очереди;
|
||||||
|
* `remove` – вернуть и удалить последний элемент из очереди;
|
||||||
|
* `count` – вернуть число вхождений элемента в очередь.
|
||||||
|
* *3839*. Дополнительно реализовать методы: ✅
|
||||||
|
* `push` – добавить элемент в начало очереди;
|
||||||
|
* `peek` – вернуть последний элемент в очереди;
|
||||||
|
* `remove` – вернуть и удалить последний элемент из очереди;
|
||||||
|
* `countIf` – вернуть число элементов очереди, удовлетворяющих
|
||||||
|
[предикату](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/function/Predicate.html).
|
||||||
|
* *3435* ✅
|
||||||
|
* Дополнительно реализовать методы:
|
||||||
|
* `indexIf` – вернуть индекс первого элемента, удовлетворяющего
|
||||||
|
[предикату](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/function/Predicate.html);
|
||||||
|
* `lastIndexIf` – вернуть индекс последнего элемента, удовлетворяющего
|
||||||
|
[предикату](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/function/Predicate.html).
|
||||||
|
* Индексы отсчитываются с головы очереди.
|
||||||
|
* Если искомого элемента нет, методы должны возвращать `-1`.
|
||||||
|
* *3233* ✅
|
||||||
|
* Дополнительно реализовать методы:
|
||||||
|
* `indexOf` – вернуть индекс первого вхождения элемента в очередь;
|
||||||
|
* `lastIndexOf` – вернуть индекс последнего вхождения элемента в очередь.
|
||||||
|
* Индексы отсчитываются с головы очереди.
|
||||||
|
* Если искомого элемента нет, методы должны возвращать `-1`.
|
||||||
|
|
||||||
|
## Домашнее задание 2. Бинарный поиск [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Базовая* ✅
|
||||||
|
* Класс `BinarySearch` должен находиться в пакете `search`
|
||||||
|
* [Исходный код тестов](java/search/BinarySearchTest.java)
|
||||||
|
* [Откомпилированные тесты](artifacts/search/BinarySearchTest.jar)
|
||||||
|
* *3637* ✅
|
||||||
|
* На вход подаётся число `x` и массив, отсортированный по невозрастанию.
|
||||||
|
* Требуется вывести число элементов массива, равных `x`.
|
||||||
|
* Не допускается использование типов `long` и `BigInteger`.
|
||||||
|
* Класс должен иметь имя `BinarySearch3637`
|
||||||
|
* *3839* ✅
|
||||||
|
* На вход подаётся число `x` и массив, отсортированный по невозрастанию.
|
||||||
|
* Требуется вывести два числа: начало и длину диапазона элементов, равных `x`.
|
||||||
|
Если таких элементов нет, то следует вывести
|
||||||
|
пустой диапазон, у которого левая граница совпадает с местом
|
||||||
|
вставки элемента `x`.
|
||||||
|
* Не допускается использование типов `long` и `BigInteger`.
|
||||||
|
* Класс должен иметь имя `BinarySearch3839`
|
||||||
|
* *3435* ✅
|
||||||
|
* На вход подается отсортированный (строго) по убыванию массив,
|
||||||
|
циклически сдвинутый на `k` элементов.
|
||||||
|
Все числа в массиве различны.
|
||||||
|
* Требуется найти `k`.
|
||||||
|
* Класс должен иметь имя `BinarySearch3435`
|
||||||
|
* *3233* ✅
|
||||||
|
* На вход подается отсортированный (строго) по возрастанию массив,
|
||||||
|
циклически сдвинутый на `k` элементов.
|
||||||
|
Все числа в массиве различны.
|
||||||
|
* Требуется найти `k`.
|
||||||
|
* Класс должен иметь имя `BinarySearch3233`
|
||||||
|
|
||||||
|
|
||||||
|
## Домашнее задание 1. Обработка ошибок [](https://git.fym.su/code.java/paradigms/actions)
|
||||||
|
|
||||||
|
Модификации
|
||||||
|
* *Base* ✅
|
||||||
|
* Класс `ExpressionParser` должен реализовывать интерфейс
|
||||||
|
[ListParser](java/expression/exceptions/ListParser.java).
|
||||||
|
* Результат разбора должен реализовывать интерфейс
|
||||||
|
[ListExpression](java/expression/ListExpression.java).
|
||||||
|
* Нельзя использовать типы `long` и `double`
|
||||||
|
* Нельзя использовать методы классов `Math` и `StrictMath`
|
||||||
|
* [Исходный код тестов](java/expression/exceptions/ExceptionsTest.java)
|
||||||
|
* Первый аргумент: `easy` или `hard`
|
||||||
|
* Последующие аргументы: модификации
|
||||||
|
* *3637* ✅
|
||||||
|
* Дополнительно реализуйте унарные операции
|
||||||
|
* `‖x‖` – модуль, `‖-5‖` равно 5;
|
||||||
|
* `³` – возведение в куб, `-5³` равно −125;
|
||||||
|
* `∛` – кубический корень, `∛-123` равно -4.
|
||||||
|
* *3839* ✅
|
||||||
|
* Дополнительно реализуйте унарные операции:
|
||||||
|
* `‖x‖` – модуль, `‖-5‖` равно 5;
|
||||||
|
* `²` – возведение в квадрат, `-5²` равно 25;
|
||||||
|
* `√` – квадратный корень, `√24` равно 4;
|
||||||
|
* `³` – возведение в куб, `-5³` равно −125;
|
||||||
|
* `∛` – кубический корень, `∛-123` равно -4.
|
||||||
|
* *3435* ✅
|
||||||
|
* Дополнительно реализуйте унарные операции:
|
||||||
|
* `‖x‖` – модуль, `‖-5‖` равно 5;
|
||||||
|
* `√` – квадратный корень, `√24` равно 4.
|
||||||
|
* *3233* ✅
|
||||||
|
* Дополнительно реализуйте унарные операции:
|
||||||
|
* `‖x‖` – модуль числа, `‖-5‖` равно 5;
|
||||||
|
* `∛` – кубический корень, `∛-123` равно -4.
|
||||||
BIN
artifacts/queue/ArrayQueueTest.jar
Normal file
BIN
artifacts/queue/ArrayQueueTest.jar
Normal file
Binary file not shown.
BIN
artifacts/queue/QueueTest.jar
Normal file
BIN
artifacts/queue/QueueTest.jar
Normal file
Binary file not shown.
BIN
artifacts/search/BinarySearchTest.jar
Normal file
BIN
artifacts/search/BinarySearchTest.jar
Normal file
Binary file not shown.
1
clojure/.gitattributes
vendored
Normal file
1
clojure/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
1
clojure/RunClojure.cmd
Normal file
1
clojure/RunClojure.cmd
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@java --class-path "%~dp0lib/*" clojure.main %*
|
||||||
4
clojure/RunClojure.sh
Normal file
4
clojure/RunClojure.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
java \
|
||||||
|
--class-path "$(dirname "0")/lib/*" \
|
||||||
|
clojure.main "$@"
|
||||||
23
clojure/TestClojure.cmd
Normal file
23
clojure/TestClojure.cmd
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
if "%~2" == "" (
|
||||||
|
echo Usage: %~n0 TEST-CLASS MODE VARIANT?
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "OUT=__OUT"
|
||||||
|
set "CLASS=%~1"
|
||||||
|
set "ARGS=%~2 %~3"
|
||||||
|
|
||||||
|
set "DIR=%~dp0"
|
||||||
|
set "DIR=%DIR:~0,-1%"
|
||||||
|
set "LIB=%DIR%/lib/*"
|
||||||
|
|
||||||
|
if exist "%OUT%" rmdir /s /q "%OUT%"
|
||||||
|
|
||||||
|
javac ^
|
||||||
|
-encoding utf-8 ^
|
||||||
|
-d "%OUT%" ^
|
||||||
|
"--class-path=%LIB%;%DIR%/../common;%DIR%" ^
|
||||||
|
"%DIR%/%CLASS:.=/%.java" ^
|
||||||
|
&& java -ea "--class-path=%LIB%;%OUT%" "%CLASS%" %ARGS%
|
||||||
23
clojure/TestClojure.sh
Executable file
23
clojure/TestClojure.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ -z "$2" ]] ; then
|
||||||
|
echo Usage: $(basename "$0") TEST-CLASS MODE VARIANT?
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLASS="$1"
|
||||||
|
ARGS="$2 ${3-}"
|
||||||
|
|
||||||
|
OUT=__out
|
||||||
|
DIR="$(dirname "$0")"
|
||||||
|
LIB="$DIR/lib/*"
|
||||||
|
|
||||||
|
rm -rf "$OUT"
|
||||||
|
|
||||||
|
javac \
|
||||||
|
-encoding utf-8 \
|
||||||
|
-d "$OUT" \
|
||||||
|
"--class-path=$LIB:$DIR/../common:$DIR" \
|
||||||
|
"$DIR/${CLASS//\.//}.java" \
|
||||||
|
&& java -ea "--class-path=$LIB:$OUT" "$CLASS" $ARGS
|
||||||
53
clojure/cljtest/ClojureEngine.java
Normal file
53
clojure/cljtest/ClojureEngine.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package cljtest;
|
||||||
|
|
||||||
|
import clojure.lang.IFn;
|
||||||
|
import common.Engine;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clojure tests engine.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class ClojureEngine implements Engine<Object> {
|
||||||
|
private static final IFn HASH_MAP = ClojureScript.var("clojure.core/hash-map");
|
||||||
|
|
||||||
|
private final Optional<IFn> evaluate;
|
||||||
|
private final String evaluateString;
|
||||||
|
private final ClojureScript.F<Object> parse;
|
||||||
|
private final ClojureScript.F<String> toString;
|
||||||
|
|
||||||
|
public ClojureEngine(final String script, final Optional<String> evaluate, final String parse, final String toString) {
|
||||||
|
ClojureScript.loadScript(script);
|
||||||
|
this.parse = ClojureScript.function(parse, Object.class);
|
||||||
|
this.toString = ClojureScript.function(toString, String.class);
|
||||||
|
|
||||||
|
this.evaluate = evaluate.map(ClojureScript::var);
|
||||||
|
evaluateString = evaluate.map(s -> s + " ").orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Object> prepare(final String expression) {
|
||||||
|
return new Result<>(expression, ClojureScript.LOAD_STRING_IN.invoke(expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Object> parse(final String expression) {
|
||||||
|
return parse.call(new Result<>("\"" + expression + "\"", expression));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Number> evaluate(final Result<Object> prepared, final double[] vars) {
|
||||||
|
final Object map = HASH_MAP.invoke("x", vars[0], "y", vars[1], "z", vars[2]);
|
||||||
|
final String context = "(%sexpr %s)\nwhere expr = %s".formatted(evaluateString, map, prepared.context());
|
||||||
|
return evaluate
|
||||||
|
.map(f -> ClojureScript.call(f, Number.class, context, new Object[]{prepared.value(), map}))
|
||||||
|
.orElseGet(() -> ClojureScript.call((IFn) prepared.value(), Number.class, context, new Object[]{map}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<String> toString(final Result<Object> prepared) {
|
||||||
|
return toString.call(prepared);
|
||||||
|
}
|
||||||
|
}
|
||||||
124
clojure/cljtest/ClojureScript.java
Normal file
124
clojure/cljtest/ClojureScript.java
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package cljtest;
|
||||||
|
|
||||||
|
import clojure.java.api.Clojure;
|
||||||
|
import clojure.lang.ArraySeq;
|
||||||
|
import clojure.lang.IFn;
|
||||||
|
import common.Engine;
|
||||||
|
import common.EngineException;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for Clojure tests.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class ClojureScript {
|
||||||
|
public static final IFn LOAD_STRING = var("clojure.core/load-string");
|
||||||
|
public static final IFn LOAD_FILE = asUser("load-file");
|
||||||
|
public static final IFn LOAD_STRING_IN = asUser("load-string");
|
||||||
|
public static Path CLOJURE_ROOT = Path.of(".");
|
||||||
|
|
||||||
|
private ClojureScript() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IFn asUser(final String function) {
|
||||||
|
return (IFn) LOAD_STRING.invoke(
|
||||||
|
"(fn " + function + "-in [arg]" +
|
||||||
|
" (binding [*ns* *ns*]" +
|
||||||
|
" (in-ns 'user)" +
|
||||||
|
" (" + function + " arg)))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadScript(final String script) {
|
||||||
|
final String escaped = CLOJURE_ROOT.toString().replace("\\", "\\\\");
|
||||||
|
LOAD_STRING_IN.invoke("(defn load-file [file] (clojure.core/load-file (str \"" + escaped + "/\" file)))");
|
||||||
|
LOAD_FILE.invoke(CLOJURE_ROOT.resolve(script).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Engine.Result<T> call(final IFn f, final Class<T> type, final String context, final Object[] args) {
|
||||||
|
final Object result;
|
||||||
|
try {
|
||||||
|
result = callUnsafe(f, args);
|
||||||
|
} catch (final AssertionError e) {
|
||||||
|
throw e;
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
throw new EngineException("No error expected in " + context, e);
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
throw new EngineException("Expected %s, found null\n%s".formatted(type.getSimpleName(), context), new NullPointerException());
|
||||||
|
}
|
||||||
|
if (!type.isInstance(result)) {
|
||||||
|
throw new EngineException("Expected %s, found %s (%s)\n%s".formatted(type.getSimpleName(), result, result.getClass().getSimpleName(), context), null);
|
||||||
|
}
|
||||||
|
return new Engine.Result<>(context, type.cast(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object callUnsafe(final IFn f, final Object[] args) {
|
||||||
|
return switch (args.length) {
|
||||||
|
case 0 -> f.invoke();
|
||||||
|
case 1 -> f.invoke(args[0]);
|
||||||
|
case 2 -> f.invoke(args[0], args[1]);
|
||||||
|
case 3 -> f.invoke(args[0], args[1], args[2]);
|
||||||
|
case 4 -> f.invoke(args[0], args[1], args[2], args[3]);
|
||||||
|
case 5 -> f.invoke(args[0], args[1], args[2], args[3], args[4]);
|
||||||
|
case 6 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||||
|
case 7 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
||||||
|
case 8 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
|
||||||
|
case 9 -> f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
|
||||||
|
default -> f.applyTo(ArraySeq.create(args));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Engine.Result<Throwable> expectException(final IFn f, final Object[] args, final String context) {
|
||||||
|
try {
|
||||||
|
callUnsafe(f, args);
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
return new Engine.Result<>(context, e);
|
||||||
|
}
|
||||||
|
assert false : "Exception expected in " + context;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> F<T> function(final String name, final Class<T> type) {
|
||||||
|
return new F<>(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public record F<T>(String name, Class<T> type, IFn f) {
|
||||||
|
public F(final String name, final Class<T> type) {
|
||||||
|
this(name.substring(name.indexOf("/") + 1), type, var(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine.Result<T> call(final Engine.Result<?>... args) {
|
||||||
|
return ClojureScript.call(
|
||||||
|
f,
|
||||||
|
type,
|
||||||
|
callToString(args),
|
||||||
|
Arrays.stream(args).map(Engine.Result::value).toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String callToString(final Engine.Result<?>[] args) {
|
||||||
|
return "(" + name + Arrays.stream(args).map(arg -> " " + arg.context()).collect(Collectors.joining()) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine.Result<Throwable> expectException(final Engine.Result<?>... args) {
|
||||||
|
return ClojureScript.expectException(
|
||||||
|
f,
|
||||||
|
Arrays.stream(args).map(Engine.Result::value).toArray(),
|
||||||
|
"(" + name + " " + Arrays.stream(args).map(Engine.Result::context).collect(Collectors.joining(" ")) + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IFn var(final String name) {
|
||||||
|
final String qualifiedName = (name.contains("/") ? "" : "user/") + name;
|
||||||
|
return Clojure.var(qualifiedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
clojure/cljtest/example/ExampleTest.java
Normal file
67
clojure/cljtest/example/ExampleTest.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package cljtest.example;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.Selector;
|
||||||
|
import base.TestCounter;
|
||||||
|
import cljtest.ClojureScript;
|
||||||
|
import common.Engine;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Example Clojure
|
||||||
|
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class ExampleTest {
|
||||||
|
private final TestCounter counter;
|
||||||
|
private final ClojureScript.F<String> hello;
|
||||||
|
private final ClojureScript.F<Number> add;
|
||||||
|
|
||||||
|
private ExampleTest(final TestCounter counter) {
|
||||||
|
this.counter = counter;
|
||||||
|
|
||||||
|
ClojureScript.loadScript("example.clj");
|
||||||
|
hello = ClojureScript.function("hello", String.class);
|
||||||
|
add = ClojureScript.function("add", Number.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test() {
|
||||||
|
counter.scope("hello", () -> {
|
||||||
|
assertHello("Clojure");
|
||||||
|
assertHello(new String[]{"easy", "hard"}[counter.mode()]);
|
||||||
|
});
|
||||||
|
counter.scope("add", () -> {
|
||||||
|
assertAdd();
|
||||||
|
assertAdd(1);
|
||||||
|
assertAdd(1, 2);
|
||||||
|
assertAdd(1, 2, 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHello(final String name) {
|
||||||
|
counter.test(() -> Asserts.assertEquals(
|
||||||
|
"Hello", "Hello, " + name + "!",
|
||||||
|
hello.call(new Engine.Result<>(name, name)).value()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertAdd(final int... numbers) {
|
||||||
|
final Engine.Result<?>[] args = Arrays.stream(numbers).mapToObj(v -> new Engine.Result<>(
|
||||||
|
v + "",
|
||||||
|
v
|
||||||
|
)).toArray(Engine.Result<?>[]::new);
|
||||||
|
counter.test(() -> Asserts.assertEquals(
|
||||||
|
Arrays.toString(numbers), Arrays.stream(numbers).sum(),
|
||||||
|
add.call(args).value().intValue()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Selector SELECTOR = new Selector(ExampleTest.class, "easy", "hard")
|
||||||
|
.variant("base", counter -> new ExampleTest(counter).test());
|
||||||
|
|
||||||
|
public static void main(final String... args) {
|
||||||
|
SELECTOR.main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
324
clojure/cljtest/linear/Item.java
Normal file
324
clojure/cljtest/linear/Item.java
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
package cljtest.linear;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.TestCounter;
|
||||||
|
import cljtest.ClojureScript;
|
||||||
|
import clojure.lang.IPersistentVector;
|
||||||
|
import common.Engine;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clojure bridge.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface Item {
|
||||||
|
Item ZERO = value(0);
|
||||||
|
Item ONE = value(1);
|
||||||
|
|
||||||
|
int dim();
|
||||||
|
|
||||||
|
boolean isValid();
|
||||||
|
|
||||||
|
Item refill(ExtendedRandom random);
|
||||||
|
|
||||||
|
Engine.Result<?> toClojure();
|
||||||
|
|
||||||
|
default Value mapValue(final DoubleUnaryOperator f) {
|
||||||
|
return value(f.applyAsDouble(value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default Vector map(final Function<Item, Item> f) {
|
||||||
|
throw new UnsupportedOperationException("map");
|
||||||
|
}
|
||||||
|
|
||||||
|
default int size() { throw new UnsupportedOperationException("size"); }
|
||||||
|
|
||||||
|
default Item get(final int index) { throw new UnsupportedOperationException("get"); }
|
||||||
|
|
||||||
|
default double value() {
|
||||||
|
throw new UnsupportedOperationException("getValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<Item> args(final int argc, final Item shape, final ExtendedRandom random) {
|
||||||
|
return Stream.generate(() -> shape.refill(random)).limit(argc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Item fromClojure(final Object value) {
|
||||||
|
if (value instanceof Number n) {
|
||||||
|
return value(n.doubleValue());
|
||||||
|
} else if (value instanceof IPersistentVector vector) {
|
||||||
|
return vector(IntStream.range(0, vector.length()).mapToObj(vector::nth).map(Item::fromClojure));
|
||||||
|
} else {
|
||||||
|
throw new AssertionError(value == null ? "null result" : "Unknown type " + value.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector vector(final Stream<? extends Item> items) {
|
||||||
|
return new Vector(items(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value value(final double value) {
|
||||||
|
return new Value(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Item> items(final Stream<? extends Item> items) {
|
||||||
|
return items.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
static Supplier<Item> generator(final int... dims) {
|
||||||
|
Supplier<Item> generator = () -> ZERO;
|
||||||
|
for (int i = dims.length - 1; i >= 0; i--) {
|
||||||
|
final int dim = dims[i];
|
||||||
|
final Supplier<Item> gen = generator;
|
||||||
|
generator = () -> vector(Stream.generate(gen).limit(dim));
|
||||||
|
}
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IntFunction<List<Item>> same(final Supplier<Item> generator) {
|
||||||
|
return same(generator.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static IntFunction<List<Item>> same(final Item shape) {
|
||||||
|
return n -> Collections.nCopies(n, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Engine.Result<?>[] toClojure(final List<Item> args) {
|
||||||
|
return toArray(args.stream().map(Item::toClojure));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Engine.Result<?>[] toArray(final Stream<? extends Engine.Result<?>> resultStream) {
|
||||||
|
return resultStream.toArray(Engine.Result<?>[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Fun> functions(final String prefix) {
|
||||||
|
return functions(prefix, Operation.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Fun> functions(final String prefix, final Operation... ops) {
|
||||||
|
return Arrays.stream(ops).map(op -> op.function(prefix)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
record Value(double value) implements Item {
|
||||||
|
public boolean isValid() {
|
||||||
|
return Double.isFinite(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dim() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value refill(final ExtendedRandom random) {
|
||||||
|
return new Value(random.nextInt(1, 99) / 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Engine.Result<?> toClojure() {
|
||||||
|
return LinearTester.number(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
return obj instanceof Value v && Asserts.isEqual(value, v.value, 1e-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Double.toString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Vector implements Item {
|
||||||
|
private final List<Item> items;
|
||||||
|
private final int dim;
|
||||||
|
|
||||||
|
private Vector(final List<Item> items) {
|
||||||
|
this.items = items;
|
||||||
|
dim = items.stream().mapToInt(Item::dim).max().orElse(0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return items.stream().allMatch(Item::isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dim() {
|
||||||
|
return dim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item get(final int index) {
|
||||||
|
return items.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector refill(final ExtendedRandom random) {
|
||||||
|
return vector(items.stream().map(item -> item.refill(random)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Engine.Result<?> toClojure() {
|
||||||
|
return LinearTester.vector(toArray(items.stream().map(Item::toClojure)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
return obj instanceof Vector v && items.equals(v.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return items.stream().map(Item::toString).collect(Collectors.joining(", ", "[", "]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector map(final Function<Item, Item> f) {
|
||||||
|
return vector(items.stream().map(f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fun {
|
||||||
|
private final Function<List<Item>, Item> expected;
|
||||||
|
private final ClojureScript.F<?> actual;
|
||||||
|
|
||||||
|
public Fun(final String name, final Function<List<Item>, Item> implementation) {
|
||||||
|
expected = implementation;
|
||||||
|
actual = ClojureScript.function(name, Object.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final TestCounter counter, final Stream<Item> argStream) {
|
||||||
|
final List<Item> args = items(argStream);
|
||||||
|
test(counter, args, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final TestCounter counter, final List<Item> args, final List<Item> fakeArgs) {
|
||||||
|
final Item expected = this.expected.apply(fakeArgs);
|
||||||
|
// if (!expected.isValid()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
test(counter, () -> {
|
||||||
|
final Engine.Result<?> result;
|
||||||
|
try {
|
||||||
|
result = actual.call(toClojure(args));
|
||||||
|
} catch (final RuntimeException | AssertionError e) {
|
||||||
|
throw new AssertionError("No error expected for " + actual.callToString(toClojure(args)), e);
|
||||||
|
}
|
||||||
|
final Item actual = fromClojure(result.value());
|
||||||
|
if (!expected.equals(actual)) {
|
||||||
|
throw new AssertionError(result.context() + ": expected " + expected + ", found " + actual);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// System.err.println("Testing? " + result.context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test(final TestCounter counter, final Runnable action) {
|
||||||
|
counter.test(() -> {
|
||||||
|
if (counter.getTestNo() % 1000 == 0) {
|
||||||
|
counter.println("Test " + counter.getTestNo());
|
||||||
|
}
|
||||||
|
action.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final int args, final Item shape, final TestCounter counter, final ExtendedRandom random) {
|
||||||
|
test(args, Item.same(shape), counter, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final int args, final IntFunction<List<Item>> shapes, final TestCounter counter, final ExtendedRandom random) {
|
||||||
|
test(shapes.apply(args), counter, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final List<Item> shapes, final TestCounter counter, final ExtendedRandom random) {
|
||||||
|
test(counter, shapes.stream().map(shape -> shape.refill(random)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectException(final TestCounter counter, final Stream<Item> items) {
|
||||||
|
expectException(counter, toClojure(items.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void expectException(final TestCounter counter, final Engine.Result<?>... args) {
|
||||||
|
test(counter, () -> {
|
||||||
|
final Engine.Result<Throwable> result = actual.expectException(args);
|
||||||
|
final boolean ok = result.value() instanceof AssertionError;
|
||||||
|
if (!ok) {
|
||||||
|
result.value().printStackTrace();
|
||||||
|
}
|
||||||
|
Asserts.assertTrue(
|
||||||
|
"AssertionError expected instead of " + result.value() + " in " + result.context(),
|
||||||
|
ok
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Operation {
|
||||||
|
ADD("+", (a, b) -> a + b, a -> a, ZERO),
|
||||||
|
SUB("-", (a, b) -> a - b, a -> -a, ZERO),
|
||||||
|
MUL("*", (a, b) -> a * b, a -> a, ONE),
|
||||||
|
DIV("d", (a, b) -> a / b, a -> 1 / a, ONE);
|
||||||
|
|
||||||
|
private final String suffix;
|
||||||
|
private final DoubleBinaryOperator binary;
|
||||||
|
private final DoubleUnaryOperator unary;
|
||||||
|
private final Item neutral;
|
||||||
|
|
||||||
|
Operation(final String suffix, final DoubleBinaryOperator binary, final DoubleUnaryOperator unary,
|
||||||
|
final Item neutral
|
||||||
|
) {
|
||||||
|
this.suffix = suffix;
|
||||||
|
this.binary = binary;
|
||||||
|
this.unary = unary;
|
||||||
|
this.neutral = neutral;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String suffix() {
|
||||||
|
return suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoubleBinaryOperator binary() {
|
||||||
|
return binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoubleUnaryOperator unary() {
|
||||||
|
return unary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item neutral() {
|
||||||
|
return neutral;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item apply(final List<Item> args) {
|
||||||
|
final Item first = args.get(0);
|
||||||
|
if (first instanceof Value) {
|
||||||
|
return value(args.size() == 1
|
||||||
|
? unary.applyAsDouble(first.value())
|
||||||
|
: args.stream().map(Value.class::cast).mapToDouble(Value::value).reduce(binary).getAsDouble());
|
||||||
|
} else {
|
||||||
|
return vector(IntStream.range(0, first.size())
|
||||||
|
.mapToObj(i -> apply(items(args.stream().map(Vector.class::cast).map(arg -> arg.get(i))))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Fun function(final String prefix) {
|
||||||
|
return new Fun(prefix + suffix, this::apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
clojure/cljtest/linear/LinearTest.java
Normal file
73
clojure/cljtest/linear/LinearTest.java
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package cljtest.linear;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.Selector;
|
||||||
|
import base.TestCounter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for
|
||||||
|
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#clojure-linear">Linear Clojure</a>
|
||||||
|
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class LinearTest {
|
||||||
|
// === Selector
|
||||||
|
|
||||||
|
public static final Selector SELECTOR = new Selector(LinearTester.class, "easy", "hard")
|
||||||
|
.variant("Base", v(LinearTester::new))
|
||||||
|
;
|
||||||
|
|
||||||
|
private LinearTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package-private*/ static Consumer<TestCounter> v(final Function<TestCounter, LinearTester> variant) {
|
||||||
|
return counter -> variant.apply(counter).test();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package-private*/ static Consumer<TestCounter> variant(final List<Item.Fun> functions, final Consumer<Test> variant) {
|
||||||
|
return v(counter -> new LinearTester(counter) {
|
||||||
|
@Override
|
||||||
|
protected void test(final int args) {
|
||||||
|
variant.accept(new Test(this, functions, args));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package-private */ record Test(
|
||||||
|
LinearTester test,
|
||||||
|
List<Item.Fun> functions,
|
||||||
|
int args
|
||||||
|
) {
|
||||||
|
public void test(final Supplier<Item> generator) {
|
||||||
|
test.test(args, functions, Item.same(generator));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final IntFunction<List<Item>> generator) {
|
||||||
|
test.test(args, functions, generator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHard() {
|
||||||
|
return test.isHard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectException(final int[] okDims, final int[][] failDims) {
|
||||||
|
test.expectException(functions, okDims, failDims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedRandom random() {
|
||||||
|
return test.random();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String... args) {
|
||||||
|
SELECTOR.main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
231
clojure/cljtest/linear/LinearTester.java
Normal file
231
clojure/cljtest/linear/LinearTester.java
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package cljtest.linear;
|
||||||
|
|
||||||
|
import base.Tester;
|
||||||
|
import base.TestCounter;
|
||||||
|
import cljtest.ClojureScript;
|
||||||
|
import clojure.lang.IPersistentVector;
|
||||||
|
import common.Engine;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tester for
|
||||||
|
* <a href="https://www.kgeorgiy.info/courses/paradigms/homeworks.html#clojure-linear">Linear Clojure</a>
|
||||||
|
* homework of <a href="https://www.kgeorgiy.info/courses/paradigms">Programming Paradigms</a> course.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class LinearTester extends Tester {
|
||||||
|
public static final ClojureScript.F<IPersistentVector> VECTOR_C = ClojureScript.function("clojure.core/vector", IPersistentVector.class);
|
||||||
|
|
||||||
|
private static final List<Item.Fun> VECTOR = Item.functions("v");
|
||||||
|
private static final List<Item.Fun> MATRIX = Item.functions("m");
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClojureScript.loadScript("linear.clj");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Item.Fun SCALAR = new Item.Fun("scalar", args -> Item.value(
|
||||||
|
IntStream.range(0, args.get(0).size())
|
||||||
|
.mapToDouble(i -> product(args.stream().map(arg -> arg.get(i))))
|
||||||
|
.sum()
|
||||||
|
));
|
||||||
|
|
||||||
|
public static final Item.Fun V_BY_S = new Item.Fun("v*s", args -> {
|
||||||
|
final double q = product(args.stream().skip(1));
|
||||||
|
return args.get(0).map(v -> v.mapValue(a -> a * q));
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final Item.Fun M_BY_S = new Item.Fun("m*s", args -> {
|
||||||
|
final double q = product(args.stream().skip(1));
|
||||||
|
return args.get(0).map(row -> row.map(v -> v.mapValue(a -> a * q)));
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final Item.Fun M_BY_V = new Item.Fun("m*v", args -> {
|
||||||
|
final Item matrix = args.get(0);
|
||||||
|
final Item vector = args.get(1);
|
||||||
|
final double[] result = new double[matrix.size()];
|
||||||
|
for (int i = 0; i < matrix.size(); i++) {
|
||||||
|
for (int j = 0; j < vector.size(); j++) {
|
||||||
|
result[i] += matrix.get(i).get(j).value() * vector.get(j).value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Item.vector(Arrays.stream(result).mapToObj(Item::value));
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final Item.Fun M_BY_M = new Item.Fun("m*m", args -> {
|
||||||
|
Item a = args.get(0);
|
||||||
|
for (final Item b : args.subList(1, args.size())) {
|
||||||
|
final double[][] result = new double[a.size()][b.get(0).size()];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
for (int j = 0; j < result[i].length; j++) {
|
||||||
|
for (int k = 0; k < b.size(); k++) {
|
||||||
|
result[i][j] += a.get(i).get(k).value() * b.get(k).get(j).value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a = Item.vector(Arrays.stream(result).map(row -> Item.vector(Arrays.stream(row).mapToObj(Item::value))));
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final Item.Fun VECT = new Item.Fun("vect", args -> {
|
||||||
|
double[] a = IntStream.range(0, 3).mapToDouble(i -> args.get(0).get(i).value()).toArray();
|
||||||
|
for (final Item bb : args.subList(1, args.size())) {
|
||||||
|
double[] b = IntStream.range(0, 3).mapToDouble(i -> bb.get(i).value()).toArray();
|
||||||
|
a = new double[]{a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]};
|
||||||
|
}
|
||||||
|
return Item.vector(Arrays.stream(a).mapToObj(Item::value));
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final Item.Fun TRANSPOSE = new Item.Fun("transpose", args -> {
|
||||||
|
final Item matrix = args.get(0);
|
||||||
|
return Item.vector(IntStream.range(0, matrix.get(0).size()).mapToObj(i -> matrix.map(row -> row.get(i))));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
private static double product(final Stream<Item> items) {
|
||||||
|
return items.mapToDouble(Item::value).reduce(1, (a, b) -> a * b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinearTester(final TestCounter counter) {
|
||||||
|
super(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Engine.Result<IPersistentVector> vector(final Number... xs) {
|
||||||
|
return wrap(LinearTester::number, xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Engine.Result<IPersistentVector> matrix(final Number[]... m) {
|
||||||
|
return wrap(LinearTester::vector, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <I, T> Engine.Result<IPersistentVector> wrap(final Function<I, Engine.Result<T>> wrapper, final I[] m) {
|
||||||
|
return vector(Arrays.stream(m).map(wrapper).toArray(Engine.Result[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Engine.Result<Number> number(final Number x) {
|
||||||
|
return new Engine.Result<>(x.toString(), x);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Engine.Result<IPersistentVector> vector(final Engine.Result<?>... xs) {
|
||||||
|
return VECTOR_C.call(xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Number[] row(final Number... numbers) {
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void test() {
|
||||||
|
runTest(2);
|
||||||
|
|
||||||
|
if (isHard()) {
|
||||||
|
runTest(1);
|
||||||
|
for (int i = 3; i <= 5; i++) {
|
||||||
|
runTest(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
expectException(VECTOR, new int[]{3}, new int[][]{{}, {3, 3}});
|
||||||
|
expectException(MATRIX, new int[]{3, 3}, new int[][]{{}, {3}, {3, 3, 3}});
|
||||||
|
expectException(List.of(VECT, SCALAR), new int[]{3}, new int[][]{{}, {3, 3}});
|
||||||
|
|
||||||
|
final Engine.Result<IPersistentVector> v123 = vector(1L, 2L, 3L);
|
||||||
|
final Engine.Result<IPersistentVector> v12 = vector(1.1, 2.1);
|
||||||
|
|
||||||
|
final Engine.Result<IPersistentVector> m123_456 = matrix(row(1.1, 2.1, 3.1), row(4.1, 5.1, 6.1));
|
||||||
|
|
||||||
|
M_BY_S.expectException(counter, m123_456, v123);
|
||||||
|
M_BY_V.expectException(counter, m123_456, v12);
|
||||||
|
M_BY_M.expectException(counter, m123_456, v123);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTest(final int args) {
|
||||||
|
counter.scope(args + " arg(s)", () -> test(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isHard() {
|
||||||
|
return counter.mode() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void expectException(final List<Item.Fun> funs, final int[] okDims, final int[][] failDims) {
|
||||||
|
final Supplier<Item> ok = Item.generator(okDims);
|
||||||
|
Stream.concat(Arrays.stream(failDims), corrupted(okDims)).map(Item::generator).forEach(fail -> {
|
||||||
|
expectException(funs, ok, fail);
|
||||||
|
expectException(funs, fail, ok);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<int[]> corrupted(final int... dims) {
|
||||||
|
return IntStream.range(0, dims.length)
|
||||||
|
.boxed()
|
||||||
|
.flatMap(i -> Stream.of(corruptIndex(i, -1, dims), corruptIndex(i, +1, dims)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] corruptIndex(final int i, final int delta, final int[] dims) {
|
||||||
|
final int[] nd = dims.clone();
|
||||||
|
nd[i] += delta;
|
||||||
|
return nd;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
protected final void expectException(final List<Item.Fun> funs, final Supplier<Item>... generators) {
|
||||||
|
for (final Item.Fun fun : funs) {
|
||||||
|
final Stream<Item> args = Arrays.stream(generators).map(Supplier::get);
|
||||||
|
fun.expectException(counter, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void test(final int args) {
|
||||||
|
for (int dim = 0; dim <= 10 / TestCounter.DENOMINATOR2; dim++) {
|
||||||
|
final Supplier<Item> generator = Item.generator(dim);
|
||||||
|
|
||||||
|
test(args, VECTOR, generator);
|
||||||
|
V_BY_S.test(counter, andScalars(args, generator));
|
||||||
|
SCALAR.test(args, generator.get(), counter, random());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int complexity = 1; complexity <= 20 / TestCounter.DENOMINATOR2; complexity++) {
|
||||||
|
for (int dim1 = 1; dim1 <= complexity; dim1++) {
|
||||||
|
final int dim2 = complexity - dim1;
|
||||||
|
if (dim2 > 0 || isHard()) {
|
||||||
|
final Supplier<Item> generator = Item.generator(dim1, dim2);
|
||||||
|
|
||||||
|
test(args, MATRIX, generator);
|
||||||
|
M_BY_S.test(counter, andScalars(args, generator));
|
||||||
|
M_BY_V.test(counter, Stream.of(generator.get().refill(random()), Item.generator(dim2).get().refill(
|
||||||
|
random())));
|
||||||
|
TRANSPOSE.test(counter, Stream.of(generator.get().refill(random())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int complex = complexity;
|
||||||
|
final int[] dims = IntStream.generate(() -> 1 + random().nextInt(complex)).limit(args + 1).toArray();
|
||||||
|
M_BY_M.test(counter, IntStream.range(0, args).mapToObj(i -> Item.generator(dims[i], dims[i + 1]).get().refill(
|
||||||
|
random())));
|
||||||
|
}
|
||||||
|
|
||||||
|
VECT.test(args, Item.generator(3).get(), counter, random());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<Item> andScalars(final int args, final Supplier<Item> generator) {
|
||||||
|
return Stream.concat(Stream.of(generator.get().refill(random())), Item.args(args - 1, Item.ZERO, random()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void test(final int args, final List<Item.Fun> funs, final IntFunction<List<Item>> generator) {
|
||||||
|
for (final Item.Fun fun : funs) {
|
||||||
|
fun.test(args, generator, counter, random());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void test(final int args, final List<Item.Fun> funs, final Supplier<Item> generator) {
|
||||||
|
test(args, funs, Item.same(generator));
|
||||||
|
}
|
||||||
|
}
|
||||||
7
clojure/example.clj
Normal file
7
clojure/example.clj
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
(defn hello [name]
|
||||||
|
(let [message (str "Hello, " name "!")]
|
||||||
|
(println " " message)
|
||||||
|
message))
|
||||||
|
|
||||||
|
(def add +)
|
||||||
|
|
||||||
8
clojure/examples.clj
Normal file
8
clojure/examples.clj
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
(load-file "examples/0_1_magic.clj")
|
||||||
|
|
||||||
|
(lecture "1. Functions")
|
||||||
|
(load-file "examples/1_1_intro.clj")
|
||||||
|
(load-file "examples/1_2_functions.clj")
|
||||||
|
(load-file "examples/1_3_lists.clj")
|
||||||
|
(load-file "examples/1_4_vectors.clj")
|
||||||
|
(load-file "examples/1_5_functions-2.clj")
|
||||||
86
clojure/examples/0_1_magic.clj
Normal file
86
clojure/examples/0_1_magic.clj
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
(defn lecture [name]
|
||||||
|
(println)
|
||||||
|
(let [line (clojure.string/join (repeat (+ 16 (count name)) "="))]
|
||||||
|
(println line)
|
||||||
|
(println "=== Lecture" name "===")
|
||||||
|
(println line)))
|
||||||
|
|
||||||
|
(defn chapter [name]
|
||||||
|
(println)
|
||||||
|
(println "==========" name "=========="))
|
||||||
|
|
||||||
|
(defn section [name]
|
||||||
|
(println)
|
||||||
|
(println "---" name "---"))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- safe-eval [expression]
|
||||||
|
(try
|
||||||
|
(eval expression)
|
||||||
|
(catch Throwable e e)))
|
||||||
|
|
||||||
|
(defmacro with-out-str-and-value [body]
|
||||||
|
`(let [s# (new java.io.StringWriter)]
|
||||||
|
(binding [*out* s#]
|
||||||
|
(let [v# (safe-eval ~body)]
|
||||||
|
(vector (str s#) v#)))))
|
||||||
|
|
||||||
|
(defn- init [seq]
|
||||||
|
(take (dec (count seq)) seq))
|
||||||
|
|
||||||
|
(defn- prepend [prefix]
|
||||||
|
(partial str prefix))
|
||||||
|
|
||||||
|
(defn- lines [indent & lines]
|
||||||
|
(apply str (map (prepend indent) (flatten lines))))
|
||||||
|
|
||||||
|
(defn- lines-collection [indent open close f value]
|
||||||
|
(let [items (mapv f value)]
|
||||||
|
(case (count items)
|
||||||
|
0 (str open close)
|
||||||
|
1 (str open (first items) close)
|
||||||
|
(lines indent
|
||||||
|
(str open (first items))
|
||||||
|
(map (prepend " ") (init (rest items)))
|
||||||
|
(str " " (last items) close)))))
|
||||||
|
|
||||||
|
(defn- remove-generated [value]
|
||||||
|
(clojure.string/replace value #"__[0-9]+#" "#"))
|
||||||
|
|
||||||
|
(defn- render [value]
|
||||||
|
(cond
|
||||||
|
(fn? value) (str "#function[" (clojure.string/replace (str (type value)) #"fn__[0-9]+" "fn") "]")
|
||||||
|
(delay? value) (str "#delay")
|
||||||
|
(instance? clojure.lang.Namespace value) (str "#namespace[" (ns-name value) "]")
|
||||||
|
(instance? Throwable value) (str (.getSimpleName (type value)) ": " (.getMessage value))
|
||||||
|
:else (remove-generated (pr-str value))))
|
||||||
|
|
||||||
|
(defn- prettify
|
||||||
|
([value] (prettify value "\n "))
|
||||||
|
([value indent]
|
||||||
|
(let [r-value (render value)]
|
||||||
|
(cond
|
||||||
|
(< (count (str indent r-value)) 80) r-value
|
||||||
|
(vector? value) (lines-collection indent "[" "]" #(clojure.string/triml (prettify % (str indent " "))) value)
|
||||||
|
(seq? value) (lines-collection indent "(" ")" #(clojure.string/triml (prettify % (str indent " "))) value)
|
||||||
|
(map? value) (lines-collection
|
||||||
|
indent "{" "}"
|
||||||
|
(fn [[key value]] (str (render key) " " (prettify value (str indent " "))))
|
||||||
|
value)
|
||||||
|
:else r-value))))
|
||||||
|
|
||||||
|
(defn example' [description & expressions]
|
||||||
|
{:pre [(not (empty? expressions))]}
|
||||||
|
(println (str " " description ": "))
|
||||||
|
(letfn [(run [expression]
|
||||||
|
(let [[output value] (with-out-str-and-value expression)]
|
||||||
|
(println " " (render expression) "->" (prettify value))
|
||||||
|
(if-not (empty? output)
|
||||||
|
(mapv #(println " >" %) (clojure.string/split-lines output)))))]
|
||||||
|
(mapv run expressions)))
|
||||||
|
|
||||||
|
(defmacro example [description & expressions]
|
||||||
|
`(apply example' ~description (quote ~expressions)))
|
||||||
|
|
||||||
|
(defn with-in-file [file action]
|
||||||
|
(with-in-str (slurp file) (action)))
|
||||||
90
clojure/examples/1_1_intro.clj
Normal file
90
clojure/examples/1_1_intro.clj
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
(chapter "Expressions and variables")
|
||||||
|
|
||||||
|
(section "Numbers and Expressions")
|
||||||
|
(example "Literals"
|
||||||
|
2
|
||||||
|
-10
|
||||||
|
"Hello world"
|
||||||
|
true)
|
||||||
|
(example "Simple expressions"
|
||||||
|
(+ 2 3)
|
||||||
|
(- 2 3)
|
||||||
|
(* 2 3)
|
||||||
|
(/ 2 3))
|
||||||
|
(example "Compound expressions"
|
||||||
|
(+ 2 (- 3 1)))
|
||||||
|
(example "Variable-args functions"
|
||||||
|
(- 10 1 2 3))
|
||||||
|
(example "Special cases"
|
||||||
|
(- 10))
|
||||||
|
(example "Nullary functions"
|
||||||
|
(+))
|
||||||
|
|
||||||
|
(section "Equality")
|
||||||
|
(example "Generic equality"
|
||||||
|
(= (* 2 3) 6)
|
||||||
|
(= (* 2 3) 6.0)
|
||||||
|
(= (* 2 3) 5)
|
||||||
|
(= (* 2 3) (+ 3 3)))
|
||||||
|
(example "Number equality"
|
||||||
|
(== (* 2 3) 6)
|
||||||
|
(== (* 2 3) 6.0)
|
||||||
|
(== (* 2 3) 5)
|
||||||
|
(== (* 2 3) (+ 3 3)))
|
||||||
|
(example "Reference equality"
|
||||||
|
(identical? (* 2 3) (+ 3 3))
|
||||||
|
(identical? (* 2 300) (+ 300 300)))
|
||||||
|
(example "Inequality"
|
||||||
|
(not (== (* 2 3) (+ 3 3))))
|
||||||
|
|
||||||
|
(section "Booleans")
|
||||||
|
(example "Literals"
|
||||||
|
true
|
||||||
|
false)
|
||||||
|
(example "not"
|
||||||
|
(not true)
|
||||||
|
(not false))
|
||||||
|
(example "and"
|
||||||
|
(and true true)
|
||||||
|
(and true false)
|
||||||
|
(and false false)
|
||||||
|
(and true true false false))
|
||||||
|
(example "or"
|
||||||
|
(or true true)
|
||||||
|
(or true false)
|
||||||
|
(or false false)
|
||||||
|
(or true true false false))
|
||||||
|
|
||||||
|
(section "Variables")
|
||||||
|
(example "Define"
|
||||||
|
(def x 10))
|
||||||
|
(example "Use"
|
||||||
|
(* x (+ x 3)))
|
||||||
|
(example "Output"
|
||||||
|
(println x))
|
||||||
|
|
||||||
|
(section "First-order functions")
|
||||||
|
(example "Output function"
|
||||||
|
+)
|
||||||
|
(example "Assign function"
|
||||||
|
(def add +))
|
||||||
|
(example "Variable as a function"
|
||||||
|
(add 10 20 30))
|
||||||
|
|
||||||
|
(section "Simple types")
|
||||||
|
(example "Integers"
|
||||||
|
(type 10))
|
||||||
|
(example "Floating-point"
|
||||||
|
(type 10.0))
|
||||||
|
(example "Rational"
|
||||||
|
(type (/ 2 3))
|
||||||
|
(type 2/3))
|
||||||
|
(example "BigInt"
|
||||||
|
(type 2N))
|
||||||
|
(example "String"
|
||||||
|
(type "Hello"))
|
||||||
|
(example "Booleans"
|
||||||
|
(type true))
|
||||||
|
(example "Type conversion"
|
||||||
|
(double 2/3)
|
||||||
|
(int 2/3))
|
||||||
91
clojure/examples/1_2_functions.clj
Normal file
91
clojure/examples/1_2_functions.clj
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
(chapter "Custom Functions")
|
||||||
|
|
||||||
|
(section "Simple Functions")
|
||||||
|
(example "Define function"
|
||||||
|
(defn square [x] (* x x)))(example "Use function"
|
||||||
|
(square 8))
|
||||||
|
(example "Multi-arg functions"
|
||||||
|
(defn mul-add [a b c] (+ (* a b) c))
|
||||||
|
(mul-add 3 10 5))
|
||||||
|
(example "Nullary function"
|
||||||
|
(defn nullary [] 10)
|
||||||
|
(nullary))
|
||||||
|
(example "Anonymous functions"
|
||||||
|
((fn [x] (+ x x)) 10)
|
||||||
|
(#(+ % %) 10)
|
||||||
|
(#(+ %1 %2) 10 20))
|
||||||
|
(example "Functions as values"
|
||||||
|
(defn twice [f] (fn [a] (f (f a))))
|
||||||
|
((twice square) 3))
|
||||||
|
|
||||||
|
(section "Recursive Functions")
|
||||||
|
(example "Recursive Fibonacci"
|
||||||
|
(defn rec-fib [n]
|
||||||
|
(cond
|
||||||
|
(== 0 n) 1
|
||||||
|
(== 1 n) 1
|
||||||
|
:else (+ (rec-fib (- n 1))
|
||||||
|
(rec-fib (- n 2)))))
|
||||||
|
(rec-fib 40))
|
||||||
|
(example "Memoized Fibonacci"
|
||||||
|
(def mem-fib
|
||||||
|
(memoize
|
||||||
|
(fn [n]
|
||||||
|
(cond
|
||||||
|
(== 0 n) 1
|
||||||
|
(== 1 n) 1
|
||||||
|
:else (+ (mem-fib (- n 1)) (mem-fib (- n 2)))))))
|
||||||
|
(mem-fib 90))
|
||||||
|
(example "Tail-recursive Fibonacci"
|
||||||
|
(defn iter-fib [n]
|
||||||
|
(letfn [(iter-fib' [n a b]
|
||||||
|
(if (== 0 n)
|
||||||
|
a
|
||||||
|
(iter-fib' (- n 1) b (+' a b))))]
|
||||||
|
(iter-fib' n 1 1)))
|
||||||
|
(iter-fib 90)
|
||||||
|
(iter-fib 10000)
|
||||||
|
(defn iter-fib-recur [n]
|
||||||
|
(letfn [(iter-fib' [n a b]
|
||||||
|
(if (== 0 n)
|
||||||
|
a
|
||||||
|
(recur (- n 1) b (+' a b))))]
|
||||||
|
(iter-fib' n 1 1)))
|
||||||
|
(iter-fib-recur 10000))
|
||||||
|
(example "Explicit loop Fibonacci"
|
||||||
|
(defn loop-fib [n]
|
||||||
|
(loop [n n a 1 b 1]
|
||||||
|
(if (== 0 n)
|
||||||
|
a
|
||||||
|
(recur (- n 1) b (+ a b)))))
|
||||||
|
(loop-fib 90))
|
||||||
|
|
||||||
|
(section "Pre and Post conditions")
|
||||||
|
(example "Fast power"
|
||||||
|
(defn power
|
||||||
|
"Raises a to the b-th power"
|
||||||
|
[a b]
|
||||||
|
{:pre [(<= 0 b)]
|
||||||
|
:post [(or (zero? b) (zero? a) (zero? (rem % a)))]}
|
||||||
|
(cond
|
||||||
|
(zero? b) 1
|
||||||
|
(even? b) (power (* a a) (quot b 2))
|
||||||
|
(odd? b) (* a (power a (dec b))))))
|
||||||
|
(example "Pre and postconditions ok"
|
||||||
|
(power 2 5)
|
||||||
|
(power 2 0)
|
||||||
|
(power 0 2))
|
||||||
|
(example "Precondition violated"
|
||||||
|
(power 2 -5))
|
||||||
|
(example "Invalid postcondition"
|
||||||
|
(defn ipower
|
||||||
|
[a b]
|
||||||
|
{:pre [(<= 0 b)]
|
||||||
|
:post [(zero? (rem % a)) (<= 0 %)]}
|
||||||
|
(power a b)))
|
||||||
|
(example "First part of invalid postcondition violated"
|
||||||
|
(ipower 2 0)
|
||||||
|
(power 2 0))
|
||||||
|
(example "Second part of invalid postcondition violated"
|
||||||
|
(ipower -2 3)
|
||||||
|
(power -2 3))
|
||||||
67
clojure/examples/1_3_lists.clj
Normal file
67
clojure/examples/1_3_lists.clj
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
(chapter "Lists")
|
||||||
|
|
||||||
|
(section "Definition and tests")
|
||||||
|
(example "Lists"
|
||||||
|
(list 1 2)
|
||||||
|
(list 1 2 "Hello" 3 4)
|
||||||
|
(list))
|
||||||
|
(example "List type"
|
||||||
|
(type (list 1 2))
|
||||||
|
(type (list))
|
||||||
|
(type ()))
|
||||||
|
(example "List variable"
|
||||||
|
(def lst (list 1 2 "Hello" 3 4)))
|
||||||
|
(example "List test"
|
||||||
|
(list? lst)
|
||||||
|
(list? (list 1))
|
||||||
|
(list? ()))
|
||||||
|
|
||||||
|
(section "Operations")
|
||||||
|
(example "Length"
|
||||||
|
(count lst))
|
||||||
|
(example "Head"
|
||||||
|
(first lst))
|
||||||
|
(example "Tail"
|
||||||
|
(rest lst))
|
||||||
|
(example "Last"
|
||||||
|
(last lst))
|
||||||
|
(example "Indexing"
|
||||||
|
(nth lst 0)
|
||||||
|
(nth lst 1)
|
||||||
|
(nth lst 2)
|
||||||
|
(nth lst 10)
|
||||||
|
(nth lst 10 "none"))
|
||||||
|
(example "Add element"
|
||||||
|
(cons 0 lst))
|
||||||
|
(example "Add elements"
|
||||||
|
(conj lst 100 200))
|
||||||
|
(example "Emptiness test"
|
||||||
|
(empty? (rest (list 1)))
|
||||||
|
(empty? (list))
|
||||||
|
(empty? ())
|
||||||
|
(empty? lst))
|
||||||
|
|
||||||
|
(section "Folds")
|
||||||
|
(example "Left fold"
|
||||||
|
(defn foldLeft
|
||||||
|
"Applies a binary operator f to a zero value and all elements of the list, going left to right"
|
||||||
|
[zero f items]
|
||||||
|
(if (empty? items)
|
||||||
|
zero
|
||||||
|
(foldLeft (f zero (first items)) f (rest items))))
|
||||||
|
(foldLeft 0 - (list 1 2 3 4)))
|
||||||
|
(example "Right fold"
|
||||||
|
(defn foldRight [zero f items]
|
||||||
|
"Applies a binary operator f to a zero value and all elements of the list, going right to left"
|
||||||
|
(if (empty? items)
|
||||||
|
zero
|
||||||
|
(f (first items) (foldRight zero f (rest items)))))
|
||||||
|
(foldRight 0 - (list 1 2 3 4)))
|
||||||
|
(example "Tail-call optimised left fold"
|
||||||
|
(defn foldLeft' [zero f items]
|
||||||
|
(if (empty? items)
|
||||||
|
zero
|
||||||
|
(recur (f zero (first items)) f (rest items))))
|
||||||
|
(count (range 1000000))
|
||||||
|
(foldLeft 0 + (range 1000000))
|
||||||
|
(foldLeft' 0 + (range 1000000)))
|
||||||
22
clojure/examples/1_4_vectors.clj
Normal file
22
clojure/examples/1_4_vectors.clj
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
(chapter "Vectors")
|
||||||
|
(example "Vectors"
|
||||||
|
(vector 1 2)
|
||||||
|
(vector 1 2 "Hello" 3 4)
|
||||||
|
[1 2]
|
||||||
|
(def vect [1 2 "Hello" 3 4]))
|
||||||
|
(example "Vector type"
|
||||||
|
(type [1 2])
|
||||||
|
(type (vector))
|
||||||
|
(type []))
|
||||||
|
(example "Queries"
|
||||||
|
(count vect)
|
||||||
|
(nth vect 2)
|
||||||
|
(nth vect 20 "none")
|
||||||
|
(vect 2)
|
||||||
|
(vect 10))
|
||||||
|
(example "Modifications"
|
||||||
|
(conj vect 100 200)
|
||||||
|
(peek vect)
|
||||||
|
(pop vect)
|
||||||
|
(assoc vect 0 100)
|
||||||
|
(assoc vect 0 100 2 200))
|
||||||
42
clojure/examples/1_5_functions-2.clj
Normal file
42
clojure/examples/1_5_functions-2.clj
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
(chapter "High-order Functions")
|
||||||
|
|
||||||
|
(section "Ordinary functions")
|
||||||
|
(example "Identity function"
|
||||||
|
(identity [1 2 3]))
|
||||||
|
(example "Constant function"
|
||||||
|
((constantly 10) 20 30))
|
||||||
|
|
||||||
|
(section "High-order functions")
|
||||||
|
(example "Function composition"
|
||||||
|
((comp square square square) 2)
|
||||||
|
((comp #(* % 2) #(+ % 2)) 10))
|
||||||
|
(example "Currying"
|
||||||
|
(def sum (partial foldLeft' 0 +))
|
||||||
|
(sum [1 2 3]))
|
||||||
|
(example "Reduce"
|
||||||
|
(def sum' (partial reduce + 0))
|
||||||
|
(sum' [1 2 3]))
|
||||||
|
(example "Application"
|
||||||
|
(apply + [1 2 3]))
|
||||||
|
(example "Map"
|
||||||
|
(mapv inc (range 10)))
|
||||||
|
(example "Juxtaposition"
|
||||||
|
((juxt + - * /) 1 2 3 4))
|
||||||
|
|
||||||
|
(section "Variable-argument functions")
|
||||||
|
(example "Sum of squares"
|
||||||
|
(defn sumSquares [& xs] (apply + (map square xs)))
|
||||||
|
(sumSquares 3 4))
|
||||||
|
(example "Sum of squares (anonymous)"
|
||||||
|
(#(apply + (map square %&)) 3 4))
|
||||||
|
|
||||||
|
(example "Explicit multi-arity"
|
||||||
|
(defn countArgs
|
||||||
|
([] "zero")
|
||||||
|
([a] "one")
|
||||||
|
([a b] "two")
|
||||||
|
([a b & as] (str (+ 2 (count as)))))
|
||||||
|
(countArgs)
|
||||||
|
(countArgs 1)
|
||||||
|
(countArgs 1 2)
|
||||||
|
(countArgs 1 2 3))
|
||||||
BIN
clojure/lib/clojure-1.12.4.jar
Normal file
BIN
clojure/lib/clojure-1.12.4.jar
Normal file
Binary file not shown.
BIN
clojure/lib/core.specs.alpha-0.4.74.jar
Normal file
BIN
clojure/lib/core.specs.alpha-0.4.74.jar
Normal file
Binary file not shown.
BIN
clojure/lib/spec.alpha-0.5.238.jar
Normal file
BIN
clojure/lib/spec.alpha-0.5.238.jar
Normal file
Binary file not shown.
92
clojure/linear.clj
Normal file
92
clojure/linear.clj
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
(defn scalar? [x] (number? x))
|
||||||
|
|
||||||
|
(defn v? [x]
|
||||||
|
(and (vector? x) (every? number? x)))
|
||||||
|
|
||||||
|
(defn m? [x]
|
||||||
|
(and (vector? x) (every? v? x)))
|
||||||
|
|
||||||
|
(defn- same-length? [& vs]
|
||||||
|
(apply = (map count vs)))
|
||||||
|
|
||||||
|
(defn- same-shape? [& ms]
|
||||||
|
(apply = (map (fn [m] (mapv count m)) ms)))
|
||||||
|
|
||||||
|
(defn- check [cond msg]
|
||||||
|
(assert cond msg))
|
||||||
|
|
||||||
|
(defn- vec-op [op & vs]
|
||||||
|
(check (every? v? vs) "Все аргументы должны быть векторами чисел")
|
||||||
|
(check (apply same-length? vs)
|
||||||
|
(str "Векторы должны быть одинаковой длины, получено: " (mapv count vs)))
|
||||||
|
(apply mapv op vs))
|
||||||
|
|
||||||
|
(def v+ (partial vec-op +))
|
||||||
|
(def v- (partial vec-op -))
|
||||||
|
(def v* (partial vec-op *))
|
||||||
|
(def vd (partial vec-op /))
|
||||||
|
|
||||||
|
(defn scalar [& vs]
|
||||||
|
(reduce + (apply v* vs)))
|
||||||
|
|
||||||
|
(defn vect
|
||||||
|
"Векторное произведение произвольного числа 3-мерных векторов."
|
||||||
|
[& vs]
|
||||||
|
(check (every? v? vs) "Все аргументы должны быть векторами")
|
||||||
|
(check (every? #(= 3 (count %)) vs)
|
||||||
|
"Векторное произведение определено только для 3-мерных векторов")
|
||||||
|
(reduce
|
||||||
|
(fn [a b]
|
||||||
|
(let [[a0 a1 a2] a
|
||||||
|
[b0 b1 b2] b]
|
||||||
|
[(- (* a1 b2) (* a2 b1))
|
||||||
|
(- (* a2 b0) (* a0 b2))
|
||||||
|
(- (* a0 b1) (* a1 b0))]))
|
||||||
|
vs))
|
||||||
|
|
||||||
|
(defn v*s [v & scalars]
|
||||||
|
(check (v? v) "Первый аргумент должен быть вектором")
|
||||||
|
(check (every? scalar? scalars) "Остальные аргументы должны быть скалярами")
|
||||||
|
(let [s (reduce * scalars)]
|
||||||
|
(mapv #(* % s) v)))
|
||||||
|
|
||||||
|
(defn- mat-op [op & ms]
|
||||||
|
(check (every? m? ms) "Все аргументы должны быть матрицами")
|
||||||
|
(check (apply same-shape? ms)
|
||||||
|
(str "Матрицы должны иметь одинаковую форму, получено: "
|
||||||
|
(mapv #(vector (count %) (count (first %))) ms)))
|
||||||
|
(apply mapv (fn [& rows] (apply vec-op op rows)) ms))
|
||||||
|
|
||||||
|
(def m+ (partial mat-op +))
|
||||||
|
(def m- (partial mat-op -))
|
||||||
|
(def m* (partial mat-op *))
|
||||||
|
(def md (partial mat-op /))
|
||||||
|
|
||||||
|
(defn m*s [m & scalars]
|
||||||
|
(check (m? m) "Первый аргумент должен быть матрицей")
|
||||||
|
(check (every? scalar? scalars) "Остальные аргументы должны быть скалярами")
|
||||||
|
(let [s (reduce * scalars)]
|
||||||
|
(mapv (fn [row] (mapv #(* % s) row)) m)))
|
||||||
|
|
||||||
|
(defn transpose [m]
|
||||||
|
(check (m? m) "Аргумент должен быть матрицей")
|
||||||
|
(apply mapv vector m))
|
||||||
|
|
||||||
|
(defn m*v [m v]
|
||||||
|
(check (m? m) "Первый аргумент должен быть матрицей")
|
||||||
|
(check (v? v) "Второй аргумент должен быть вектором")
|
||||||
|
(check (= (count (first m)) (count v))
|
||||||
|
(str "Число столбцов матрицы (" (count (first m))
|
||||||
|
") должно совпадать с длиной вектора (" (count v) ")"))
|
||||||
|
(mapv #(scalar % v) m))
|
||||||
|
|
||||||
|
(defn m*m [& ms]
|
||||||
|
(check (every? m? ms) "Все аргументы должны быть матрицами")
|
||||||
|
(reduce
|
||||||
|
(fn [a b]
|
||||||
|
(check (= (count (first a)) (count b))
|
||||||
|
(str "Число столбцов левой матрицы (" (count (first a))
|
||||||
|
") должно совпадать с числом строк правой (" (count b) ")"))
|
||||||
|
(let [bt (transpose b)]
|
||||||
|
(mapv (fn [row] (mapv #(scalar row %) bt)) a)))
|
||||||
|
ms))
|
||||||
92
common/base/Asserts.java
Normal file
92
common/base/Asserts.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||||
|
public final class Asserts {
|
||||||
|
static {
|
||||||
|
Locale.setDefault(Locale.US);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Asserts() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertEquals(final String message, final Object expected, final Object actual) {
|
||||||
|
final String reason = String.format("%s:%n expected `%s`,%n actual `%s`",
|
||||||
|
message, toString(expected), toString(actual));
|
||||||
|
assertTrue(reason, Objects.deepEquals(expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(final Object value) {
|
||||||
|
if (value != null && value.getClass().isArray()) {
|
||||||
|
final String result = Arrays.deepToString(new Object[]{value});
|
||||||
|
return result.substring(1, result.length() - 1);
|
||||||
|
} else {
|
||||||
|
return Objects.toString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void assertEquals(final String message, final List<T> expected, final List<T> actual) {
|
||||||
|
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||||
|
assertEquals(message + ":" + (i + 1), expected.get(i), actual.get(i));
|
||||||
|
}
|
||||||
|
assertEquals(message + ": Number of items", expected.size(), actual.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertTrue(final String message, final boolean value) {
|
||||||
|
if (!value) {
|
||||||
|
throw error("%s", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertEquals(final String message, final double expected, final double actual, final double precision) {
|
||||||
|
assertTrue(
|
||||||
|
String.format("%s: Expected %.12f, found %.12f", message, expected, actual),
|
||||||
|
isEqual(expected, actual, precision)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean is(final double expected, final double actual, final double precision) {
|
||||||
|
return Math.abs(expected - actual) < precision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEqual(final double expected, final double actual, final double precision) {
|
||||||
|
final double error = Math.abs(actual - expected);
|
||||||
|
return error <= precision
|
||||||
|
|| error <= precision * Math.abs(expected)
|
||||||
|
|| !Double.isFinite(expected)
|
||||||
|
|| Math.abs(expected) > 1e100
|
||||||
|
|| Math.abs(expected) < precision && !Double.isFinite(actual)
|
||||||
|
|| is(Math.PI, expected, precision) && is(0, actual, precision)
|
||||||
|
|| is(0, expected, precision) && is(Math.PI, actual, precision)
|
||||||
|
|| is(-Math.PI, expected, precision) && is(Math.PI, actual, precision)
|
||||||
|
|| is(Math.PI, expected, precision) && is(-Math.PI, actual, precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertSame(final String message, final Object expected, final Object actual) {
|
||||||
|
assertTrue(String.format("%s: expected same objects: %s and %s", message, expected, actual), expected == actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkAssert(final Class<?> c) {
|
||||||
|
if (!c.desiredAssertionStatus()) {
|
||||||
|
throw error("You should enable assertions by running 'java -ea %s'", c.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AssertionError error(final String format, final Object... args) {
|
||||||
|
final String message = String.format(format, args);
|
||||||
|
return args.length > 0 && args[args.length - 1] instanceof Throwable
|
||||||
|
? new AssertionError(message, (Throwable) args[args.length - 1])
|
||||||
|
: new AssertionError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printStackTrace(final String message) {
|
||||||
|
new Exception(message).printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
common/base/BaseChecker.java
Normal file
20
common/base/BaseChecker.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public abstract class BaseChecker {
|
||||||
|
protected final TestCounter counter;
|
||||||
|
|
||||||
|
protected BaseChecker(final TestCounter counter) {
|
||||||
|
this.counter = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedRandom random() {
|
||||||
|
return counter.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int mode() {
|
||||||
|
return counter.mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
95
common/base/Either.java
Normal file
95
common/base/Either.java
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface Either<L, R> {
|
||||||
|
<NR> Either<L, NR> mapRight(final Function<? super R, NR> f);
|
||||||
|
<NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f);
|
||||||
|
<T> T either(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
|
||||||
|
|
||||||
|
boolean isRight();
|
||||||
|
|
||||||
|
L getLeft();
|
||||||
|
R getRight();
|
||||||
|
|
||||||
|
static <L, R> Either<L, R> right(final R value) {
|
||||||
|
return new Either<>() {
|
||||||
|
@Override
|
||||||
|
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||||
|
return right(f.apply(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||||
|
return f.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||||
|
return rf.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRight() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public L getLeft() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R getRight() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Right(%s)", value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <L, R> Either<L, R> left(final L value) {
|
||||||
|
return new Either<>() {
|
||||||
|
@Override
|
||||||
|
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||||
|
return left(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||||
|
return left(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||||
|
return lf.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRight() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public L getLeft() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R getRight() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Left(%s)", value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
89
common/base/ExtendedRandom.java
Normal file
89
common/base/ExtendedRandom.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class ExtendedRandom {
|
||||||
|
public static final String ENGLISH = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
public static final String RUSSIAN = "абвгдеежзийклмнопрстуфхцчшщъыьэюя";
|
||||||
|
public static final String GREEK = "αβγŋδεζηθικλμνξοπρτυφχψω";
|
||||||
|
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||||
|
public static final String SPACES = " \t\n\u000B\u2029\f";
|
||||||
|
|
||||||
|
private final Random random;
|
||||||
|
|
||||||
|
public ExtendedRandom(final Random random) {
|
||||||
|
this.random = random;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedRandom(final Class<?> owner) {
|
||||||
|
this(new Random(7912736473497634913L + owner.getName().hashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String randomString(final String chars) {
|
||||||
|
return randomChar(chars) + (random.nextBoolean() ? "" : randomString(chars));
|
||||||
|
}
|
||||||
|
|
||||||
|
public char randomChar(final String chars) {
|
||||||
|
return chars.charAt(nextInt(chars.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String randomString(final String chars, final int length) {
|
||||||
|
final StringBuilder string = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
string.append(randomChar(chars));
|
||||||
|
}
|
||||||
|
return string.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String randomString(final String chars, final int minLength, final int maxLength) {
|
||||||
|
return randomString(chars, nextInt(minLength, maxLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean nextBoolean() {
|
||||||
|
return random.nextBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nextInt() {
|
||||||
|
return random.nextInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nextInt(final int min, final int max) {
|
||||||
|
return nextInt(max - min + 1) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nextInt(final int n) {
|
||||||
|
return random.nextInt(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final <T> T randomItem(final T... items) {
|
||||||
|
return items[nextInt(items.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T randomItem(final List<T> items) {
|
||||||
|
return items.get(nextInt(items.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Random getRandom() {
|
||||||
|
return random;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> List<T> random(final int list, final Function<ExtendedRandom, T> generator) {
|
||||||
|
return Stream.generate(() -> generator.apply(this)).limit(list).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double nextDouble() {
|
||||||
|
return random.nextDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E> void shuffle(final List<E> all) {
|
||||||
|
Collections.shuffle(all, random);
|
||||||
|
}
|
||||||
|
}
|
||||||
92
common/base/Functional.java
Normal file
92
common/base/Functional.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class Functional {
|
||||||
|
private Functional() {}
|
||||||
|
|
||||||
|
public static <T, R> List<R> map(final Collection<T> items, final Function<? super T, ? extends R> f) {
|
||||||
|
return items.stream().map(f).collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, R> List<R> map(final List<T> items, final BiFunction<? super Integer, ? super T, ? extends R> f) {
|
||||||
|
return IntStream.range(0, items.size())
|
||||||
|
.mapToObj(i -> f.apply(i, items.get(i)))
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <K, T, R> Map<K, R> mapValues(final Map<K, T> map, final Function<T, R> f) {
|
||||||
|
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <K, T> Map<K, T> mergeMaps(final Map<K, T>... maps) {
|
||||||
|
return Stream.of(maps).flatMap(m -> m.entrySet().stream())
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> List<T> concat(final Collection<? extends T>... items) {
|
||||||
|
final List<T> result = new ArrayList<>();
|
||||||
|
for (final Collection<? extends T> item : items) {
|
||||||
|
result.addAll(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> append(final Collection<T> collection, final T item) {
|
||||||
|
final List<T> list = new ArrayList<>(collection);
|
||||||
|
list.add(item);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<List<T>> allValues(final List<T> vals, final int length) {
|
||||||
|
return Stream.generate(() -> vals)
|
||||||
|
.limit(length)
|
||||||
|
.reduce(
|
||||||
|
List.of(List.of()),
|
||||||
|
(prev, next) -> next.stream()
|
||||||
|
.flatMap(value -> prev.stream().map(list -> append(list, value)))
|
||||||
|
.toList(),
|
||||||
|
(prev, next) -> next.stream()
|
||||||
|
.flatMap(suffix -> prev.stream().map(prefix -> concat(prefix, suffix)))
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <K, V> V get(final Map<K, V> map, final K key) {
|
||||||
|
final V result = map.get(key);
|
||||||
|
if (result == null) {
|
||||||
|
throw new NullPointerException(key.toString() + " in " + map(map.keySet(), Objects::toString));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addRange(final List<Integer> values, final int d, final int c) {
|
||||||
|
for (int i = -d; i <= d; i++) {
|
||||||
|
values.add(c + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void forEachPair(final T[] items, final BiConsumer<? super T, ? super T> consumer) {
|
||||||
|
assert items.length % 2 == 0;
|
||||||
|
IntStream.range(0, items.length / 2).forEach(i -> consumer.accept(items[i * 2], items[i * 2 + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static <T> List<Pair<T, T>> toPairs(final T[] items) {
|
||||||
|
assert items.length % 2 == 0;
|
||||||
|
return IntStream.range(0, items.length / 2)
|
||||||
|
.mapToObj(i -> Pair.of(items[i * 2], items[i * 2 + 1]))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
56
common/base/Log.java
Normal file
56
common/base/Log.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class Log {
|
||||||
|
private final Pattern LINES = Pattern.compile("\n");
|
||||||
|
private int indent;
|
||||||
|
|
||||||
|
public static Supplier<Void> action(final Runnable action) {
|
||||||
|
return () -> {
|
||||||
|
action.run();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scope(final String name, final Runnable action) {
|
||||||
|
scope(name, action(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T scope(final String name, final Supplier<T> action) {
|
||||||
|
println(name);
|
||||||
|
indent++;
|
||||||
|
try {
|
||||||
|
return silentScope(name, action);
|
||||||
|
} finally {
|
||||||
|
indent--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T silentScope(final String ignoredName, final Supplier<T> action) {
|
||||||
|
return action.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||||
|
public void println(final Object value) {
|
||||||
|
for (final String line : LINES.split(String.valueOf(value))) {
|
||||||
|
System.out.println(indent() + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void format(final String format, final Object... args) {
|
||||||
|
println(String.format(format,args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String indent() {
|
||||||
|
return " ".repeat(indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getIndent() {
|
||||||
|
return indent;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
common/base/MainChecker.java
Normal file
28
common/base/MainChecker.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||||
|
public final class MainChecker {
|
||||||
|
private final Runner runner;
|
||||||
|
|
||||||
|
public MainChecker(final Runner runner) {
|
||||||
|
this.runner = runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> run(final TestCounter counter, final String... input) {
|
||||||
|
return runner.run(counter, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> run(final TestCounter counter, final List<String> input) {
|
||||||
|
return runner.run(counter, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||||
|
runner.testEquals(counter, input, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
common/base/Named.java
Normal file
15
common/base/Named.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public record Named<T>(String name, T value) {
|
||||||
|
public static <T> Named<T> of(final String name, final T f) {
|
||||||
|
return new Named<>(name, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
common/base/Pair.java
Normal file
44
common/base/Pair.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused"})
|
||||||
|
public record Pair<F, S>(F first, S second) {
|
||||||
|
public static <F, S> Pair<F, S> of(final F first, final S second) {
|
||||||
|
return new Pair<>(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <F, S> Pair<F, S> of(final Map.Entry<F, S> e) {
|
||||||
|
return of(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <F, S> UnaryOperator<Pair<F, S>> lift(final UnaryOperator<F> f, final UnaryOperator<S> s) {
|
||||||
|
return p -> of(f.apply(p.first), s.apply(p.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <F, S> BinaryOperator<Pair<F, S>> lift(final BinaryOperator<F> f, final BinaryOperator<S> s) {
|
||||||
|
return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, F, S> Function<T, Pair<F, S>> tee(
|
||||||
|
final Function<? super T, ? extends F> f,
|
||||||
|
final Function<? super T, ? extends S> s
|
||||||
|
) {
|
||||||
|
return t -> of(f.apply(t), s.apply(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + first + ", " + second + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public <R> Pair<F, R> second(final R second) {
|
||||||
|
return new Pair<>(first, second);
|
||||||
|
}
|
||||||
|
}
|
||||||
185
common/base/Runner.java
Normal file
185
common/base/Runner.java
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Runner {
|
||||||
|
List<String> run(final TestCounter counter, final List<String> input);
|
||||||
|
|
||||||
|
default List<String> run(final TestCounter counter, final String... input) {
|
||||||
|
return run(counter, List.of(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||||
|
counter.test(() -> {
|
||||||
|
final List<String> actual = run(counter, input);
|
||||||
|
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||||
|
final String exp = expected.get(i);
|
||||||
|
final String act = actual.get(i);
|
||||||
|
if (!exp.equalsIgnoreCase(act)) {
|
||||||
|
Asserts.assertEquals("Line " + (i + 1), exp, act);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Asserts.assertEquals("Number of lines", expected.size(), actual.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Packages packages(final String... packages) {
|
||||||
|
return new Packages(List.of(packages));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface CommentRunner {
|
||||||
|
List<String> run(String comment, TestCounter counter, List<String> input);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Packages {
|
||||||
|
private final List<String> packages;
|
||||||
|
|
||||||
|
private Packages(final List<String> packages) {
|
||||||
|
this.packages = packages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runner std(final String className) {
|
||||||
|
final CommentRunner main = main(className);
|
||||||
|
return (counter, input) -> counter.call("io", () -> {
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
try (final PrintWriter writer = new PrintWriter(baos)) {
|
||||||
|
input.forEach(writer::println);
|
||||||
|
}
|
||||||
|
|
||||||
|
final InputStream oldIn = System.in;
|
||||||
|
try {
|
||||||
|
System.setIn(new ByteArrayInputStream(baos.toByteArray()));
|
||||||
|
return main.run(String.format("[%d input lines]", input.size()), counter, List.of());
|
||||||
|
} finally {
|
||||||
|
System.setIn(oldIn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConfusingMainMethod")
|
||||||
|
public CommentRunner main(final String className) {
|
||||||
|
final Method method = findMain(className);
|
||||||
|
|
||||||
|
return (comment, counter, input) -> {
|
||||||
|
counter.format("Running test %02d: java %s %s%n", counter.getTestNo(), method.getDeclaringClass().getName(), comment);
|
||||||
|
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
@SuppressWarnings("UseOfSystemOutOrSystemErr") final PrintStream oldOut = System.out;
|
||||||
|
try {
|
||||||
|
System.setOut(new PrintStream(out, false, StandardCharsets.UTF_8));
|
||||||
|
method.invoke(null, new Object[]{input.toArray(String[]::new)});
|
||||||
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray()), StandardCharsets.UTF_8));
|
||||||
|
final List<String> result = new ArrayList<>();
|
||||||
|
while (true) {
|
||||||
|
final String line = reader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
result.add("");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.add(line.trim());
|
||||||
|
}
|
||||||
|
} catch (final InvocationTargetException e) {
|
||||||
|
final Throwable cause = e.getCause();
|
||||||
|
throw Asserts.error("main thrown exception %s: %s", cause.getClass().getSimpleName(), cause);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw Asserts.error("Cannot invoke main: %s: %s", e.getClass().getSimpleName(), e.getMessage());
|
||||||
|
} finally {
|
||||||
|
System.setOut(oldOut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findMain(final String className) {
|
||||||
|
try {
|
||||||
|
final URL url = new File(".").toURI().toURL();
|
||||||
|
final List<String> candidates = packages.stream()
|
||||||
|
.flatMap(pkg -> {
|
||||||
|
final String prefix = pkg.isEmpty() ? pkg : pkg + ".";
|
||||||
|
return Stream.of(prefix + className, prefix + "$" + className);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
//noinspection ClassLoaderInstantiation,resource,IOResourceOpenedButNotSafelyClosed
|
||||||
|
final URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
|
||||||
|
for (final String candidate : candidates) {
|
||||||
|
try {
|
||||||
|
final Class<?> loaded = classLoader.loadClass(candidate);
|
||||||
|
if (!Modifier.isPublic(loaded.getModifiers())) {
|
||||||
|
throw Asserts.error("Class %s is not public", candidate);
|
||||||
|
}
|
||||||
|
final Method main = loaded.getMethod("main", String[].class);
|
||||||
|
if (!Modifier.isPublic(main.getModifiers()) || !Modifier.isStatic(main.getModifiers())) {
|
||||||
|
throw Asserts.error("Method main of class %s should be public and static", candidate);
|
||||||
|
}
|
||||||
|
return main;
|
||||||
|
} catch (final ClassNotFoundException e) {
|
||||||
|
// Ignore
|
||||||
|
} catch (final NoSuchMethodException e) {
|
||||||
|
throw Asserts.error("Could not find method main(String[]) in class %s", candidate, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Asserts.error("Could not find neither of classes %s", candidates);
|
||||||
|
} catch (final MalformedURLException e) {
|
||||||
|
throw Asserts.error("Invalid path", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClassName(final String pkg, final String className) {
|
||||||
|
return pkg.isEmpty() ? className : pkg + "." + className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runner args(final String className) {
|
||||||
|
final CommentRunner main = main(className);
|
||||||
|
// final AtomicReference<String> prev = new AtomicReference<>("");
|
||||||
|
return (counter, input) -> {
|
||||||
|
final int total = input.stream().mapToInt(String::length).sum() + input.size() * 3;
|
||||||
|
final String comment = total <= 300
|
||||||
|
? input.stream().collect(Collectors.joining("\" \"", "\"", "\""))
|
||||||
|
: input.size() <= 100
|
||||||
|
? String.format("[%d arguments, sizes: %s]", input.size(), input.stream()
|
||||||
|
.mapToInt(String::length)
|
||||||
|
.mapToObj(String::valueOf)
|
||||||
|
.collect(Collectors.joining(", ")))
|
||||||
|
: String.format("[%d arguments, total size: %d]", input.size(), total);
|
||||||
|
// assert comment.length() <= 5 || !prev.get().equals(comment) : "Duplicate tests " + comment;
|
||||||
|
// prev.set(comment);
|
||||||
|
return main.run(comment, counter, input);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runner files(final String className) {
|
||||||
|
final Runner args = args(className);
|
||||||
|
return (counter, input) -> counter.call("io", () -> {
|
||||||
|
final Path inf = counter.getFile("in");
|
||||||
|
final Path ouf = counter.getFile("out");
|
||||||
|
Files.write(inf, input);
|
||||||
|
args.run(counter, List.of(inf.toString(), ouf.toString()));
|
||||||
|
final List<String> output = Files.readAllLines(ouf);
|
||||||
|
Files.delete(inf);
|
||||||
|
Files.delete(ouf);
|
||||||
|
return output;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
143
common/base/Selector.java
Normal file
143
common/base/Selector.java
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class Selector {
|
||||||
|
private final Class<?> owner;
|
||||||
|
private final List<String> modes;
|
||||||
|
private final Set<String> variantNames = new LinkedHashSet<>();
|
||||||
|
private final Map<String, Consumer<TestCounter>> variants = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public Selector(final Class<?> owner, final String... modes) {
|
||||||
|
this.owner = owner;
|
||||||
|
this.modes = List.of(modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Selector variant(final String name, final Consumer<TestCounter> operations) {
|
||||||
|
Asserts.assertTrue("Duplicate variant " + name, variants.put(name.toLowerCase(), operations) == null);
|
||||||
|
variantNames.add(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void check(final boolean condition, final String format, final Object... args) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new IllegalArgumentException(String.format(format, args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||||
|
public void main(final String... args) {
|
||||||
|
try {
|
||||||
|
final String mode;
|
||||||
|
if (modes.isEmpty()) {
|
||||||
|
check(args.length >= 1, "At least one argument expected, found %s", args.length);
|
||||||
|
mode = "";
|
||||||
|
} else {
|
||||||
|
check(args.length >= 2, "At least two arguments expected, found %s", args.length);
|
||||||
|
mode = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> vars = Arrays.stream(args).skip(modes.isEmpty() ? 0 : 1)
|
||||||
|
.flatMap(arg -> Arrays.stream(arg.split("[ +]+")))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
test(mode, vars);
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
|
if (modes.isEmpty()) {
|
||||||
|
System.err.println("Usage: " + owner.getName() + " VARIANT...");
|
||||||
|
} else {
|
||||||
|
System.err.println("Usage: " + owner.getName() + " MODE VARIANT...");
|
||||||
|
System.err.println("Modes: " + String.join(", ", modes));
|
||||||
|
}
|
||||||
|
System.err.println("Variants: " + String.join(", ", variantNames));
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final String mode, List<String> vars) {
|
||||||
|
final int modeNo = modes.isEmpty() ? -1 : modes.indexOf(mode) ;
|
||||||
|
check(modes.isEmpty() || modeNo >= 0, "Unknown mode '%s'", mode);
|
||||||
|
if (variantNames.contains("Base") && !vars.contains("Base")) {
|
||||||
|
vars = new ArrayList<>(vars);
|
||||||
|
vars.add(0, "Base");
|
||||||
|
}
|
||||||
|
|
||||||
|
vars.forEach(variant -> check(variants.containsKey(variant.toLowerCase()), "Unknown variant '%s'", variant));
|
||||||
|
|
||||||
|
final Map<String, String> properties = modes.isEmpty()
|
||||||
|
? Map.of("variant", String.join("+", vars))
|
||||||
|
: Map.of("variant", String.join("+", vars), "mode", mode);
|
||||||
|
final TestCounter counter = new TestCounter(owner, modeNo, properties);
|
||||||
|
counter.printHead();
|
||||||
|
vars.forEach(variant -> counter.scope("Testing " + variant, () -> variants.get(variant.toLowerCase()).accept(counter)));
|
||||||
|
counter.printStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V extends Tester> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final String... modes) {
|
||||||
|
return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||||
|
return new Composite<>(owner, factory, tester, modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getModes() {
|
||||||
|
return modes.isEmpty() ? List.of("~") : modes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getVariants() {
|
||||||
|
return List.copyOf(variants.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public static final class Composite<V> {
|
||||||
|
private final Selector selector;
|
||||||
|
private final Function<TestCounter, V> factory;
|
||||||
|
private final BiConsumer<V, TestCounter> tester;
|
||||||
|
private List<Consumer<? super V>> base;
|
||||||
|
|
||||||
|
private Composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||||
|
selector = new Selector(owner, modes);
|
||||||
|
this.factory = factory;
|
||||||
|
this.tester = tester;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final Composite<V> variant(final String name, final Consumer<? super V>... parts) {
|
||||||
|
if ("Base".equalsIgnoreCase(name)) {
|
||||||
|
base = List.of(parts);
|
||||||
|
return v(name.toLowerCase());
|
||||||
|
} else {
|
||||||
|
return v(name, parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
private Composite<V> v(final String name, final Consumer<? super V>... parts) {
|
||||||
|
selector.variant(name, counter -> {
|
||||||
|
final V variant = factory.apply(counter);
|
||||||
|
for (final Consumer<? super V> part : base) {
|
||||||
|
part.accept(variant);
|
||||||
|
}
|
||||||
|
for (final Consumer<? super V> part : parts) {
|
||||||
|
part.accept(variant);
|
||||||
|
}
|
||||||
|
tester.accept(variant, counter);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Selector selector() {
|
||||||
|
return selector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
common/base/TestCounter.java
Normal file
184
common/base/TestCounter.java
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class TestCounter extends Log {
|
||||||
|
public static final int DENOMINATOR = Integer.getInteger("base.denominator", 1);
|
||||||
|
public static final int DENOMINATOR2 = (int) Math.round(Math.sqrt(DENOMINATOR));
|
||||||
|
|
||||||
|
private static final String JAR_EXT = ".jar";
|
||||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
|
||||||
|
|
||||||
|
private final Class<?> owner;
|
||||||
|
private final int mode;
|
||||||
|
private final Map<String, ?> properties;
|
||||||
|
private final ExtendedRandom random;
|
||||||
|
|
||||||
|
private final long start = System.currentTimeMillis();
|
||||||
|
private int passed;
|
||||||
|
|
||||||
|
public TestCounter(final Class<?> owner, final int mode, final Map<String, ?> properties) {
|
||||||
|
Locale.setDefault(Locale.US);
|
||||||
|
Asserts.checkAssert(getClass());
|
||||||
|
|
||||||
|
this.owner = owner;
|
||||||
|
this.mode = mode;
|
||||||
|
this.properties = properties;
|
||||||
|
random = new ExtendedRandom(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int mode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTestNo() {
|
||||||
|
return passed + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test(final Runnable action) {
|
||||||
|
testV(() -> {
|
||||||
|
action.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void testForEach(final Iterable<? extends T> items, final Consumer<? super T> action) {
|
||||||
|
for (final T item : items) {
|
||||||
|
test(() -> action.accept(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T testV(final Supplier<T> action) {
|
||||||
|
return silentScope("Test " + getTestNo(), () -> {
|
||||||
|
final T result = action.get();
|
||||||
|
passed++;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLine() {
|
||||||
|
return getIndent() == 0 ? "=" : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printHead() {
|
||||||
|
println("=== " + getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printStatus() {
|
||||||
|
format("%s%n%s%n", getLine().repeat(30), getTitle());
|
||||||
|
format("%d tests passed in %dms%n", passed, System.currentTimeMillis() - start);
|
||||||
|
println("Version: " + getVersion(owner));
|
||||||
|
println("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTitle() {
|
||||||
|
return String.format("%s %s", owner.getSimpleName(), properties.isEmpty() ? "" : properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getVersion(final Class<?> clazz) {
|
||||||
|
try {
|
||||||
|
final ClassLoader cl = clazz.getClassLoader();
|
||||||
|
final URL url = cl.getResource(clazz.getName().replace('.', '/') + ".class");
|
||||||
|
if (url == null) {
|
||||||
|
return "(no manifest)";
|
||||||
|
}
|
||||||
|
|
||||||
|
final String path = url.getPath();
|
||||||
|
final int index = path.indexOf(JAR_EXT);
|
||||||
|
if (index == -1) {
|
||||||
|
return DATE_FORMAT.format(new Date(new File(path).lastModified()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String jarPath = path.substring(0, index + JAR_EXT.length());
|
||||||
|
try (final JarFile jarFile = new JarFile(new File(new URI(jarPath)))) {
|
||||||
|
final JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
|
||||||
|
return DATE_FORMAT.format(new Date(entry.getTime()));
|
||||||
|
}
|
||||||
|
} catch (final IOException | URISyntaxException e) {
|
||||||
|
return "error: " + e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T call(final String message, final SupplierE<T> supplier) {
|
||||||
|
return get(supplier).either(e -> fail(e, "%s", message), Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shouldFail(final String message, @SuppressWarnings("TypeMayBeWeakened") final RunnableE action) {
|
||||||
|
test(() -> get(action).either(e -> null, v -> fail("%s", message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fail(final String format, final Object... args) {
|
||||||
|
return fail(Asserts.error(format, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fail(final Throwable throwable) {
|
||||||
|
return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fail(final Throwable throwable, final String format, final Object... args) {
|
||||||
|
final String message = String.format(format, args);
|
||||||
|
println("ERROR: " + message);
|
||||||
|
throw throwable instanceof Error ? (Error) throwable : new AssertionError(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkTrue(final boolean condition, final String message, final Object... args) {
|
||||||
|
if (!condition) {
|
||||||
|
fail(message, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Either<Exception, T> get(final SupplierE<T> supplier) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getFile(final String suffix) {
|
||||||
|
return Paths.get(String.format("test%d.%s", getTestNo(), suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedRandom random() {
|
||||||
|
return random;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface SupplierE<T> extends Supplier<Either<Exception, T>> {
|
||||||
|
T getE() throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Either<Exception, T> get() {
|
||||||
|
try {
|
||||||
|
return Either.right(getE());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return Either.left(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface RunnableE extends SupplierE<Void> {
|
||||||
|
void run() throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Void getE() throws Exception {
|
||||||
|
run();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
common/base/Tester.java
Normal file
18
common/base/Tester.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public abstract class Tester extends BaseChecker {
|
||||||
|
protected Tester(final TestCounter counter) {
|
||||||
|
super(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void test();
|
||||||
|
|
||||||
|
public void run(final Class<?> test, final String... args) {
|
||||||
|
System.out.println("=== Testing " + test.getSimpleName() + " " + String.join(" ", args));
|
||||||
|
test();
|
||||||
|
counter.printStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
common/base/Unit.java
Normal file
15
common/base/Unit.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class Unit {
|
||||||
|
public static final Unit INSTANCE = new Unit();
|
||||||
|
|
||||||
|
private Unit() { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "unit";
|
||||||
|
}
|
||||||
|
}
|
||||||
7
common/base/package-info.java
Normal file
7
common/base/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Common homeworks test classes
|
||||||
|
* of <a href="https://www.kgeorgiy.info/courses/paradigms/">Paradigms of Programming</a> course.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
package base;
|
||||||
37
common/common/Engine.java
Normal file
37
common/common/Engine.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package common;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test engine.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface Engine<X> {
|
||||||
|
Result<X> prepare(String expression);
|
||||||
|
|
||||||
|
Result<Number> evaluate(final Result<X> prepared, double[] vars);
|
||||||
|
|
||||||
|
Result<String> toString(final Result<X> prepared);
|
||||||
|
|
||||||
|
default Result<X> parse(final String expression) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
record Result<T>(String context, T value) {
|
||||||
|
public void assertEquals(final T expected) {
|
||||||
|
Asserts.assertEquals(context(), expected, value());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <R> Result<R> cast(final BiFunction<T, String, R> convert) {
|
||||||
|
return new Result<>(context(), convert.apply(value(), context()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return context();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
common/common/EngineException.java
Normal file
12
common/common/EngineException.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown on test engine error.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class EngineException extends RuntimeException {
|
||||||
|
public EngineException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
common/common/expression/AnyOp.java
Normal file
13
common/common/expression/AnyOp.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public record AnyOp(ExprTester.Func f, int min, int max, int fixed) {
|
||||||
|
public Predicate<Collection<?>> arity() {
|
||||||
|
return args -> min <= args.size() && args.size() <= max;
|
||||||
|
}
|
||||||
|
}
|
||||||
281
common/common/expression/ArithmeticBuilder.java
Normal file
281
common/common/expression/ArithmeticBuilder.java
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.DoubleBinaryOperator;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic arithmetics.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class ArithmeticBuilder implements OperationsBuilder {
|
||||||
|
private final BaseVariant variant;
|
||||||
|
private final F neg;
|
||||||
|
private final F add;
|
||||||
|
private final F sub;
|
||||||
|
private final F mul;
|
||||||
|
private final F div;
|
||||||
|
|
||||||
|
public ArithmeticBuilder(final boolean varargs, final List<String> variables) {
|
||||||
|
variant = new BaseVariant(varargs);
|
||||||
|
variables.forEach(this::variable);
|
||||||
|
|
||||||
|
final Expr.Op negate = Expr.Op.op("negate");
|
||||||
|
variant.unary("negate", negate, a -> -a);
|
||||||
|
|
||||||
|
//noinspection Convert2MethodRef
|
||||||
|
add = f(variant.infix("+", 100, (a, b) -> a + b), 2);
|
||||||
|
sub = f(variant.infix("-", 100, (a, b) -> a - b), 2);
|
||||||
|
mul = f(variant.infix("*", 200, (a, b) -> a * b), 2);
|
||||||
|
div = f(variant.infix("/", 200, (a, b) -> a / b), 2);
|
||||||
|
neg = f(negate, 1);
|
||||||
|
|
||||||
|
basicTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void basicTests() {
|
||||||
|
final List<F> ops = List.of(neg, add, sub, mul, div);
|
||||||
|
variant.tests(() -> Stream.of(
|
||||||
|
Stream.of(variant.c()),
|
||||||
|
variant.getVariables().stream(),
|
||||||
|
ops.stream().map(F::c),
|
||||||
|
ops.stream().map(F::v),
|
||||||
|
ops.stream().map(F::r),
|
||||||
|
ops.stream().map(F::r),
|
||||||
|
Stream.of(
|
||||||
|
div.f(neg.r(), r()),
|
||||||
|
div.f(r(), mul.r()),
|
||||||
|
add.f(add.f(mul.r(), mul.r()), mul.r()),
|
||||||
|
sub.f(add.f(mul.r(), mul.f(r(), mul.f(r(), mul.r()))), mul.r())
|
||||||
|
)
|
||||||
|
).flatMap(Function.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void constant(final String name, final String alias, final double value) {
|
||||||
|
alias(name, alias);
|
||||||
|
final ExprTester.Func expr = vars -> value;
|
||||||
|
variant.nullary(name, expr);
|
||||||
|
final Expr constant = Expr.nullary(name, expr);
|
||||||
|
variant.tests(() -> Stream.of(
|
||||||
|
neg.f(constant),
|
||||||
|
add.f(constant, r()),
|
||||||
|
sub.f(r(), constant),
|
||||||
|
mul.f(r(), constant),
|
||||||
|
div.f(constant, r())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unary(final String id, final String alias, final DoubleUnaryOperator f) {
|
||||||
|
final Expr.Op op = Expr.Op.op(id);
|
||||||
|
variant.unary(id, op, f);
|
||||||
|
variant.alias(id, alias);
|
||||||
|
unaryTests(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void brackets(final String left, final String right, final String alias, final DoubleUnaryOperator f) {
|
||||||
|
final String id = left + right;
|
||||||
|
final Expr.Op op = new Expr.Op(id, "", left, right);
|
||||||
|
variant.unary(id, op, f);
|
||||||
|
variant.alias(id, alias);
|
||||||
|
unaryTests(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unaryTests(final Expr.Op op) {
|
||||||
|
final F f = f(op, 1);
|
||||||
|
variant.tests(() -> Stream.of(
|
||||||
|
f.c(),
|
||||||
|
f.v(),
|
||||||
|
f.f(sub.r()),
|
||||||
|
f.f(add.r()),
|
||||||
|
f.f(div.f(f.r(), add.r())),
|
||||||
|
add.f(f.f(f.f(add.r())), mul.f(r(), mul.f(r(), f.r())))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void binary(final String name, final String alias, final DoubleBinaryOperator f) {
|
||||||
|
final Expr.Op op = Expr.Op.op(name);
|
||||||
|
variant.binary(name, op, f);
|
||||||
|
variant.alias(name, alias);
|
||||||
|
binaryTests(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void binaryTests(final Expr.Op op) {
|
||||||
|
final F f = f(op, 2);
|
||||||
|
variant.tests(() -> Stream.of(
|
||||||
|
f.c(),
|
||||||
|
f.v(),
|
||||||
|
f.r(),
|
||||||
|
f.f(neg.r(), add.r()),
|
||||||
|
f.f(sub.r(), neg.r()),
|
||||||
|
f.f(neg.r(), f.r()),
|
||||||
|
f.f(f.r(), neg.r())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private record F(Expr.Op op, int arity, BaseVariant variant) {
|
||||||
|
public Expr f(final Expr... args) {
|
||||||
|
assert arity < 0 || arity == args.length;
|
||||||
|
return variant.f(op, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr v() {
|
||||||
|
return g(variant::v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr c() {
|
||||||
|
return g(variant::c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr r() {
|
||||||
|
return g(variant::r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr g(final Supplier<Expr> g) {
|
||||||
|
return f(Stream.generate(g).limit(arity).toArray(Expr[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private F f(final Expr.Op op, final int arity) {
|
||||||
|
return new F(op, arity, variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr r() {
|
||||||
|
return variant.r();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr f(final Expr.Op op, final Expr... args) {
|
||||||
|
return variant.f(op, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void infix(final String name, final String alias, final int priority, final DoubleBinaryOperator f) {
|
||||||
|
final Expr.Op op = variant.infix(name, priority, f);
|
||||||
|
variant.alias(name, alias);
|
||||||
|
binaryTests(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fixed(
|
||||||
|
final String name,
|
||||||
|
final String alias,
|
||||||
|
final int arity,
|
||||||
|
final ExprTester.Func f
|
||||||
|
) {
|
||||||
|
final Expr.Op op = Expr.Op.op(name);
|
||||||
|
variant.fixed(name, op, arity, f);
|
||||||
|
variant.alias(name, alias);
|
||||||
|
|
||||||
|
if (arity == 1) {
|
||||||
|
unaryTests(op);
|
||||||
|
} else if (arity == 2) {
|
||||||
|
binaryTests(op);
|
||||||
|
} else if (arity == 3) {
|
||||||
|
final F ff = f(op, 3);
|
||||||
|
variant.tests(() -> {
|
||||||
|
final Expr e1 = ff.c();
|
||||||
|
final Expr e2 = ff.v();
|
||||||
|
final Expr e3 = ff.f(add.r(), sub.r(), mul.r());
|
||||||
|
return Stream.of(
|
||||||
|
ff.f(variant.c(), r(), r()),
|
||||||
|
ff.f(r(), variant.c(), r()),
|
||||||
|
ff.f(r(), r(), variant.c()),
|
||||||
|
ff.f(variant.v(), mul.v(), mul.v()),
|
||||||
|
ff.f(mul.v(), variant.v(), mul.v()),
|
||||||
|
ff.f(mul.v(), r(), mul.v()),
|
||||||
|
ff.r(),
|
||||||
|
e1,
|
||||||
|
e2,
|
||||||
|
e3,
|
||||||
|
ff.f(e1, e2, e3)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (arity == 4) {
|
||||||
|
final F ff = f(op, 4);
|
||||||
|
variant.tests(() -> {
|
||||||
|
final Expr e1 = ff.c();
|
||||||
|
final Expr e2 = ff.v();
|
||||||
|
final Expr e3 = ff.r();
|
||||||
|
final Expr e4 = ff.f(add.r(), sub.r(), mul.r(), div.r());
|
||||||
|
return Stream.of(
|
||||||
|
ff.r(),
|
||||||
|
ff.r(),
|
||||||
|
ff.r(),
|
||||||
|
e1,
|
||||||
|
e2,
|
||||||
|
e3,
|
||||||
|
e4,
|
||||||
|
ff.f(e1, e2, e3, e4)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
variant.tests(() -> Stream.concat(
|
||||||
|
Stream.of(
|
||||||
|
f(op, arity, variant::c),
|
||||||
|
f(op, arity, variant::v)
|
||||||
|
),
|
||||||
|
IntStream.range(0, 10).mapToObj(i -> f(op, arity, variant::r))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr f(final Expr.Op op, final int arity, final Supplier<Expr> generator) {
|
||||||
|
return f(op, Stream.generate(generator).limit(arity).toArray(Expr[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void any(
|
||||||
|
final String name,
|
||||||
|
final String alias,
|
||||||
|
final AnyOp op
|
||||||
|
) {
|
||||||
|
final Expr.Op eop = Expr.Op.op(name);
|
||||||
|
variant.any(name, eop, op);
|
||||||
|
variant.alias(name, alias);
|
||||||
|
|
||||||
|
if (variant.hasVarargs()) {
|
||||||
|
variant.tests(() -> Stream.<List<Expr>>of(
|
||||||
|
List.of(),
|
||||||
|
List.of(r()),
|
||||||
|
List.of(r(), r()),
|
||||||
|
List.of(r(), r(), r()),
|
||||||
|
List.of(r(), r(), r(), r()),
|
||||||
|
List.of(r(), r(), r(), r(), r()),
|
||||||
|
List.of(add.r(), r()),
|
||||||
|
List.of(r(), r(), sub.r())
|
||||||
|
).filter(op.arity()).map(args -> args.toArray(Expr[]::new)).map(f(eop, -1)::f));
|
||||||
|
}
|
||||||
|
|
||||||
|
variant.tests(() -> IntStream.rangeClosed(op.min(), op.max())
|
||||||
|
.mapToObj(i -> f(eop, variant.hasVarargs() ? i : op.fixed(), variant::r)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void variable(final String name) {
|
||||||
|
variant.variable(name, variant.getVariables().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void alias(final String name, final String alias) {
|
||||||
|
variant.alias(name, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(final String... names) {
|
||||||
|
variant.remove(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseVariant variant() {
|
||||||
|
return variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
218
common/common/expression/BaseVariant.java
Normal file
218
common/common/expression/BaseVariant.java
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import common.expression.ExprTester.Func;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.DoubleBinaryOperator;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base expressions variant.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class BaseVariant implements Variant {
|
||||||
|
private static final int MAX_C = 1_000;
|
||||||
|
private static final Expr ZERO = c(0);
|
||||||
|
|
||||||
|
private final ExtendedRandom random = new ExtendedRandom(getClass());
|
||||||
|
private final boolean varargs;
|
||||||
|
|
||||||
|
private final StringMap<Operator> operators = new StringMap<>();
|
||||||
|
private final StringMap<Expr> nullary = new StringMap<>();
|
||||||
|
private final StringMap<Expr> variables = new StringMap<>();
|
||||||
|
private final Map<String, String> aliases = new HashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, Integer> priorities = new HashMap<>();
|
||||||
|
|
||||||
|
public final List<Supplier<Stream<Expr>>> tests = new ArrayList<>();
|
||||||
|
|
||||||
|
public BaseVariant(final boolean varargs) {
|
||||||
|
this.varargs = varargs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Expr> getTests() {
|
||||||
|
return tests.stream().flatMap(Supplier::get).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr randomTest(final int size) {
|
||||||
|
return generate(size / 10 + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr generate(final int depth) {
|
||||||
|
return depth > 0 ? generateOp(depth) : r();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr r() {
|
||||||
|
if (random.nextBoolean()) {
|
||||||
|
return variables.random(random);
|
||||||
|
} else if (nullary.isEmpty() || random.nextBoolean()){
|
||||||
|
return c();
|
||||||
|
} else {
|
||||||
|
return nullary.random(random);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr c() {
|
||||||
|
return random.nextBoolean() ? ZERO : c(random.nextInt(-MAX_C, MAX_C));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr v() {
|
||||||
|
return random().randomItem(variables.values().toArray(Expr[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Expr generateOp(final int depth) {
|
||||||
|
if (random.nextInt(6) == 0 || operators.isEmpty()) {
|
||||||
|
return generateP(depth);
|
||||||
|
} else {
|
||||||
|
final Operator operator = operators.random(random);
|
||||||
|
final Expr[] args = Stream.generate(() -> generateP(depth))
|
||||||
|
.limit(random.nextInt(operator.minArity, operator.maxArity))
|
||||||
|
.toArray(Expr[]::new);
|
||||||
|
return f(operator.op, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Expr generateP(final int depth) {
|
||||||
|
return generate(random.nextInt(depth));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tests(final Supplier<Stream<Expr>> tests) {
|
||||||
|
this.tests.add(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fixed(final String name, final Expr.Op op, final int arity, final Func f) {
|
||||||
|
op(name, op, arity, arity, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void op(final String id, final Expr.Op op, final int minArity, final int maxArity, final Func f) {
|
||||||
|
assert !operators.contains(id) : "Duplicate op %s".formatted(id);
|
||||||
|
operators.put(id, new Operator(op, minArity, maxArity, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(final String... names) {
|
||||||
|
//noinspection SlowAbstractSetRemoveAll
|
||||||
|
operators.values.keySet().removeAll(Arrays.asList(names));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void any(final String id, final Expr.Op eop, final AnyOp op) {
|
||||||
|
if (varargs) {
|
||||||
|
op(id, eop, op.min(), op.max(), op.f());
|
||||||
|
} else {
|
||||||
|
op(id, eop, op.fixed(), op.fixed(), op.f());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unary(final String id, final Expr.Op op, final DoubleUnaryOperator answer) {
|
||||||
|
fixed(id, op, 1, args -> answer.applyAsDouble(args[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void binary(final String name, final Expr.Op op, final DoubleBinaryOperator answer) {
|
||||||
|
fixed(name, op, 2, args -> answer.applyAsDouble(args[0], args[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr.Op infix(final String name, final int priority, final DoubleBinaryOperator answer) {
|
||||||
|
final Expr.Op op = Expr.Op.op(name);
|
||||||
|
binary(name, op, answer);
|
||||||
|
priorities.put(name, priority);
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nullary(final String name, final Func f) {
|
||||||
|
nullary.put(name, Expr.nullary(name, f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr f(final Expr.Op op, final Expr... args) {
|
||||||
|
final Func operator = operators.get(op.id());
|
||||||
|
Objects.requireNonNull(operator, "Unknown operation " + op.id());
|
||||||
|
return new Expr(
|
||||||
|
vars -> operator.applyAsDouble(Stream.of(args).mapToDouble(arg -> arg.evaluate(vars)).toArray()),
|
||||||
|
cata -> cata.operation(op, Stream.of(args).map(arg -> arg.cata(cata)).toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Expr n(final String name) {
|
||||||
|
return nullary.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Expr c(final int value) {
|
||||||
|
return Expr.constant(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr variable(final String name, final int index) {
|
||||||
|
final Expr variable = Expr.variable(name, index);
|
||||||
|
variables.put(name, variable);
|
||||||
|
return variable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Expr> getVariables() {
|
||||||
|
return List.copyOf(variables.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtendedRandom random() {
|
||||||
|
return random;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasVarargs() {
|
||||||
|
return varargs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getPriority(final String op) {
|
||||||
|
return priorities.get(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Operator(Expr.Op op, int minArity, int maxArity, Func f) implements Func {
|
||||||
|
private Operator {
|
||||||
|
assert 0 <= minArity && minArity <= maxArity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double applyAsDouble(final double[] args) {
|
||||||
|
return Arrays.stream(args).allMatch(Double::isFinite) ? f.applyAsDouble(args) : Double.NaN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StringMap<T> {
|
||||||
|
private final List<String> names = new ArrayList<>();
|
||||||
|
private final Map<String, T> values = new HashMap<>();
|
||||||
|
|
||||||
|
public T get(final String name) {
|
||||||
|
return values.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T random(final ExtendedRandom random) {
|
||||||
|
return get(random.randomItem(names));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmpty() {
|
||||||
|
return values.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void put(final String name, final T value) {
|
||||||
|
names.add(name);
|
||||||
|
values.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<T> values() {
|
||||||
|
return values.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(final String name) {
|
||||||
|
return values.containsKey(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void alias(final String name, final String alias) {
|
||||||
|
aliases.put(name, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolve(final String id, final String name) {
|
||||||
|
return aliases.getOrDefault(id, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
common/common/expression/Dialect.java
Normal file
57
common/common/expression/Dialect.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression dialect.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class Dialect {
|
||||||
|
private final Expr.Cata<String> cata;
|
||||||
|
|
||||||
|
private Dialect(final Expr.Cata<String> cata) {
|
||||||
|
this.cata = cata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialect(final String variable, final String constant, final BiFunction<Expr.Op, List<String>, String> nary) {
|
||||||
|
this(new Expr.Cata<>(variable::formatted, constant::formatted, name -> name, nary));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialect(final String variable, final String constant, final String operation, final String separator) {
|
||||||
|
this(variable, constant, operation(operation, separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BiFunction<Expr.Op, List<String>, String> operation(final String template, final String separator) {
|
||||||
|
return (op, args) -> template.replace("{op}", op.name()).replace("{args}", String.join(separator, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialect renamed(final BiFunction<String, String, String> renamer) {
|
||||||
|
return updated(cata -> cata.withOperation(nary -> (op, args) -> nary.apply(
|
||||||
|
op.rename(renamer.apply(op.id(), op.name())), args)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialect updated(final UnaryOperator<Expr.Cata<String>> updater) {
|
||||||
|
return new Dialect(updater.apply(cata));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String render(final Expr expr) {
|
||||||
|
return expr.cata(cata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String meta(final String name, final String... args) {
|
||||||
|
return cata.operation(Expr.Op.op(name), List.of(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dialect functional() {
|
||||||
|
return renamed(Dialect::toFunctional);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toFunctional(final String id, final String name) {
|
||||||
|
return name.chars().allMatch(Character::isUpperCase)
|
||||||
|
? name.toLowerCase()
|
||||||
|
: Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
157
common/common/expression/Diff.java
Normal file
157
common/common/expression/Diff.java
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.Named;
|
||||||
|
import common.Engine;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static common.expression.ExprTester.EPS;
|
||||||
|
import static common.expression.ExprTester.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression differentiator.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class Diff {
|
||||||
|
private static final double D = 1e-6;
|
||||||
|
|
||||||
|
private final int base;
|
||||||
|
private final Dialect dialect;
|
||||||
|
|
||||||
|
public Diff(final int base, final Dialect dialect) {
|
||||||
|
this.dialect = dialect;
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <X> void diff(final ExprTester<X> tester, final boolean reparse) {
|
||||||
|
tester.addStage(() -> {
|
||||||
|
for (final Test expr : tester.language.getTests()) {
|
||||||
|
checkDiff(tester, expr, reparse, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private <X> List<Engine.Result<String>> checkDiff(
|
||||||
|
final ExprTester<X> tester,
|
||||||
|
final Test test,
|
||||||
|
final boolean reparse,
|
||||||
|
final boolean simplify
|
||||||
|
) {
|
||||||
|
final List<Engine.Result<String>> results = new ArrayList<>(test.variables().size() + 1);
|
||||||
|
System.out.println(" Testing diff: " + test.parsed());
|
||||||
|
|
||||||
|
if (simplify) {
|
||||||
|
final Engine.Result<X> simplified = tester.engine.prepare(dialect.meta("simplify", test.parsed()));
|
||||||
|
test.points().forEachOrdered(point -> {
|
||||||
|
final double[] vars = Arrays.stream(point).map(v -> v + base).toArray();
|
||||||
|
tester.assertValue("simplified expression", simplified, vars, test.evaluate(vars));
|
||||||
|
});
|
||||||
|
results.add(tester.engine.toString(simplified));
|
||||||
|
}
|
||||||
|
|
||||||
|
final double[] indices = IntStream.range(0, test.variables().size()).mapToDouble(a -> a).toArray();
|
||||||
|
for (final Expr variable : test.variables()) {
|
||||||
|
final List<Named<Engine.Result<X>>> ways = new ArrayList<>();
|
||||||
|
final String diffS = dialect.meta("diff", test.parsed(), dialect.render(variable));
|
||||||
|
addWays("diff", tester, reparse, diffS, ways);
|
||||||
|
|
||||||
|
if (simplify) {
|
||||||
|
final String simplifyS = dialect.meta("simplify", diffS);
|
||||||
|
results.add(tester.engine.toString(addWays("simplified", tester, reparse, simplifyS, ways)));
|
||||||
|
}
|
||||||
|
|
||||||
|
final int index = (int) variable.evaluate(indices);
|
||||||
|
|
||||||
|
test.points().forEachOrdered(point -> {
|
||||||
|
final double[] vars = Arrays.stream(point).map(v -> v + base).toArray();
|
||||||
|
final double center = test.evaluate(vars);
|
||||||
|
if (ok(center)) {
|
||||||
|
final double lft = evaluate(test, vars, index, -D);
|
||||||
|
final double rt = evaluate(test, vars, index, D);
|
||||||
|
final double left = (center - lft) / D;
|
||||||
|
final double right = (rt - center) / D;
|
||||||
|
if (ok(lft) && ok(rt) && ok(left) && ok(right) && Math.abs(left - right) < EPS) {
|
||||||
|
for (final Named<Engine.Result<X>> way : ways) {
|
||||||
|
tester.assertValue(
|
||||||
|
"diff by %s, %s".formatted(dialect.render(variable), way.name()),
|
||||||
|
way.value(), vars, (left + right) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <X> Engine.Result<X> addWays(
|
||||||
|
final String name,
|
||||||
|
final ExprTester<X> tester,
|
||||||
|
final boolean reparse,
|
||||||
|
final String exprS,
|
||||||
|
final List<Named<Engine.Result<X>>> ways
|
||||||
|
) {
|
||||||
|
final Engine.Result<X> exprR = tester.engine.prepare(exprS);
|
||||||
|
ways.add(Named.of(name, exprR));
|
||||||
|
if (reparse) {
|
||||||
|
ways.add(Named.of("reparsed " + name, tester.parse(tester.engine.toString(exprR).value())));
|
||||||
|
}
|
||||||
|
return exprR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean ok(final double value) {
|
||||||
|
final double abs = Math.abs(value);
|
||||||
|
return EPS < abs && abs < 1 / EPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double evaluate(final Test test, final double[] vars, final int index, final double d) {
|
||||||
|
vars[index] += d;
|
||||||
|
final double result = test.evaluate(vars);
|
||||||
|
vars[index] -= d;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <X> void simplify(final ExprTester<X> tester) {
|
||||||
|
final List<int[]> simplifications = tester.language.getSimplifications();
|
||||||
|
if (simplifications == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tester.addStage(() -> {
|
||||||
|
final List<int[]> newSimplifications = new ArrayList<>();
|
||||||
|
final List<Test> tests = tester.language.getTests();
|
||||||
|
|
||||||
|
for (int i = 0; i < simplifications.size(); i++) {
|
||||||
|
final Test expr = tests.get(i);
|
||||||
|
final int[] expected = simplifications.get(i);
|
||||||
|
final List<Engine.Result<String>> actual = checkDiff(tester, expr, true, true);
|
||||||
|
if (expected != null) {
|
||||||
|
for (int j = 0; j < expected.length; j++) {
|
||||||
|
final Engine.Result<String> result = actual.get(j);
|
||||||
|
final int length = result.value().length();
|
||||||
|
Asserts.assertTrue(
|
||||||
|
"Simplified length too long: %d instead of %d%s"
|
||||||
|
.formatted(length, expected[j], result.context()),
|
||||||
|
length <= expected[j]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newSimplifications.add(actual.stream().mapToInt(result -> result.value().length()).toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!newSimplifications.isEmpty()) {
|
||||||
|
System.err.println(newSimplifications.stream()
|
||||||
|
.map(row -> Arrays.stream(row)
|
||||||
|
.mapToObj(Integer::toString)
|
||||||
|
.collect(Collectors.joining(", ", "{", "}")))
|
||||||
|
.collect(Collectors.joining(", ", "new int[][]{", "}")));
|
||||||
|
System.err.println(simplifications.size() + " " + newSimplifications.size());
|
||||||
|
throw new AssertionError("Uncovered");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
130
common/common/expression/Expr.java
Normal file
130
common/common/expression/Expr.java
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression instance.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
* @param coCata There are no forall generics in Java, so using Object as placeholder.
|
||||||
|
*/
|
||||||
|
public record Expr(ExprTester.Func answer, Function<Cata<Object>, Object> coCata) {
|
||||||
|
public <T> T cata(final Cata<T> cata) {
|
||||||
|
@SuppressWarnings("unchecked") final Function<Cata<T>, T> coCata = (Function<Cata<T>, T>) (Function<?, ?>) this.coCata;
|
||||||
|
return coCata.apply(cata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double evaluate(final double... vars) {
|
||||||
|
return answer.applyAsDouble(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Expr constant(final int value) {
|
||||||
|
return new Expr(vars -> value, cata -> cata.constant(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Expr variable(final String name, final int index) {
|
||||||
|
return new Expr(vars -> vars[index], cata -> cata.variable(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Expr nullary(final String name, final ExprTester.Func answer) {
|
||||||
|
return new Expr(answer, cata -> cata.nullary(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression catamorphism.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public record Cata<T>(
|
||||||
|
Function<String, T> variable,
|
||||||
|
IntFunction<T> constant,
|
||||||
|
Function<String, T> nullary,
|
||||||
|
BiFunction<Op, List<T>, T> operation
|
||||||
|
) {
|
||||||
|
public T variable(final String name) {
|
||||||
|
return variable.apply(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T constant(final int value) {
|
||||||
|
return constant.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T nullary(final String name) {
|
||||||
|
return nullary.apply(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T operation(final Op op, final List<T> args) {
|
||||||
|
return operation.apply(op, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cata<T> withOperation(final UnaryOperator<BiFunction<Op, List<T>, T>> updater) {
|
||||||
|
return new Cata<>(variable, constant, nullary, updater.apply(operation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Op {
|
||||||
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
private final String before;
|
||||||
|
private final String after;
|
||||||
|
private final Map<String, Op> renames = new HashMap<>();
|
||||||
|
|
||||||
|
public Op(final String id, final String name, final String before, final String after) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.before = before;
|
||||||
|
this.after = after;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Op op(final String name) {
|
||||||
|
return new Op(name, name, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Op rename(final String newName) {
|
||||||
|
return renames.computeIfAbsent(newName, n -> new Op(id, n, before, after));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String enclose(final String inner) {
|
||||||
|
return before + inner + after;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (obj == this) return true;
|
||||||
|
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||||
|
final var that = (Op) obj;
|
||||||
|
return Objects.equals(this.name, that.name) &&
|
||||||
|
Objects.equals(this.before, that.before) &&
|
||||||
|
Objects.equals(this.after, that.after);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id, name, before, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Op[id=%s, name=%s, before=%s, after=%s]".formatted(id, name, before, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean enclosing() {
|
||||||
|
return !before.isEmpty() && !after.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
221
common/common/expression/ExprTester.java
Normal file
221
common/common/expression/ExprTester.java
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.TestCounter;
|
||||||
|
import base.Tester;
|
||||||
|
import common.Engine;
|
||||||
|
import common.EngineException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.ToDoubleFunction;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expressions tester.
|
||||||
|
*
|
||||||
|
* @author Niyaz Nigmatullin
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class ExprTester<E> extends Tester {
|
||||||
|
public static final int N = 128;
|
||||||
|
public static final double EPS = 1e-3;
|
||||||
|
public static final int RANDOM_TESTS = 444;
|
||||||
|
|
||||||
|
private final int randomTests;
|
||||||
|
/*package*/ final Engine<E> engine;
|
||||||
|
/*package*/ final Language language;
|
||||||
|
private final List<Runnable> stages = new ArrayList<>();
|
||||||
|
private final boolean testToString;
|
||||||
|
|
||||||
|
private final Generator<String> spoiler;
|
||||||
|
private final Generator<BadInput> corruptor;
|
||||||
|
|
||||||
|
public static final Generator<String> STANDARD_SPOILER = (input, expr, random, builder) -> builder
|
||||||
|
.add(input)
|
||||||
|
.add(addSpaces(input, random));
|
||||||
|
|
||||||
|
public ExprTester(
|
||||||
|
final TestCounter counter,
|
||||||
|
final int randomTests,
|
||||||
|
final Engine<E> engine,
|
||||||
|
final Language language,
|
||||||
|
final boolean testToString,
|
||||||
|
final Generator<String> spoiler,
|
||||||
|
final Generator<BadInput> corruptor
|
||||||
|
) {
|
||||||
|
super(counter);
|
||||||
|
this.randomTests = randomTests;
|
||||||
|
this.engine = engine;
|
||||||
|
this.language = language;
|
||||||
|
this.testToString = testToString;
|
||||||
|
this.spoiler = spoiler;
|
||||||
|
this.corruptor = corruptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Predicate<String> UNSAFE = Pattern.compile("[-\\p{Alnum}+*/.=&|^<>◀▶◁▷≤≥?⁰-⁹₀-₉:]").asPredicate();
|
||||||
|
|
||||||
|
private static boolean safe(final char ch) {
|
||||||
|
return !UNSAFE.test("" + ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String addSpaces(final String expression, final ExtendedRandom random) {
|
||||||
|
String spaced = expression;
|
||||||
|
for (int n = StrictMath.min(10, 200 / expression.length()); n > 0;) {
|
||||||
|
final int index = random.nextInt(spaced.length() + 1);
|
||||||
|
final char c = index == 0 ? 0 : spaced.charAt(index - 1);
|
||||||
|
final char nc = index == spaced.length() ? 0 : spaced.charAt(index);
|
||||||
|
if ((safe(c) || safe(nc)) && c != '\'' && nc != '\'' && c != '"' && nc != '"') {
|
||||||
|
spaced = spaced.substring(0, index) + " " + spaced.substring(index);
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spaced;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void test() {
|
||||||
|
for (final Test test : language.getTests()) {
|
||||||
|
try {
|
||||||
|
test(test, prepared -> counter.scope(
|
||||||
|
"Testing: " + prepared,
|
||||||
|
() -> test.points().forEachOrdered(vars -> assertValue(
|
||||||
|
"original expression",
|
||||||
|
prepared,
|
||||||
|
vars,
|
||||||
|
test.evaluate(vars)
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
} catch (final RuntimeException | AssertionError e) {
|
||||||
|
throw new AssertionError("Error while testing " + test.parsed() + ": " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counter.scope("Random tests", () -> testRandom(randomTests));
|
||||||
|
stages.forEach(Runnable::run);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int limit(final int variables) {
|
||||||
|
return (int) Math.floor(Math.pow(N, 1.0 / variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test(final Test test, final Consumer<Engine.Result<E>> check) {
|
||||||
|
final Consumer<Engine.Result<E>> fullCheck = parsed -> counter.test(() -> {
|
||||||
|
check.accept(parsed);
|
||||||
|
if (testToString) {
|
||||||
|
counter.test(() -> engine.toString(parsed).assertEquals(test.toStr()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fullCheck.accept(engine.prepare(test.parsed()));
|
||||||
|
spoiler.forEach(10, test, random(), input -> fullCheck.accept(parse(input)));
|
||||||
|
corruptor.forEach(3, test, random(), input -> input.assertError(this::parse));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Engine.Result<E> parse(final String expression) {
|
||||||
|
return engine.parse(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRandom(final int n) {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (i % 100 == 0) {
|
||||||
|
counter.format("Completed %3d out of %d%n", i, n);
|
||||||
|
}
|
||||||
|
final double[] vars = language.randomVars();
|
||||||
|
|
||||||
|
final Test test = language.randomTest(i);
|
||||||
|
final double answer = test.evaluate(vars);
|
||||||
|
|
||||||
|
test(test, prepared -> assertValue("random expression", prepared, vars, answer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertValue(final String context, final Engine.Result<E> prepared, final double[] vars, final double expected) {
|
||||||
|
counter.test(() -> {
|
||||||
|
final Engine.Result<Number> result = engine.evaluate(prepared, vars);
|
||||||
|
Asserts.assertEquals("%n\tFor %s%s".formatted(context, result.context()), expected, result.value().doubleValue(), EPS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int mode(final String[] args, final Class<?> type, final String... modes) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
System.err.println("ERROR: No arguments found");
|
||||||
|
} else if (args.length > 1) {
|
||||||
|
System.err.println("ERROR: Only one argument expected, " + args.length + " found");
|
||||||
|
} else if (!Arrays.asList(modes).contains(args[0])) {
|
||||||
|
System.err.println("ERROR: First argument should be one of: \"" + String.join("\", \"", modes) + "\", found: \"" + args[0] + "\"");
|
||||||
|
} else {
|
||||||
|
return Arrays.asList(modes).indexOf(args[0]);
|
||||||
|
}
|
||||||
|
System.err.println("Usage: java -ea " + type.getName() + " {" + String.join("|", modes) + "}");
|
||||||
|
System.exit(1);
|
||||||
|
throw new AssertionError("Return from System.exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addStage(final Runnable stage) {
|
||||||
|
stages.add(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Func extends ToDoubleFunction<double[]> {
|
||||||
|
@Override
|
||||||
|
double applyAsDouble(double... args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Test(Expr expr, String parsed, String unparsed, String toStr, List<Expr> variables) {
|
||||||
|
public double evaluate(final double... vars) {
|
||||||
|
return expr.evaluate(vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<double[]> points() {
|
||||||
|
final int n = limit(variables.size());
|
||||||
|
return IntStream.range(0, N).mapToObj(i -> IntStream.iterate(i, j -> j / n)
|
||||||
|
.map(j -> j % n)
|
||||||
|
.limit(variables.size())
|
||||||
|
.mapToDouble(j -> j)
|
||||||
|
.toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BadInput(String prefix, String comment, String suffix) {
|
||||||
|
public String assertError(final Function<String, Engine.Result<?>> parse) {
|
||||||
|
try {
|
||||||
|
final Engine.Result<?> parsed = parse.apply(prefix + suffix);
|
||||||
|
throw new AssertionError("Parsing error expected for '%s%s%s', got %s"
|
||||||
|
.formatted(prefix, comment, suffix, parsed.value()));
|
||||||
|
} catch (final EngineException e) {
|
||||||
|
return e.getCause().getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Generator<T> {
|
||||||
|
void generate(String input, Expr expr, ExtendedRandom random, Stream.Builder<? super T> builder);
|
||||||
|
|
||||||
|
static <T> Generator<T> empty() {
|
||||||
|
return (i, e, r, b) -> {};
|
||||||
|
}
|
||||||
|
|
||||||
|
default Generator<T> combine(final Generator<? extends T> that) {
|
||||||
|
return (i, e, r, b) -> {
|
||||||
|
this.generate(i, e, r, b);
|
||||||
|
that.generate(i, e, r, b);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default void forEach(final int limit, final Test test, final ExtendedRandom random, final Consumer<? super T> consumer) {
|
||||||
|
final Stream.Builder<T> builder = Stream.builder();
|
||||||
|
generate(test.unparsed(), test.expr(), random, builder);
|
||||||
|
builder.build()
|
||||||
|
.sorted(Comparator.comparingInt(Object::hashCode))
|
||||||
|
.limit(limit)
|
||||||
|
.forEach(consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
common/common/expression/Language.java
Normal file
64
common/common/expression/Language.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import common.expression.ExprTester.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression language.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class Language {
|
||||||
|
private final Dialect parsed;
|
||||||
|
private final Dialect unparsed;
|
||||||
|
private final Dialect toString;
|
||||||
|
private final BaseVariant variant;
|
||||||
|
private final List<Test> tests;
|
||||||
|
private final List<int[]> simplifications;
|
||||||
|
|
||||||
|
public Language(
|
||||||
|
final Dialect parsed,
|
||||||
|
final Dialect unparsed,
|
||||||
|
final Dialect toString,
|
||||||
|
final BaseVariant variant,
|
||||||
|
final List<int[]> simplifications
|
||||||
|
) {
|
||||||
|
this.parsed = parsed;
|
||||||
|
this.unparsed = unparsed;
|
||||||
|
this.toString = toString;
|
||||||
|
this.variant = variant;
|
||||||
|
|
||||||
|
tests = variant.getTests().stream().map(this::test).toList();
|
||||||
|
assert simplifications == null || simplifications.isEmpty() || simplifications.size() == tests.size();
|
||||||
|
this.simplifications = simplifications != null && simplifications.isEmpty()
|
||||||
|
? Collections.nCopies(tests.size(), null) : simplifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Test test(final Expr expr) {
|
||||||
|
return new Test(
|
||||||
|
expr,
|
||||||
|
parsed.render(expr),
|
||||||
|
unparsed.render(expr),
|
||||||
|
toString.render(expr),
|
||||||
|
variant.getVariables()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Test randomTest(final int size) {
|
||||||
|
return test(variant.randomTest(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[] randomVars() {
|
||||||
|
return variant.random().getRandom().doubles().limit(variant.getVariables().size()).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Test> getTests() {
|
||||||
|
return tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<int[]> getSimplifications() {
|
||||||
|
return simplifications;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
common/common/expression/LanguageBuilder.java
Normal file
79
common/common/expression/LanguageBuilder.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import base.Selector;
|
||||||
|
import base.TestCounter;
|
||||||
|
import base.Tester;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntPredicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression test builder.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class LanguageBuilder {
|
||||||
|
public final OperationsBuilder ops;
|
||||||
|
private List<int[]> simplifications;
|
||||||
|
private Function<Expr.Cata<String>, Expr.Cata<String>> toStr = Function.identity();
|
||||||
|
private ExprTester.Generator<ExprTester.BadInput> corruptor = ExprTester.Generator.empty();
|
||||||
|
|
||||||
|
public LanguageBuilder(final boolean testMulti, final List<String> variables) {
|
||||||
|
ops = new ArithmeticBuilder(testMulti, variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Selector.Composite<LanguageBuilder> selector(
|
||||||
|
final Class<?> owner,
|
||||||
|
final IntPredicate testMulti,
|
||||||
|
final List<String> variables,
|
||||||
|
final BiFunction<LanguageBuilder, TestCounter, Tester> tester,
|
||||||
|
final String... modes
|
||||||
|
) {
|
||||||
|
return Selector.composite(
|
||||||
|
owner,
|
||||||
|
counter -> new LanguageBuilder(testMulti.test(counter.mode()), variables),
|
||||||
|
(builder, counter) -> tester.apply(builder, counter).test(),
|
||||||
|
modes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Selector.Composite<LanguageBuilder> selector(
|
||||||
|
final Class<?> owner,
|
||||||
|
final IntPredicate testMulti,
|
||||||
|
final BiFunction<LanguageBuilder, TestCounter, Tester> tester,
|
||||||
|
final String... modes
|
||||||
|
) {
|
||||||
|
return selector(owner, testMulti, List.of("x", "y", "z"), tester, modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variant variant() {
|
||||||
|
return ops.variant();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Language language(final Dialect parsed, final Dialect unparsed) {
|
||||||
|
final BaseVariant variant = ops.variant();
|
||||||
|
return new Language(parsed.renamed(variant::resolve), unparsed.updated(toStr::apply), unparsed, variant, simplifications);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toStr(final Function<Expr.Cata<String>, Expr.Cata<String>> updater) {
|
||||||
|
toStr = updater.compose(toStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<Expr.Cata<String>, Expr.Cata<String>> getToStr() {
|
||||||
|
return toStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSimplifications(final List<int[]> simplifications) {
|
||||||
|
this.simplifications = simplifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCorruptor(final ExprTester.Generator<ExprTester.BadInput> corruptor) {
|
||||||
|
this.corruptor = this.corruptor.combine(corruptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExprTester.Generator<ExprTester.BadInput> getCorruptor() {
|
||||||
|
return corruptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
common/common/expression/Operation.java
Normal file
11
common/common/expression/Operation.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression operation.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface Operation extends Consumer<LanguageBuilder> {
|
||||||
|
}
|
||||||
311
common/common/expression/Operations.java
Normal file
311
common/common/expression/Operations.java
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import base.Functional;
|
||||||
|
import base.Pair;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.function.DoubleBinaryOperator;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.ToDoubleFunction;
|
||||||
|
import java.util.stream.DoubleStream;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known expression operations.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||||
|
public enum Operations {
|
||||||
|
;
|
||||||
|
|
||||||
|
public static final Operation ARITH = builder -> {
|
||||||
|
builder.ops.alias("negate", "Negate");
|
||||||
|
builder.ops.alias("+", "Add");
|
||||||
|
builder.ops.alias("-", "Subtract");
|
||||||
|
builder.ops.alias("*", "Multiply");
|
||||||
|
builder.ops.alias("/", "Divide");
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Operation NARY_ARITH = builder -> {
|
||||||
|
builder.ops.remove("negate", "+", "-", "*", "/");
|
||||||
|
|
||||||
|
builder.ops.unary("negate", "Negate", a -> -a);
|
||||||
|
|
||||||
|
builder.ops.any("+", "Add", arith(0, Double::sum, 0));
|
||||||
|
builder.ops.any("-", "Subtract", arith(0, (a, b) -> a - b, 1));
|
||||||
|
builder.ops.any("*", "Multiply", arith(1, (a, b) -> a * b, 0));
|
||||||
|
builder.ops.any("/", "Divide", arith(1, (a, b) -> a / b, 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// === Common
|
||||||
|
|
||||||
|
public static Operation constant(final String name, final double value) {
|
||||||
|
return constant(name, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation constant(final String name, final String alias, final double value) {
|
||||||
|
return builder -> builder.ops.constant(name, alias, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation unary(final String name, final String alias, final DoubleUnaryOperator op) {
|
||||||
|
return builder -> builder.ops.unary(name, alias, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation brackets(final String left, final String right, final String alias, final DoubleUnaryOperator op) {
|
||||||
|
return builder -> builder.ops.brackets(left, right, alias, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Operation binary(final String name, final String alias, final DoubleBinaryOperator op) {
|
||||||
|
return builder -> builder.ops.binary(name, alias, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnyOp arith(final double zero, final DoubleBinaryOperator f, final int minArity) {
|
||||||
|
final ExprTester.Func func = args -> args.length == 0 ? zero
|
||||||
|
: args.length == 1 ? f.applyAsDouble(zero, args[0])
|
||||||
|
: Arrays.stream(args).reduce(f).orElseThrow();
|
||||||
|
return new AnyOp(func, minArity, minArity + 5, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// === More common
|
||||||
|
|
||||||
|
public record Op(String name, String alias, int minArity, int maxArity, ExprTester.Func f) {
|
||||||
|
public Operation fix(final int arity) {
|
||||||
|
assert minArity <= arity && arity <= maxArity;
|
||||||
|
final String patched =
|
||||||
|
name.contains("₀") ? name.replace("₀", fix(arity, '₀')) :
|
||||||
|
name.contains("0") ? name.replace("0", fix(arity, '0')) :
|
||||||
|
name + fix(arity, name.charAt(0) > 0xff ? '₀' : '0');
|
||||||
|
return fixed(patched, alias + arity, arity, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fix(final int arity, final char c) {
|
||||||
|
return "" + (char) (c + arity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Operation any(final int fixedArity) {
|
||||||
|
return checker -> checker.ops.any(name, alias, new AnyOp(f, minArity, maxArity, fixedArity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Op op(final String name, final String alias, final int minArity, final ExprTester.Func f) {
|
||||||
|
return new Op(name, alias, minArity, minArity + 5, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Op op1(final String alias, final int minArity, final ExprTester.Func f) {
|
||||||
|
return new Op(Character.toLowerCase(alias.charAt(0)) + alias.substring(1), alias, minArity, minArity + 5, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Op opS(final String name, final String alias, final int minArity, final ToDoubleFunction<DoubleStream> f) {
|
||||||
|
return op(name, alias, minArity, args -> f.applyAsDouble(Arrays.stream(args)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Op opO(final String name, final String alias, final int minArity, final Function<DoubleStream, OptionalDouble> f) {
|
||||||
|
return opS(name, alias, minArity, f.andThen(OptionalDouble::orElseThrow)::apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation fixed(final String name, final String alias, final int arity, final ExprTester.Func f) {
|
||||||
|
return builder -> builder.ops.fixed(name, alias, arity, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
public static Operation range(final int min, final int max, final Op... ops) {
|
||||||
|
final List<Operation> operations = IntStream.rangeClosed(min, max)
|
||||||
|
.mapToObj(i -> Arrays.stream(ops).map(op -> op.fix(i)))
|
||||||
|
.flatMap(Function.identity())
|
||||||
|
.toList();
|
||||||
|
return builder -> operations.forEach(op -> op.accept(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
public static Operation any(final int fixed, final Op... ops) {
|
||||||
|
final List<Operation> operations = Arrays.stream(ops).map(op -> op.any(fixed)).toList();
|
||||||
|
return builder -> operations.forEach(op -> op.accept(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation infix(
|
||||||
|
final String name,
|
||||||
|
final String alias,
|
||||||
|
final int priority,
|
||||||
|
final DoubleBinaryOperator op
|
||||||
|
) {
|
||||||
|
return checker -> checker.ops.infix(name, alias, priority, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// === Variables
|
||||||
|
public static final Operation VARIABLES = builder ->
|
||||||
|
Stream.of("y", "z").forEach(builder.ops::variable);
|
||||||
|
|
||||||
|
|
||||||
|
// === OneTwo
|
||||||
|
public static final Operation ONE = constant("one", 1);
|
||||||
|
public static final Operation TWO = constant("two", 2);
|
||||||
|
public static final Operation THREE = constant("three", 3);
|
||||||
|
|
||||||
|
|
||||||
|
// === Clamp, wrap
|
||||||
|
public static final Operation CLAMP = fixed("clamp", "Clamp", 3, args ->
|
||||||
|
args[1] <= args[2] ? Math.min(Math.max(args[0], args[1]), args[2]) : Double.NaN);
|
||||||
|
public static final Operation WRAP = fixed("wrap", "Wrap", 3, args ->
|
||||||
|
args[1] < args[2]
|
||||||
|
? args[0] - Math.floor((args[0] - args[1]) / (args[2] - args[1])) * (args[2] - args[1])
|
||||||
|
: Double.NaN);
|
||||||
|
|
||||||
|
|
||||||
|
// === ArcTan
|
||||||
|
public static final Operation ATAN = unary("atan", "ArcTan", Math::atan);
|
||||||
|
public static final Operation ATAN2 = binary("atan2", "ArcTan2", Math::atan2);
|
||||||
|
|
||||||
|
|
||||||
|
// === ArgMin, ArgMax
|
||||||
|
|
||||||
|
private static Op arg(final String name, final String alias, final Function<DoubleStream, OptionalDouble> f) {
|
||||||
|
return opO(
|
||||||
|
name, "Arg" + alias, 1, args -> {
|
||||||
|
final double[] values = args.toArray();
|
||||||
|
return f.apply(Arrays.stream(values)).stream()
|
||||||
|
.flatMap(value -> IntStream.range(0, values.length)
|
||||||
|
.filter(i -> values[i] == value).asDoubleStream())
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Op ARG_MIN = arg("argMin", "Min", DoubleStream::min);
|
||||||
|
public static final Op ARG_MAX = arg("argMax", "Max", DoubleStream::max);
|
||||||
|
|
||||||
|
|
||||||
|
// === SoftClamp
|
||||||
|
public static final Operation SOFT_CLAMP = fixed("softClamp", "SoftClamp", 4, args ->
|
||||||
|
args[1] <= args[2] && args[3] > 0
|
||||||
|
? args[1] + (args[2] - args[1]) / (1 + Math.exp(args[3] * ((args[2] + args[1]) / 2 - args[0])))
|
||||||
|
: Double.NaN);
|
||||||
|
|
||||||
|
|
||||||
|
// === SinCos
|
||||||
|
public static final Operation SIN = unary("sin", "Sin", Math::sin);
|
||||||
|
public static final Operation COS = unary("cos", "Cos", Math::cos);
|
||||||
|
|
||||||
|
|
||||||
|
// === Pow, Log
|
||||||
|
public static final Operation POW = binary("pow", "Power", Math::pow);
|
||||||
|
public static final Operation LOG = binary("log", "Log", (a, b) -> Math.log(Math.abs(b)) / Math.log(Math.abs(a)));
|
||||||
|
|
||||||
|
|
||||||
|
// === Sum
|
||||||
|
public static final Op SUM = opS("sum", "Sum", 0, DoubleStream::sum);
|
||||||
|
|
||||||
|
|
||||||
|
// === Gauss
|
||||||
|
public static double gauss(final double a, final double b, final double c, final double x) {
|
||||||
|
final double q = (x - b) / c;
|
||||||
|
return a * Math.exp(-q * q / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Operation GAUSS = fixed("gauss", "Gauss", 4, args -> gauss(args[0], args[1], args[2], args[3]));
|
||||||
|
|
||||||
|
|
||||||
|
// === Avg
|
||||||
|
public static final Op AVG = opO("avg", "Avg", 1, DoubleStream::average);
|
||||||
|
|
||||||
|
|
||||||
|
// === SoftWrap
|
||||||
|
public static final Operation SOFT_WRAP = fixed("softWrap", "SoftWrap", 4, args -> {
|
||||||
|
if (args[1] >= args[2] || args[3] < 0) {
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
final double x = args[0];
|
||||||
|
final double min = args[1];
|
||||||
|
final double max = args[2];
|
||||||
|
final double l = args[3];
|
||||||
|
final double a = Math.PI * (x - min) / (max - min);
|
||||||
|
return Math.asin(Math.cos(a) * Math.tanh(l * -Math.sin(a))) * (max - min) / Math.PI + (max + min) / 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// === SumExp
|
||||||
|
public static double sumexp(final double[] args) {
|
||||||
|
return Arrays.stream(args).map(Math::exp).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Op SUM_EXP = op1("SumExp", 0, Operations::sumexp);
|
||||||
|
public static final Op LSE = op1("Lse", 1, args -> Math.log(sumexp(args)));
|
||||||
|
|
||||||
|
|
||||||
|
// === LME
|
||||||
|
public static final Op LME = op1("Lme", 1, args -> Math.log(sumexp(args) / args.length));
|
||||||
|
|
||||||
|
|
||||||
|
// === MeanExp
|
||||||
|
public static double meanexp(final double[] args) {
|
||||||
|
return Arrays.stream(args).map(Math::exp).sum() / args.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Op MEAN_EXP = op1("MeanExp", 0, Operations::meanexp);
|
||||||
|
|
||||||
|
|
||||||
|
// === ArcTan12
|
||||||
|
public static final Op ATAN_12 = new Op(
|
||||||
|
"atan12",
|
||||||
|
"ArcTan12",
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
args -> args.length == 1 ? Math.atan(args[0]) : Math.atan2(args[0], args[1])
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// === Parentheses
|
||||||
|
public static Operation parentheses(final String... vars) {
|
||||||
|
final List<Pair<String, String>> variants = Functional.toPairs(vars);
|
||||||
|
return language -> {
|
||||||
|
if (variants.size() > 1) {
|
||||||
|
language.addCorruptor((input, expr, random, builder) -> {
|
||||||
|
for (final Pair<String, String> from : variants) {
|
||||||
|
final int index = input.lastIndexOf(from.first());
|
||||||
|
if (index >= 0) {
|
||||||
|
Pair<String, String> to = from;
|
||||||
|
while (Objects.equals(to.second(), from.second())) {
|
||||||
|
to = random.randomItem(variants);
|
||||||
|
}
|
||||||
|
final String head = input.substring(0, index);
|
||||||
|
final String tail = input.substring(index + from.first().length());
|
||||||
|
builder.add(new ExprTester.BadInput(head, "<UNMATCHED PARENTHESIS->", to.first() + tail));
|
||||||
|
builder.add(new ExprTester.BadInput(head, "<REMOVED PARENTHESES>", tail));
|
||||||
|
}
|
||||||
|
final int insIndex = random.nextInt(input.length());
|
||||||
|
builder.add(new ExprTester.BadInput(
|
||||||
|
input.substring(0, insIndex),
|
||||||
|
"<INSERTED PARENTHESIS-->",
|
||||||
|
(random.nextBoolean() ? from.first() : from.second()) + input.substring(insIndex)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
language.toStr(cata -> cata.withOperation(operation -> (op, args) -> {
|
||||||
|
final String inner = operation.apply(op, args);
|
||||||
|
final int openIndex = inner.indexOf("(");
|
||||||
|
final int closeIndex = inner.lastIndexOf(")");
|
||||||
|
if (openIndex < 0 || closeIndex < 0) {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
final String prefix = inner.substring(0, openIndex);
|
||||||
|
final String middle = inner.substring(openIndex + 1, closeIndex);
|
||||||
|
final String suffix = inner.substring(closeIndex + 1);
|
||||||
|
if (variants.stream().anyMatch(v -> prefix.contains(v.first()) || suffix.contains(v.second()))) {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
final Pair<String, String> variant = variants.get(Math.abs(inner.hashCode() % variants.size()));
|
||||||
|
return prefix + variant.first() + middle + variant.second() + suffix;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
33
common/common/expression/OperationsBuilder.java
Normal file
33
common/common/expression/OperationsBuilder.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import java.util.function.DoubleBinaryOperator;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operations builder.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface OperationsBuilder {
|
||||||
|
void constant(String name, String alias, double value);
|
||||||
|
|
||||||
|
void variable(String name);
|
||||||
|
|
||||||
|
void unary(String name, String alias, DoubleUnaryOperator op);
|
||||||
|
|
||||||
|
void brackets(String left, String right, String alias, DoubleUnaryOperator op);
|
||||||
|
|
||||||
|
void binary(String name, String alias, DoubleBinaryOperator op);
|
||||||
|
|
||||||
|
void infix(String name, String alias, int priority, DoubleBinaryOperator op);
|
||||||
|
|
||||||
|
void fixed(String name, String alias, int arity, ExprTester.Func f);
|
||||||
|
|
||||||
|
void any(String name, String alias, AnyOp op);
|
||||||
|
|
||||||
|
void alias(String name, String alias);
|
||||||
|
|
||||||
|
void remove(String... names);
|
||||||
|
|
||||||
|
BaseVariant variant();
|
||||||
|
}
|
||||||
16
common/common/expression/Variant.java
Normal file
16
common/common/expression/Variant.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package common.expression;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression variant meta-info.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface Variant {
|
||||||
|
ExtendedRandom random();
|
||||||
|
|
||||||
|
boolean hasVarargs();
|
||||||
|
|
||||||
|
Integer getPriority(String op);
|
||||||
|
}
|
||||||
39
java/expression/Abs.java
Normal file
39
java/expression/Abs.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Abs extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Abs(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int absInt(int n) {
|
||||||
|
if (n == Integer.MIN_VALUE) throw new OverflowException("abs");
|
||||||
|
return n < 0 ? -n : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return absInt(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return absInt(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return absInt(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).abs(); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).abs(); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "‖" + operand + "‖"; }
|
||||||
|
@Override public String toMiniString() { return "‖" + operand.toMiniString() + "‖"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Abs)) return false;
|
||||||
|
return operand.equals(((Abs) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x41425321; }
|
||||||
|
}
|
||||||
114
java/expression/AbstractBinaryOperation.java
Normal file
114
java/expression/AbstractBinaryOperation.java
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public abstract class AbstractBinaryOperation extends AbstractExpression {
|
||||||
|
|
||||||
|
protected final AbstractExpression left;
|
||||||
|
protected final AbstractExpression right;
|
||||||
|
|
||||||
|
protected AbstractBinaryOperation(
|
||||||
|
AbstractExpression left,
|
||||||
|
AbstractExpression right
|
||||||
|
) {
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String getOperator();
|
||||||
|
|
||||||
|
protected abstract int getPriority();
|
||||||
|
|
||||||
|
protected abstract boolean isRightAssoc();
|
||||||
|
|
||||||
|
protected abstract int applyInt(int a, int b);
|
||||||
|
|
||||||
|
protected abstract BigInteger applyBi(BigInteger a, BigInteger b);
|
||||||
|
|
||||||
|
protected abstract BigDecimal applyBd(BigDecimal a, BigDecimal b);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
return applyInt(left.evaluate(x), right.evaluate(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
return applyInt(left.evaluate(x, y, z), right.evaluate(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return applyInt(left.evaluate(vars), right.evaluate(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
return applyBi(left.evaluateBi(vars), right.evaluateBi(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
return applyBd(left.evaluateBd(vars), right.evaluateBd(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + left + " " + getOperator() + " " + right + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
return miniLeft() + " " + getOperator() + " " + miniRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String miniLeft() {
|
||||||
|
if (
|
||||||
|
left instanceof AbstractBinaryOperation op &&
|
||||||
|
op.getPriority() < getPriority()
|
||||||
|
) {
|
||||||
|
return "(" + op.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return left.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String miniRight() {
|
||||||
|
if (right instanceof AbstractBinaryOperation op) {
|
||||||
|
boolean samePriority = op.getPriority() == getPriority();
|
||||||
|
boolean lowerPriority = op.getPriority() < getPriority();
|
||||||
|
boolean needParens =
|
||||||
|
lowerPriority ||
|
||||||
|
(samePriority && isRightAssoc()) ||
|
||||||
|
(samePriority && op.isRightAssoc() && getPriority() > 1) ||
|
||||||
|
(samePriority &&
|
||||||
|
!getOperator().equals(op.getOperator()) &&
|
||||||
|
!op.isRightAssoc() &&
|
||||||
|
!isRightAssoc());
|
||||||
|
if (needParens) {
|
||||||
|
return "(" + op.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return right.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null || getClass() != obj.getClass()) return false;
|
||||||
|
AbstractBinaryOperation other = (AbstractBinaryOperation) obj;
|
||||||
|
return left.equals(other.left) && right.equals(other.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (
|
||||||
|
31 * (31 * left.hashCode() + right.hashCode()) +
|
||||||
|
getClass().hashCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
java/expression/AbstractExpression.java
Normal file
42
java/expression/AbstractExpression.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public abstract class AbstractExpression
|
||||||
|
implements
|
||||||
|
Expression,
|
||||||
|
TripleExpression,
|
||||||
|
ListExpression,
|
||||||
|
BigIntegerListExpression,
|
||||||
|
BigDecimalListExpression
|
||||||
|
{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract int evaluate(int x);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract int evaluate(int x, int y, int z);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract int evaluate(List<Integer> vars);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract BigInteger evaluateBi(List<BigInteger> vars);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract BigDecimal evaluateBd(List<BigDecimal> vars);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String toString();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract boolean equals(Object obj);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract int hashCode();
|
||||||
|
}
|
||||||
44
java/expression/Add.java
Normal file
44
java/expression/Add.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Add extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Add(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.add(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.add(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
363
java/expression/BigDecimalListExpression.java
Normal file
363
java/expression/BigDecimalListExpression.java
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.Pair;
|
||||||
|
import base.TestCounter;
|
||||||
|
import expression.common.ExpressionKind;
|
||||||
|
import expression.common.Type;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-argument arithmetic expression over {@link BigDecimal}s.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
@SuppressWarnings("ClassReferencesSubclass")
|
||||||
|
public interface BigDecimalListExpression extends ToMiniString {
|
||||||
|
BigDecimal evaluateBd(List<BigDecimal> variables);
|
||||||
|
|
||||||
|
// Tests follow. You may temporarily remove everything til the end.
|
||||||
|
|
||||||
|
Add EXAMPLE = new Add(
|
||||||
|
new Subtract(new Variable(0), new Const(BigDecimal.ONE)),
|
||||||
|
new Multiply(new Variable(1), new Const(BigDecimal.TEN))
|
||||||
|
);
|
||||||
|
|
||||||
|
Type<BigDecimal> TYPE = new Type<>(
|
||||||
|
v -> new BigDecimal(v + ".000"),
|
||||||
|
random -> BigDecimal.valueOf(random.getRandom().nextGaussian()),
|
||||||
|
BigDecimal.class
|
||||||
|
);
|
||||||
|
ExpressionKind<BigDecimalListExpression, BigDecimal> KIND =
|
||||||
|
new ExpressionKind<>(
|
||||||
|
TYPE,
|
||||||
|
BigDecimalListExpression.class,
|
||||||
|
(r, c) ->
|
||||||
|
IntStream.range(0, c)
|
||||||
|
.mapToObj(name ->
|
||||||
|
Pair.<String, BigDecimalListExpression>of(
|
||||||
|
"$" + name,
|
||||||
|
new Variable(name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
(expr, variables, values) -> expr.evaluateBd(values)
|
||||||
|
);
|
||||||
|
|
||||||
|
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||||
|
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||||
|
Asserts.assertEquals(
|
||||||
|
"Example toString()",
|
||||||
|
"(($0 - 1) + ($1 * 10))",
|
||||||
|
EXAMPLE.toString()
|
||||||
|
);
|
||||||
|
Asserts.assertEquals(
|
||||||
|
EXAMPLE + " at (2, 3)",
|
||||||
|
BigDecimal.valueOf(31),
|
||||||
|
EXAMPLE.evaluateBd(
|
||||||
|
List.of(BigDecimal.valueOf(2), BigDecimal.valueOf(3))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final Variable vx = new Variable(0);
|
||||||
|
final Variable vy = new Variable(1);
|
||||||
|
|
||||||
|
return new ExpressionTester<>(
|
||||||
|
counter,
|
||||||
|
KIND,
|
||||||
|
c -> v -> c,
|
||||||
|
(op, a, b) -> v -> op.apply(a.evaluateBd(v), b.evaluateBd(v)),
|
||||||
|
BigDecimal::add,
|
||||||
|
BigDecimal::subtract,
|
||||||
|
BigDecimal::multiply,
|
||||||
|
BigDecimal::divide
|
||||||
|
)
|
||||||
|
.basic("10", "10", v -> v(10), c(10))
|
||||||
|
.basic("$x", "$x", BigDecimalListExpression::x, vx)
|
||||||
|
.basic("$y", "$y", BigDecimalListExpression::y, vy)
|
||||||
|
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
|
||||||
|
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
|
||||||
|
.basic(
|
||||||
|
"(2 - $x)",
|
||||||
|
"2 - $x",
|
||||||
|
v -> v(2).subtract(x(v)),
|
||||||
|
new Subtract(c(2), vx)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(3 * $x)",
|
||||||
|
"3 * $x",
|
||||||
|
v -> v(3).multiply(x(v)),
|
||||||
|
new Multiply(c(3), vx)
|
||||||
|
)
|
||||||
|
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
|
||||||
|
.basic(
|
||||||
|
"($x / -2)",
|
||||||
|
"$x / -2",
|
||||||
|
v -> x(v).divide(v(-2)),
|
||||||
|
new Divide(vx, c(-2))
|
||||||
|
)
|
||||||
|
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
|
||||||
|
.basic(
|
||||||
|
"((1 + 2) + 3)",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Add(new Add(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 + (2 * 3))",
|
||||||
|
"1 + 2 * 3",
|
||||||
|
v -> v(7),
|
||||||
|
new Add(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 - (2 * 3))",
|
||||||
|
"1 - 2 * 3",
|
||||||
|
v -> v(-5),
|
||||||
|
new Subtract(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 + (2 + 3))",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Add(c(1), new Add(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 - 2) - 3)",
|
||||||
|
"1 - 2 - 3",
|
||||||
|
v -> v(-4),
|
||||||
|
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 - (2 - 3))",
|
||||||
|
"1 - (2 - 3)",
|
||||||
|
v -> v(2),
|
||||||
|
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 * 2) * 3)",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 * (2 * 3))",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((10 / 2) / 3)",
|
||||||
|
"10 / 2 / 3",
|
||||||
|
v -> v(10).divide(v(2)).divide(v(3)),
|
||||||
|
new Divide(new Divide(c(10), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 / (3 / 2))",
|
||||||
|
"10 / (3 / 2)",
|
||||||
|
v -> v(10).divide(v(3).divide(v(2))),
|
||||||
|
new Divide(c(10), new Divide(c(3), c(2)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(($x * $x) + (($x - 1) / 10))",
|
||||||
|
"$x * $x + ($x - 1) / 10",
|
||||||
|
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
|
||||||
|
new Add(
|
||||||
|
new Multiply(vx, vx),
|
||||||
|
new Divide(new Subtract(vx, c(1)), c(10))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"($x * -1000000000)",
|
||||||
|
"$x * -1000000000",
|
||||||
|
v -> x(v).multiply(v(-1_000_000_000)),
|
||||||
|
new Multiply(vx, c(-1_000_000_000))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"($x * -1000000000000000)",
|
||||||
|
"$x * -1000000000000000",
|
||||||
|
v -> x(v).multiply(v(-1_000_000_000_000_000L)),
|
||||||
|
new Multiply(vx, c(-1_000_000_000_000_000L))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 / $x)",
|
||||||
|
"10 / $x",
|
||||||
|
v -> v(10).divide(x(v)),
|
||||||
|
new Divide(c(10), vx)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"($x / $x)",
|
||||||
|
"$x / $x",
|
||||||
|
v -> x(v).divide(x(v)),
|
||||||
|
new Divide(vx, vx)
|
||||||
|
)
|
||||||
|
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
|
||||||
|
.advanced(
|
||||||
|
"($x - 1)",
|
||||||
|
"$x - 1",
|
||||||
|
v -> x(v).subtract(v(1)),
|
||||||
|
new Subtract(vx, c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 * 2)",
|
||||||
|
"1 * 2",
|
||||||
|
v -> v(1 * 2),
|
||||||
|
new Multiply(c(1), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x / 1)",
|
||||||
|
"$x / 1",
|
||||||
|
v -> x(v).divide(v(1)),
|
||||||
|
new Divide(vx, c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (2 + 1))",
|
||||||
|
"1 + 2 + 1",
|
||||||
|
v -> v(1 + 2 + 1),
|
||||||
|
new Add(c(1), new Add(c(2), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x - ($x - 1))",
|
||||||
|
"$x - ($x - 1)",
|
||||||
|
v -> x(v).subtract(x(v).subtract(v(1))),
|
||||||
|
new Subtract(vx, new Subtract(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 * ($x / 1))",
|
||||||
|
"2 * ($x / 1)",
|
||||||
|
v -> v(2).multiply(x(v).divide(v(1))),
|
||||||
|
new Multiply(c(2), new Divide(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 / ($x - 1))",
|
||||||
|
"2 / ($x - 1)",
|
||||||
|
v -> v(2).divide(x(v).subtract(v(1))),
|
||||||
|
new Divide(c(2), new Subtract(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 * 2) + $x)",
|
||||||
|
"1 * 2 + $x",
|
||||||
|
v -> v(1 * 2).add(x(v)),
|
||||||
|
new Add(new Multiply(c(1), c(2)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x - 1) - 2)",
|
||||||
|
"$x - 1 - 2",
|
||||||
|
v -> x(v).subtract(v(3)),
|
||||||
|
new Subtract(new Subtract(vx, c(1)), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x / 1) * 2)",
|
||||||
|
"$x / 1 * 2",
|
||||||
|
v -> x(v).multiply(v(2)),
|
||||||
|
new Multiply(new Divide(vx, c(1)), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 + 1) / 1)",
|
||||||
|
"(2 + 1) / 1",
|
||||||
|
v -> v(3),
|
||||||
|
new Divide(new Add(c(2), c(1)), c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (1 + (2 + 1)))",
|
||||||
|
"1 + 1 + 2 + 1",
|
||||||
|
v -> v(1 + 1 + 2 + 1),
|
||||||
|
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x - ((1 * 2) + $x))",
|
||||||
|
"$x - (1 * 2 + $x)",
|
||||||
|
v -> x(v).subtract(v(1 * 2).add(x(v))),
|
||||||
|
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x * (2 / ($x - 1)))",
|
||||||
|
"$x * (2 / ($x - 1))",
|
||||||
|
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
|
||||||
|
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x / (1 + (2 + 1)))",
|
||||||
|
"$x / (1 + 2 + 1)",
|
||||||
|
v -> x(v).divide(v(1 + 2 + 1)),
|
||||||
|
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 * 2) + (2 + 1))",
|
||||||
|
"1 * 2 + 2 + 1",
|
||||||
|
v -> v(1 * 2 + 2 + 1),
|
||||||
|
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 + 1) - (2 + 1))",
|
||||||
|
"2 + 1 - (2 + 1)",
|
||||||
|
v -> v(2 + 1 - (2 + 1)),
|
||||||
|
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x - 1) * ($x / 1))",
|
||||||
|
"($x - 1) * ($x / 1)",
|
||||||
|
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
|
||||||
|
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x - 1) / (1 * 2))",
|
||||||
|
"($x - 1) / (1 * 2)",
|
||||||
|
v -> x(v).subtract(v(1)).divide(v(2)),
|
||||||
|
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((($x - 1) - 2) + $x)",
|
||||||
|
"$x - 1 - 2 + $x",
|
||||||
|
v -> x(v).subtract(v(3)).add(x(v)),
|
||||||
|
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((1 * 2) + $x) - 1)",
|
||||||
|
"1 * 2 + $x - 1",
|
||||||
|
v -> v(1).add(x(v)),
|
||||||
|
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((2 + 1) / 1) * $x)",
|
||||||
|
"(2 + 1) / 1 * $x",
|
||||||
|
v -> v(3).multiply(x(v)),
|
||||||
|
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 / ($x - 1)) / 2)",
|
||||||
|
"2 / ($x - 1) / 2",
|
||||||
|
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
|
||||||
|
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal x(final List<BigDecimal> vars) {
|
||||||
|
return vars.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal y(final List<BigDecimal> vars) {
|
||||||
|
return vars.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Const c(final BigDecimal v) {
|
||||||
|
return TYPE.constant(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Const c(final long v) {
|
||||||
|
return TYPE.constant(v(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal v(final long v) {
|
||||||
|
return BigDecimal.valueOf(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void main(final String... args) {
|
||||||
|
TripleExpression.SELECTOR.variant(
|
||||||
|
"BigDecimalList",
|
||||||
|
ExpressionTest.v(BigDecimalListExpression::tester)
|
||||||
|
).main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
357
java/expression/BigIntegerListExpression.java
Normal file
357
java/expression/BigIntegerListExpression.java
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.Pair;
|
||||||
|
import base.TestCounter;
|
||||||
|
import expression.common.ExpressionKind;
|
||||||
|
import expression.common.Type;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
@SuppressWarnings("ClassReferencesSubclass")
|
||||||
|
public interface BigIntegerListExpression extends ToMiniString {
|
||||||
|
BigInteger evaluateBi(List<BigInteger> variables);
|
||||||
|
|
||||||
|
// Tests follow. You may temporarily remove everything til the end.
|
||||||
|
|
||||||
|
Add EXAMPLE = new Add(
|
||||||
|
new Subtract(new Variable(0), new Const(BigInteger.ONE)),
|
||||||
|
new Multiply(new Variable(1), new Const(BigInteger.TEN))
|
||||||
|
);
|
||||||
|
|
||||||
|
Type<BigInteger> TYPE = new Type<>(
|
||||||
|
BigInteger::valueOf,
|
||||||
|
random -> v(random.getRandom().nextLong()),
|
||||||
|
BigInteger.class
|
||||||
|
);
|
||||||
|
ExpressionKind<BigIntegerListExpression, BigInteger> KIND =
|
||||||
|
new ExpressionKind<>(
|
||||||
|
TYPE,
|
||||||
|
BigIntegerListExpression.class,
|
||||||
|
(r, c) ->
|
||||||
|
IntStream.range(0, c)
|
||||||
|
.mapToObj(name ->
|
||||||
|
Pair.<String, BigIntegerListExpression>of(
|
||||||
|
"$" + name,
|
||||||
|
new Variable(name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
(expr, variables, values) -> expr.evaluateBi(values)
|
||||||
|
);
|
||||||
|
|
||||||
|
@SuppressWarnings("PointlessArithmeticExpression")
|
||||||
|
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||||
|
Asserts.assertEquals(
|
||||||
|
"Example toString()",
|
||||||
|
"(($0 - 1) + ($1 * 10))",
|
||||||
|
EXAMPLE.toString()
|
||||||
|
);
|
||||||
|
Asserts.assertEquals(
|
||||||
|
EXAMPLE + " at (2, 3)",
|
||||||
|
BigInteger.valueOf(31),
|
||||||
|
EXAMPLE.evaluateBi(
|
||||||
|
List.of(BigInteger.valueOf(2), BigInteger.valueOf(3))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final Variable vx = new Variable(0);
|
||||||
|
final Variable vy = new Variable(1);
|
||||||
|
|
||||||
|
return new ExpressionTester<>(
|
||||||
|
counter,
|
||||||
|
KIND,
|
||||||
|
c -> v -> c,
|
||||||
|
(op, a, b) -> v -> op.apply(a.evaluateBi(v), b.evaluateBi(v)),
|
||||||
|
BigInteger::add,
|
||||||
|
BigInteger::subtract,
|
||||||
|
BigInteger::multiply,
|
||||||
|
BigInteger::divide
|
||||||
|
)
|
||||||
|
.basic("10", "10", v -> v(10), c(10))
|
||||||
|
.basic("$x", "$x", BigIntegerListExpression::x, vx)
|
||||||
|
.basic("$y", "$y", BigIntegerListExpression::y, vy)
|
||||||
|
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
|
||||||
|
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
|
||||||
|
.basic(
|
||||||
|
"(2 - $x)",
|
||||||
|
"2 - $x",
|
||||||
|
v -> v(2).subtract(x(v)),
|
||||||
|
new Subtract(c(2), vx)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(3 * $x)",
|
||||||
|
"3 * $x",
|
||||||
|
v -> v(3).multiply(x(v)),
|
||||||
|
new Multiply(c(3), vx)
|
||||||
|
)
|
||||||
|
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
|
||||||
|
.basic(
|
||||||
|
"($x / -2)",
|
||||||
|
"$x / -2",
|
||||||
|
v -> x(v).divide(v(-2)),
|
||||||
|
new Divide(vx, c(-2))
|
||||||
|
)
|
||||||
|
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
|
||||||
|
.basic(
|
||||||
|
"((1 + 2) + 3)",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Add(new Add(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 + (2 * 3))",
|
||||||
|
"1 + 2 * 3",
|
||||||
|
v -> v(7),
|
||||||
|
new Add(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 - (2 * 3))",
|
||||||
|
"1 - 2 * 3",
|
||||||
|
v -> v(-5),
|
||||||
|
new Subtract(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 + (2 + 3))",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Add(c(1), new Add(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 - 2) - 3)",
|
||||||
|
"1 - 2 - 3",
|
||||||
|
v -> v(-4),
|
||||||
|
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 - (2 - 3))",
|
||||||
|
"1 - (2 - 3)",
|
||||||
|
v -> v(2),
|
||||||
|
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 * 2) * 3)",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 * (2 * 3))",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
v -> v(6),
|
||||||
|
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((10 / 2) / 3)",
|
||||||
|
"10 / 2 / 3",
|
||||||
|
v -> v(10 / 2 / 3),
|
||||||
|
new Divide(new Divide(c(10), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 / (3 / 2))",
|
||||||
|
"10 / (3 / 2)",
|
||||||
|
v -> v(10 / (3 / 2)),
|
||||||
|
new Divide(c(10), new Divide(c(3), c(2)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(($x * $x) + (($x - 1) / 10))",
|
||||||
|
"$x * $x + ($x - 1) / 10",
|
||||||
|
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
|
||||||
|
new Add(
|
||||||
|
new Multiply(vx, vx),
|
||||||
|
new Divide(new Subtract(vx, c(1)), c(10))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"($x * -1000000000)",
|
||||||
|
"$x * -1000000000",
|
||||||
|
v -> x(v).multiply(v(-1_000_000_000)),
|
||||||
|
new Multiply(vx, c(-1_000_000_000))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"($x * -1000000000000000)",
|
||||||
|
"$x * -1000000000000000",
|
||||||
|
v -> x(v).multiply(v(-1_000_000_000_000_000L)),
|
||||||
|
new Multiply(vx, c(-1_000_000_000_000_000L))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 / $x)",
|
||||||
|
"10 / $x",
|
||||||
|
v -> v(10).divide(x(v)),
|
||||||
|
new Divide(c(10), vx)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"($x / $x)",
|
||||||
|
"$x / $x",
|
||||||
|
v -> x(v).divide(x(v)),
|
||||||
|
new Divide(vx, vx)
|
||||||
|
)
|
||||||
|
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
|
||||||
|
.advanced(
|
||||||
|
"($x - 1)",
|
||||||
|
"$x - 1",
|
||||||
|
v -> x(v).subtract(v(1)),
|
||||||
|
new Subtract(vx, c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 * 2)",
|
||||||
|
"1 * 2",
|
||||||
|
v -> v(1 * 2),
|
||||||
|
new Multiply(c(1), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x / 1)",
|
||||||
|
"$x / 1",
|
||||||
|
v -> x(v).divide(v(1)),
|
||||||
|
new Divide(vx, c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (2 + 1))",
|
||||||
|
"1 + 2 + 1",
|
||||||
|
v -> v(1 + 2 + 1),
|
||||||
|
new Add(c(1), new Add(c(2), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x - ($x - 1))",
|
||||||
|
"$x - ($x - 1)",
|
||||||
|
v -> x(v).subtract(x(v).subtract(v(1))),
|
||||||
|
new Subtract(vx, new Subtract(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 * ($x / 1))",
|
||||||
|
"2 * ($x / 1)",
|
||||||
|
v -> v(2).multiply(x(v).divide(v(1))),
|
||||||
|
new Multiply(c(2), new Divide(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 / ($x - 1))",
|
||||||
|
"2 / ($x - 1)",
|
||||||
|
v -> v(2).divide(x(v).subtract(v(1))),
|
||||||
|
new Divide(c(2), new Subtract(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 * 2) + $x)",
|
||||||
|
"1 * 2 + $x",
|
||||||
|
v -> v(1 * 2).add(x(v)),
|
||||||
|
new Add(new Multiply(c(1), c(2)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x - 1) - 2)",
|
||||||
|
"$x - 1 - 2",
|
||||||
|
v -> x(v).subtract(v(3)),
|
||||||
|
new Subtract(new Subtract(vx, c(1)), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x / 1) * 2)",
|
||||||
|
"$x / 1 * 2",
|
||||||
|
v -> x(v).multiply(v(2)),
|
||||||
|
new Multiply(new Divide(vx, c(1)), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 + 1) / 1)",
|
||||||
|
"(2 + 1) / 1",
|
||||||
|
v -> v(3),
|
||||||
|
new Divide(new Add(c(2), c(1)), c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (1 + (2 + 1)))",
|
||||||
|
"1 + 1 + 2 + 1",
|
||||||
|
v -> v(1 + 1 + 2 + 1),
|
||||||
|
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x - ((1 * 2) + $x))",
|
||||||
|
"$x - (1 * 2 + $x)",
|
||||||
|
v -> x(v).subtract(v(1 * 2).add(x(v))),
|
||||||
|
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x * (2 / ($x - 1)))",
|
||||||
|
"$x * (2 / ($x - 1))",
|
||||||
|
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
|
||||||
|
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"($x / (1 + (2 + 1)))",
|
||||||
|
"$x / (1 + 2 + 1)",
|
||||||
|
v -> x(v).divide(v(1 + 2 + 1)),
|
||||||
|
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 * 2) + (2 + 1))",
|
||||||
|
"1 * 2 + 2 + 1",
|
||||||
|
v -> v(1 * 2 + 2 + 1),
|
||||||
|
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 + 1) - (2 + 1))",
|
||||||
|
"2 + 1 - (2 + 1)",
|
||||||
|
v -> v(2 + 1 - (2 + 1)),
|
||||||
|
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x - 1) * ($x / 1))",
|
||||||
|
"($x - 1) * ($x / 1)",
|
||||||
|
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
|
||||||
|
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(($x - 1) / (1 * 2))",
|
||||||
|
"($x - 1) / (1 * 2)",
|
||||||
|
v -> x(v).subtract(v(1)).divide(v(2)),
|
||||||
|
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((($x - 1) - 2) + $x)",
|
||||||
|
"$x - 1 - 2 + $x",
|
||||||
|
v -> x(v).subtract(v(3)).add(x(v)),
|
||||||
|
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((1 * 2) + $x) - 1)",
|
||||||
|
"1 * 2 + $x - 1",
|
||||||
|
v -> v(1).add(x(v)),
|
||||||
|
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((2 + 1) / 1) * $x)",
|
||||||
|
"(2 + 1) / 1 * $x",
|
||||||
|
v -> v(3).multiply(x(v)),
|
||||||
|
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 / ($x - 1)) / 2)",
|
||||||
|
"2 / ($x - 1) / 2",
|
||||||
|
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
|
||||||
|
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigInteger x(final List<BigInteger> vars) {
|
||||||
|
return vars.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigInteger y(final List<BigInteger> vars) {
|
||||||
|
return vars.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Const c(final long v) {
|
||||||
|
return TYPE.constant(v(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigInteger v(final long v) {
|
||||||
|
return BigInteger.valueOf(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void main(final String... args) {
|
||||||
|
TripleExpression.SELECTOR.variant(
|
||||||
|
"BigIntegerList",
|
||||||
|
ExpressionTest.v(BigIntegerListExpression::tester)
|
||||||
|
).main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
java/expression/Cbrt.java
Normal file
62
java/expression/Cbrt.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Cbrt extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Cbrt(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int cbrtInt(int n) {
|
||||||
|
if (n == 0) return 0;
|
||||||
|
boolean negative = n < 0;
|
||||||
|
int abs = (n == Integer.MIN_VALUE) ? Integer.MAX_VALUE : (negative ? -n : n);
|
||||||
|
|
||||||
|
int lo = 0, hi = 1290;
|
||||||
|
while (lo < hi) {
|
||||||
|
int mid = (lo + hi + 1) / 2;
|
||||||
|
if (mid * mid * mid <= abs) {
|
||||||
|
lo = mid;
|
||||||
|
} else {
|
||||||
|
hi = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return negative ? -lo : lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return cbrtInt(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return cbrtInt(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return cbrtInt(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
throw new UnsupportedOperationException("∛ not supported for BigInteger");
|
||||||
|
}
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
throw new UnsupportedOperationException("∛ not supported for BigDecimal");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() { return "∛(" + operand + ")"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "∛(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "∛" + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Cbrt)) return false;
|
||||||
|
return operand.equals(((Cbrt) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x43425254; }
|
||||||
|
}
|
||||||
75
java/expression/Ceiling.java
Normal file
75
java/expression/Ceiling.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Ceiling extends AbstractExpression {
|
||||||
|
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Ceiling(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ceilInt(int n) {
|
||||||
|
return Math.ceilDiv(n, 1000) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
return ceilInt(operand.evaluate(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
return ceilInt(operand.evaluate(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return ceilInt(operand.evaluate(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"ceiling not supported for BigInteger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"ceiling not supported for BigDecimal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ceiling(" + operand + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "ceiling(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "ceiling " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Ceiling)) return false;
|
||||||
|
return operand.equals(((Ceiling) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return operand.hashCode() ^ 0x43454C47;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
java/expression/Clear.java
Normal file
46
java/expression/Clear.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Clear extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Clear(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "clear";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return a & ~(1 << b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.clearBit(b.intValueExact());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"clear not supported for BigDecimal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
java/expression/Const.java
Normal file
84
java/expression/Const.java
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Const extends AbstractExpression {
|
||||||
|
|
||||||
|
private final int intValue;
|
||||||
|
private final BigInteger biValue;
|
||||||
|
private final BigDecimal bdValue;
|
||||||
|
private final String repr;
|
||||||
|
|
||||||
|
public Const(int value) {
|
||||||
|
this.intValue = value;
|
||||||
|
this.biValue = BigInteger.valueOf(value);
|
||||||
|
this.bdValue = BigDecimal.valueOf(value);
|
||||||
|
this.repr = Integer.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Const(BigInteger value) {
|
||||||
|
this.intValue = value.intValueExact();
|
||||||
|
this.biValue = value;
|
||||||
|
this.bdValue = new BigDecimal(value);
|
||||||
|
this.repr = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Const(BigDecimal value) {
|
||||||
|
this.bdValue = value;
|
||||||
|
this.biValue = value.toBigIntegerExact();
|
||||||
|
this.intValue = biValue.intValueExact();
|
||||||
|
this.repr = value.toPlainString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
return biValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
return bdValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return repr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
return repr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Const)) return false;
|
||||||
|
return repr.equals(((Const) obj).repr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return repr.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
55
java/expression/Cube.java
Normal file
55
java/expression/Cube.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Cube extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Cube(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int checkedMul(int a, int b) {
|
||||||
|
if (a == 0 || b == 0) return 0;
|
||||||
|
if (a == Integer.MIN_VALUE || b == Integer.MIN_VALUE) throw new OverflowException("cube");
|
||||||
|
int r = a * b;
|
||||||
|
if (r / a != b) throw new OverflowException("cube");
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int cubeInt(int n) {
|
||||||
|
return checkedMul(checkedMul(n, n), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return cubeInt(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return cubeInt(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return cubeInt(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).pow(3); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).pow(3); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "(" + operand + ")³"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
String s = operand.toMiniString();
|
||||||
|
boolean needParens = operand instanceof AbstractBinaryOperation
|
||||||
|
|| operand instanceof Cbrt
|
||||||
|
|| operand instanceof Sqrt
|
||||||
|
|| (s.startsWith("-") && (s.length() < 2 || !Character.isDigit(s.charAt(1))));
|
||||||
|
return needParens ? "(" + s + ")³" : s + "³";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Cube)) return false;
|
||||||
|
return operand.equals(((Cube) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x43554245; }
|
||||||
|
}
|
||||||
83
java/expression/Digits.java
Normal file
83
java/expression/Digits.java
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Digits extends AbstractExpression {
|
||||||
|
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Digits(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int digitsSum(int n) {
|
||||||
|
if (n == 0) return 0;
|
||||||
|
boolean negative = n < 0;
|
||||||
|
long abs = Math.abs((long) n);
|
||||||
|
int sum = 0;
|
||||||
|
while (abs > 0) {
|
||||||
|
sum += (int) (abs % 10);
|
||||||
|
abs /= 10;
|
||||||
|
}
|
||||||
|
return negative ? -sum : sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
return digitsSum(operand.evaluate(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
return digitsSum(operand.evaluate(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return digitsSum(operand.evaluate(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"digits not supported for BigInteger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"digits not supported for BigDecimal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "digits(" + operand + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "digits(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "digits " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Digits)) return false;
|
||||||
|
return operand.equals(((Digits) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return operand.hashCode() ^ 0x44494754;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
java/expression/Divide.java
Normal file
45
java/expression/Divide.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.math.MathContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Divide extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Divide(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.divide(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.divide(b, MathContext.DECIMAL128);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
java/expression/DivisionByZeroException.java
Normal file
10
java/expression/DivisionByZeroException.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class DivisionByZeroException extends ArithmeticException {
|
||||||
|
public DivisionByZeroException() {
|
||||||
|
super("division by zero");
|
||||||
|
}
|
||||||
|
}
|
||||||
285
java/expression/Expression.java
Normal file
285
java/expression/Expression.java
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import base.Asserts;
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.Pair;
|
||||||
|
import base.TestCounter;
|
||||||
|
import expression.common.ExpressionKind;
|
||||||
|
import expression.common.Type;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-argument arithmetic expression over integers.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
@SuppressWarnings("ClassReferencesSubclass")
|
||||||
|
public interface Expression extends ToMiniString {
|
||||||
|
int evaluate(int x);
|
||||||
|
|
||||||
|
// Tests follow. You may temporarily remove everything til the end.
|
||||||
|
|
||||||
|
Subtract EXAMPLE = new Subtract(
|
||||||
|
new Multiply(new Const(2), new Variable("x")),
|
||||||
|
new Const(3)
|
||||||
|
);
|
||||||
|
|
||||||
|
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||||
|
ExpressionKind<Expression, Integer> KIND = new ExpressionKind<>(
|
||||||
|
TYPE,
|
||||||
|
Expression.class,
|
||||||
|
List.of(Pair.of("x", new Variable("x"))),
|
||||||
|
(expr, variables, values) -> expr.evaluate(values.get(0))
|
||||||
|
);
|
||||||
|
|
||||||
|
private static Const c(final int c) {
|
||||||
|
return new Const(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "PointlessArithmeticExpression", "Convert2MethodRef" })
|
||||||
|
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||||
|
Asserts.assertEquals(
|
||||||
|
"Example toString()",
|
||||||
|
"((2 * x) - 3)",
|
||||||
|
EXAMPLE.toString()
|
||||||
|
);
|
||||||
|
Asserts.assertEquals("Example at 5", 7, EXAMPLE.evaluate(5));
|
||||||
|
Asserts.assertTrue(
|
||||||
|
"Example equals 1",
|
||||||
|
new Multiply(new Const(2), new Variable("x")).equals(
|
||||||
|
new Multiply(new Const(2), new Variable("x"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Asserts.assertTrue(
|
||||||
|
"Example equals 2",
|
||||||
|
!new Multiply(new Const(2), new Variable("x")).equals(
|
||||||
|
new Multiply(new Variable("x"), new Const(2))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final Variable vx = new Variable("x");
|
||||||
|
final Const c1 = c(1);
|
||||||
|
final Const c2 = c(2);
|
||||||
|
|
||||||
|
return new ExpressionTester<>(
|
||||||
|
counter,
|
||||||
|
KIND,
|
||||||
|
c -> x -> c,
|
||||||
|
(op, a, b) -> x -> op.apply(a.evaluate(x), b.evaluate(x)),
|
||||||
|
(a, b) -> a + b,
|
||||||
|
(a, b) -> a - b,
|
||||||
|
(a, b) -> a * b,
|
||||||
|
(a, b) -> a / b
|
||||||
|
)
|
||||||
|
.basic("10", "10", x -> 10, c(10))
|
||||||
|
.basic("x", "x", x -> x, vx)
|
||||||
|
.basic("(x + 2)", "x + 2", x -> x + 2, new Add(vx, c(2)))
|
||||||
|
.basic("(2 - x)", "2 - x", x -> 2 - x, new Subtract(c(2), vx))
|
||||||
|
.basic("(3 * x)", "3 * x", x -> 3 * x, new Multiply(c(3), vx))
|
||||||
|
.basic("(x + x)", "x + x", x -> x + x, new Add(vx, vx))
|
||||||
|
.basic("(x / -2)", "x / -2", x -> -x / 2, new Divide(vx, c(-2)))
|
||||||
|
.basic("(2 + x)", "2 + x", x -> 2 + x, new Add(c(2), vx))
|
||||||
|
.basic(
|
||||||
|
"((1 + 2) + 3)",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
x -> 6,
|
||||||
|
new Add(new Add(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 + (2 + 3))",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
x -> 6,
|
||||||
|
new Add(c(1), new Add(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 - 2) - 3)",
|
||||||
|
"1 - 2 - 3",
|
||||||
|
x -> -4,
|
||||||
|
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 - (2 - 3))",
|
||||||
|
"1 - (2 - 3)",
|
||||||
|
x -> 2,
|
||||||
|
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 * 2) * 3)",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
x -> 6,
|
||||||
|
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 * (2 * 3))",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
x -> 6,
|
||||||
|
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((10 / 2) / 3)",
|
||||||
|
"10 / 2 / 3",
|
||||||
|
x -> 10 / 2 / 3,
|
||||||
|
new Divide(new Divide(c(10), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 / (3 / 2))",
|
||||||
|
"10 / (3 / 2)",
|
||||||
|
x -> 10 / (3 / 2),
|
||||||
|
new Divide(c(10), new Divide(c(3), c(2)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 * (3 / 2))",
|
||||||
|
"10 * (3 / 2)",
|
||||||
|
x -> 10 * (3 / 2),
|
||||||
|
new Multiply(c(10), new Divide(c(3), c(2)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 + (3 - 2))",
|
||||||
|
"10 + 3 - 2",
|
||||||
|
x -> 10 + (3 - 2),
|
||||||
|
new Add(c(10), new Subtract(c(3), c(2)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((x * x) + ((x - 1) / 10))",
|
||||||
|
"x * x + (x - 1) / 10",
|
||||||
|
x -> x * x + (x - 1) / 10,
|
||||||
|
new Add(
|
||||||
|
new Multiply(vx, vx),
|
||||||
|
new Divide(new Subtract(vx, c(1)), c(10))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(x * -1000000000)",
|
||||||
|
"x * -1000000000",
|
||||||
|
x -> x * -1_000_000_000,
|
||||||
|
new Multiply(vx, c(-1_000_000_000))
|
||||||
|
)
|
||||||
|
.basic("(10 / x)", "10 / x", x -> 10 / x, new Divide(c(10), vx))
|
||||||
|
.basic("(x / x)", "x / x", x -> x / x, new Divide(vx, vx))
|
||||||
|
.advanced("(2 + 1)", "2 + 1", x -> 2 + 1, new Add(c2, c1))
|
||||||
|
.advanced("(x - 1)", "x - 1", x -> x - 1, new Subtract(vx, c1))
|
||||||
|
.advanced("(1 * 2)", "1 * 2", x -> 1 * 2, new Multiply(c1, c2))
|
||||||
|
.advanced("(x / 1)", "x / 1", x -> x / 1, new Divide(vx, c1))
|
||||||
|
.advanced(
|
||||||
|
"(1 + (2 + 1))",
|
||||||
|
"1 + 2 + 1",
|
||||||
|
x -> 1 + 2 + 1,
|
||||||
|
new Add(c1, new Add(c2, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(x - (x - 1))",
|
||||||
|
"x - (x - 1)",
|
||||||
|
x -> x - (x - 1),
|
||||||
|
new Subtract(vx, new Subtract(vx, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 * (x / 1))",
|
||||||
|
"2 * (x / 1)",
|
||||||
|
x -> 2 * (x / 1),
|
||||||
|
new Multiply(c2, new Divide(vx, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 / (x - 1))",
|
||||||
|
"2 / (x - 1)",
|
||||||
|
x -> 2 / (x - 1),
|
||||||
|
new Divide(c2, new Subtract(vx, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 * 2) + x)",
|
||||||
|
"1 * 2 + x",
|
||||||
|
x -> 1 * 2 + x,
|
||||||
|
new Add(new Multiply(c1, c2), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((x - 1) - 2)",
|
||||||
|
"x - 1 - 2",
|
||||||
|
x -> x - 1 - 2,
|
||||||
|
new Subtract(new Subtract(vx, c1), c2)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((x / 1) * 2)",
|
||||||
|
"x / 1 * 2",
|
||||||
|
x -> (x / 1) * 2,
|
||||||
|
new Multiply(new Divide(vx, c1), c2)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 + 1) / 1)",
|
||||||
|
"(2 + 1) / 1",
|
||||||
|
x -> (2 + 1) / 1,
|
||||||
|
new Divide(new Add(c2, c1), c1)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (1 + (2 + 1)))",
|
||||||
|
"1 + 1 + 2 + 1",
|
||||||
|
x -> 1 + 1 + 2 + 1,
|
||||||
|
new Add(c1, new Add(c1, new Add(c2, c1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(x - ((1 * 2) + x))",
|
||||||
|
"x - (1 * 2 + x)",
|
||||||
|
x -> x - (1 * 2 + x),
|
||||||
|
new Subtract(vx, new Add(new Multiply(c1, c2), vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(x * (2 / (x - 1)))",
|
||||||
|
"x * (2 / (x - 1))",
|
||||||
|
x -> x * (2 / (x - 1)),
|
||||||
|
new Multiply(vx, new Divide(c2, new Subtract(vx, c1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(x / (1 + (2 + 1)))",
|
||||||
|
"x / (1 + 2 + 1)",
|
||||||
|
x -> x / (1 + 2 + 1),
|
||||||
|
new Divide(vx, new Add(c1, new Add(c2, c1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 * 2) + (2 + 1))",
|
||||||
|
"1 * 2 + 2 + 1",
|
||||||
|
x -> 1 * 2 + 2 + 1,
|
||||||
|
new Add(new Multiply(c1, c2), new Add(c2, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 + 1) - (2 + 1))",
|
||||||
|
"2 + 1 - (2 + 1)",
|
||||||
|
x -> 2 + 1 - (2 + 1),
|
||||||
|
new Subtract(new Add(c2, c1), new Add(c2, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((x - 1) * (x / 1))",
|
||||||
|
"(x - 1) * (x / 1)",
|
||||||
|
x -> (x - 1) * (x / 1),
|
||||||
|
new Multiply(new Subtract(vx, c1), new Divide(vx, c1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((x - 1) / (1 * 2))",
|
||||||
|
"(x - 1) / (1 * 2)",
|
||||||
|
x -> (x - 1) / (1 * 2),
|
||||||
|
new Divide(new Subtract(vx, c1), new Multiply(c1, c2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((x - 1) - 2) + x)",
|
||||||
|
"x - 1 - 2 + x",
|
||||||
|
x -> x - 1 - 2 + x,
|
||||||
|
new Add(new Subtract(new Subtract(vx, c1), c2), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((1 * 2) + x) - 1)",
|
||||||
|
"1 * 2 + x - 1",
|
||||||
|
x -> 1 * 2 + x - 1,
|
||||||
|
new Subtract(new Add(new Multiply(c1, c2), vx), c1)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((2 + 1) / 1) * x)",
|
||||||
|
"(2 + 1) / 1 * x",
|
||||||
|
x -> ((2 + 1) / 1) * x,
|
||||||
|
new Multiply(new Divide(new Add(c2, c1), c1), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 / (x - 1)) / 2)",
|
||||||
|
"2 / (x - 1) / 2",
|
||||||
|
x -> 2 / (x - 1) / 2,
|
||||||
|
new Divide(new Divide(c2, new Subtract(vx, c1)), c2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
java/expression/ExpressionTest.java
Normal file
30
java/expression/ExpressionTest.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import base.Selector;
|
||||||
|
import base.TestCounter;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public final class ExpressionTest {
|
||||||
|
|
||||||
|
public static final Selector SELECTOR = new Selector(
|
||||||
|
ExpressionTest.class,
|
||||||
|
"easy",
|
||||||
|
"hard"
|
||||||
|
).variant("Base", v(Expression::tester));
|
||||||
|
|
||||||
|
private ExpressionTest() {}
|
||||||
|
|
||||||
|
public static Consumer<TestCounter> v(
|
||||||
|
final Function<TestCounter, ? extends ExpressionTester<?, ?>> tester
|
||||||
|
) {
|
||||||
|
return t -> tester.apply(t).test();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String... args) {
|
||||||
|
SELECTOR.main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
415
java/expression/ExpressionTester.java
Normal file
415
java/expression/ExpressionTester.java
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import static base.Asserts.assertTrue;
|
||||||
|
|
||||||
|
import base.*;
|
||||||
|
import expression.common.*;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class ExpressionTester<E extends ToMiniString, C> extends Tester {
|
||||||
|
|
||||||
|
private final List<Integer> VALUES = IntStream.rangeClosed(-10, 10)
|
||||||
|
.boxed()
|
||||||
|
.toList();
|
||||||
|
private final ExpressionKind<E, C> kind;
|
||||||
|
|
||||||
|
private final List<Test> basic = new ArrayList<>();
|
||||||
|
private final List<Test> advanced = new ArrayList<>();
|
||||||
|
private final Set<String> used = new HashSet<>();
|
||||||
|
private final GeneratorBuilder generator;
|
||||||
|
|
||||||
|
private final List<Pair<ToMiniString, String>> prev = new ArrayList<>();
|
||||||
|
private final Map<String, C> mappings;
|
||||||
|
|
||||||
|
protected ExpressionTester(
|
||||||
|
final TestCounter counter,
|
||||||
|
final ExpressionKind<E, C> kind,
|
||||||
|
final Function<C, E> expectedConstant,
|
||||||
|
final Binary<C, E> binary,
|
||||||
|
final BinaryOperator<C> add,
|
||||||
|
final BinaryOperator<C> sub,
|
||||||
|
final BinaryOperator<C> mul,
|
||||||
|
final BinaryOperator<C> div,
|
||||||
|
final Map<String, C> mappings
|
||||||
|
) {
|
||||||
|
super(counter);
|
||||||
|
this.kind = kind;
|
||||||
|
this.mappings = mappings;
|
||||||
|
|
||||||
|
generator = new GeneratorBuilder(
|
||||||
|
expectedConstant,
|
||||||
|
kind::constant,
|
||||||
|
binary,
|
||||||
|
kind::randomValue
|
||||||
|
);
|
||||||
|
generator.binary("+", 1600, add, Add.class);
|
||||||
|
generator.binary("-", 1602, sub, Subtract.class);
|
||||||
|
generator.binary("*", 2001, mul, Multiply.class);
|
||||||
|
generator.binary("/", 2002, div, Divide.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ExpressionTester(
|
||||||
|
final TestCounter counter,
|
||||||
|
final ExpressionKind<E, C> kind,
|
||||||
|
final Function<C, E> expectedConstant,
|
||||||
|
final Binary<C, E> binary,
|
||||||
|
final BinaryOperator<C> add,
|
||||||
|
final BinaryOperator<C> sub,
|
||||||
|
final BinaryOperator<C> mul,
|
||||||
|
final BinaryOperator<C> div
|
||||||
|
) {
|
||||||
|
this(
|
||||||
|
counter,
|
||||||
|
kind,
|
||||||
|
expectedConstant,
|
||||||
|
binary,
|
||||||
|
add,
|
||||||
|
sub,
|
||||||
|
mul,
|
||||||
|
div,
|
||||||
|
Map.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return kind.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void test() {
|
||||||
|
counter.scope("Basic tests", () -> basic.forEach(Test::test));
|
||||||
|
counter.scope("Advanced tests", () -> advanced.forEach(Test::test));
|
||||||
|
counter.scope("Random tests", generator::testRandom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "ConstantValue", "EqualsWithItself" })
|
||||||
|
private void checkEqualsAndToString(
|
||||||
|
final String full,
|
||||||
|
final String mini,
|
||||||
|
final ToMiniString expression,
|
||||||
|
final ToMiniString copy
|
||||||
|
) {
|
||||||
|
checkToString("toString", full, expression.toString());
|
||||||
|
if (mode() > 0) {
|
||||||
|
checkToString("toMiniString", mini, expression.toMiniString());
|
||||||
|
}
|
||||||
|
|
||||||
|
counter.test(() -> {
|
||||||
|
assertTrue("Equals to this", expression.equals(expression));
|
||||||
|
assertTrue("Equals to copy", expression.equals(copy));
|
||||||
|
assertTrue("Equals to null", !expression.equals(null));
|
||||||
|
assertTrue("Copy equals to null", !copy.equals(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
final String expressionToString = Objects.requireNonNull(
|
||||||
|
expression.toString()
|
||||||
|
);
|
||||||
|
for (final Pair<ToMiniString, String> pair : prev) {
|
||||||
|
counter.test(() -> {
|
||||||
|
final ToMiniString prev = pair.first();
|
||||||
|
final String prevToString = pair.second();
|
||||||
|
final boolean equals = prevToString.equals(expressionToString);
|
||||||
|
assertTrue(
|
||||||
|
"Equals to " + prevToString,
|
||||||
|
prev.equals(expression) == equals
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
"Equals to " + prevToString,
|
||||||
|
expression.equals(prev) == equals
|
||||||
|
);
|
||||||
|
assertTrue(
|
||||||
|
"Inconsistent hashCode for " + prev + " and " + expression,
|
||||||
|
(prev.hashCode() == expression.hashCode()) == equals
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkToString(
|
||||||
|
final String method,
|
||||||
|
final String expected,
|
||||||
|
final String actual
|
||||||
|
) {
|
||||||
|
counter.test(() ->
|
||||||
|
assertTrue(
|
||||||
|
String.format(
|
||||||
|
"Invalid %s\n expected: %s\n actual: %s",
|
||||||
|
method,
|
||||||
|
expected,
|
||||||
|
actual
|
||||||
|
),
|
||||||
|
expected.equals(actual)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void check(
|
||||||
|
final String full,
|
||||||
|
final E expected,
|
||||||
|
final E actual,
|
||||||
|
final List<String> variables,
|
||||||
|
final List<C> values
|
||||||
|
) {
|
||||||
|
final String vars = IntStream.range(0, variables.size())
|
||||||
|
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
counter.test(() -> {
|
||||||
|
final Object expectedResult = evaluate(expected, variables, values);
|
||||||
|
final Object actualResult = evaluate(actual, variables, values);
|
||||||
|
final String reason = String.format(
|
||||||
|
"%s:%n expected `%s`,%n actual `%s`",
|
||||||
|
String.format("f(%s)\nwhere f is %s", vars, full),
|
||||||
|
Asserts.toString(expectedResult),
|
||||||
|
Asserts.toString(actualResult)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
expectedResult != null &&
|
||||||
|
actualResult != null &&
|
||||||
|
expectedResult.getClass() == actualResult.getClass() &&
|
||||||
|
(expectedResult.getClass() == Double.class ||
|
||||||
|
expectedResult.getClass() == Float.class)
|
||||||
|
) {
|
||||||
|
final double expectedValue = (
|
||||||
|
(Number) expectedResult
|
||||||
|
).doubleValue();
|
||||||
|
final double actualValue = (
|
||||||
|
(Number) actualResult
|
||||||
|
).doubleValue();
|
||||||
|
Asserts.assertEquals(reason, expectedValue, actualValue, 1e-6);
|
||||||
|
} else {
|
||||||
|
assertTrue(
|
||||||
|
reason,
|
||||||
|
Objects.deepEquals(expectedResult, actualResult)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object evaluate(
|
||||||
|
final E expression,
|
||||||
|
final List<String> variables,
|
||||||
|
final List<C> values
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return kind.evaluate(expression, variables, values);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return e.getClass().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ExpressionTester<E, C> basic(
|
||||||
|
final String full,
|
||||||
|
final String mini,
|
||||||
|
final E expected,
|
||||||
|
final E actual
|
||||||
|
) {
|
||||||
|
return basicF(full, mini, expected, vars -> actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ExpressionTester<E, C> basicF(
|
||||||
|
final String full,
|
||||||
|
final String mini,
|
||||||
|
final E expected,
|
||||||
|
final Function<List<String>, E> actual
|
||||||
|
) {
|
||||||
|
return basic(new Test(full, mini, expected, actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpressionTester<E, C> basic(final Test test) {
|
||||||
|
Asserts.assertTrue(test.full, used.add(test.full));
|
||||||
|
basic.add(test);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ExpressionTester<E, C> advanced(
|
||||||
|
final String full,
|
||||||
|
final String mini,
|
||||||
|
final E expected,
|
||||||
|
final E actual
|
||||||
|
) {
|
||||||
|
return advancedF(full, mini, expected, vars -> actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ExpressionTester<E, C> advancedF(
|
||||||
|
final String full,
|
||||||
|
final String mini,
|
||||||
|
final E expected,
|
||||||
|
final Function<List<String>, E> actual
|
||||||
|
) {
|
||||||
|
Asserts.assertTrue(full, used.add(full));
|
||||||
|
advanced.add(new Test(full, mini, expected, actual));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <E> Named<E> variable(
|
||||||
|
final String name,
|
||||||
|
final E expected
|
||||||
|
) {
|
||||||
|
return Named.of(name, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Binary<C, E> {
|
||||||
|
E apply(BinaryOperator<C> op, E a, E b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Test {
|
||||||
|
|
||||||
|
private final String full;
|
||||||
|
private final String mini;
|
||||||
|
private final E expected;
|
||||||
|
private final Function<List<String>, E> actual;
|
||||||
|
|
||||||
|
private Test(
|
||||||
|
final String full,
|
||||||
|
final String mini,
|
||||||
|
final E expected,
|
||||||
|
final Function<List<String>, E> actual
|
||||||
|
) {
|
||||||
|
this.full = full;
|
||||||
|
this.mini = mini;
|
||||||
|
this.expected = expected;
|
||||||
|
this.actual = actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void test() {
|
||||||
|
final List<Pair<String, E>> variables = kind
|
||||||
|
.variables()
|
||||||
|
.generate(random(), 3);
|
||||||
|
final List<String> names = Functional.map(variables, Pair::first);
|
||||||
|
final E actual = kind.cast(this.actual.apply(names));
|
||||||
|
final String full = mangle(this.full, names);
|
||||||
|
final String mini = mangle(this.mini, names);
|
||||||
|
|
||||||
|
counter.test(() -> {
|
||||||
|
kind
|
||||||
|
.allValues(variables.size(), VALUES)
|
||||||
|
.forEach(values ->
|
||||||
|
check(mini, expected, actual, names, values)
|
||||||
|
);
|
||||||
|
checkEqualsAndToString(full, mini, actual, actual);
|
||||||
|
prev.add(Pair.of(actual, full));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mangle(String string, final List<String> names) {
|
||||||
|
for (int i = 0; i < names.size(); i++) {
|
||||||
|
string = string.replace("$" + (char) ('x' + i), names.get(i));
|
||||||
|
}
|
||||||
|
for (final Map.Entry<String, C> mapping : mappings.entrySet()) {
|
||||||
|
string = string.replace(
|
||||||
|
mapping.getKey(),
|
||||||
|
mapping.getValue().toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class GeneratorBuilder {
|
||||||
|
|
||||||
|
private final Generator.Builder<C> generator;
|
||||||
|
private final NodeRendererBuilder<C> renderer =
|
||||||
|
new NodeRendererBuilder<>(random());
|
||||||
|
private final Renderer.Builder<C, Unit, E> expected;
|
||||||
|
private final Renderer.Builder<C, Unit, E> actual;
|
||||||
|
private final Renderer.Builder<C, Unit, E> copy;
|
||||||
|
private final Binary<C, E> binary;
|
||||||
|
|
||||||
|
private GeneratorBuilder(
|
||||||
|
final Function<C, E> expectedConstant,
|
||||||
|
final Function<? super C, E> actualConstant,
|
||||||
|
final Binary<C, E> binary,
|
||||||
|
final Function<ExtendedRandom, C> randomValue
|
||||||
|
) {
|
||||||
|
generator = Generator.builder(
|
||||||
|
() -> randomValue.apply(random()),
|
||||||
|
random()
|
||||||
|
);
|
||||||
|
expected = Renderer.builder(expectedConstant::apply);
|
||||||
|
actual = Renderer.builder(actualConstant::apply);
|
||||||
|
copy = Renderer.builder(actualConstant::apply);
|
||||||
|
|
||||||
|
this.binary = binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void binary(
|
||||||
|
final String name,
|
||||||
|
final int priority,
|
||||||
|
final BinaryOperator<C> op,
|
||||||
|
final Class<?> type
|
||||||
|
) {
|
||||||
|
generator.add(name, 2);
|
||||||
|
renderer.binary(name, priority);
|
||||||
|
|
||||||
|
expected.binary(name, (unit, a, b) -> binary.apply(op, a, b));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Constructor<? extends E> constructor = (Constructor<
|
||||||
|
? extends E
|
||||||
|
>) Arrays.stream(type.getConstructors())
|
||||||
|
.filter(cons -> Modifier.isPublic(cons.getModifiers()))
|
||||||
|
.filter(cons -> cons.getParameterCount() == 2)
|
||||||
|
.findFirst()
|
||||||
|
.orElseGet(() ->
|
||||||
|
counter.fail(
|
||||||
|
"%s(..., ...) constructor not found",
|
||||||
|
type.getSimpleName()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
final Renderer.BinaryOperator<Unit, E> actual = (unit, a, b) -> {
|
||||||
|
try {
|
||||||
|
return constructor.newInstance(a, b);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return counter.fail(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.actual.binary(name, actual);
|
||||||
|
copy.binary(name, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRandom() {
|
||||||
|
final NodeRenderer<C> renderer = this.renderer.build();
|
||||||
|
final Renderer<C, Unit, E> expectedRenderer = this.expected.build();
|
||||||
|
final Renderer<C, Unit, E> actualRenderer = this.actual.build();
|
||||||
|
final expression.common.Generator<C, E> generator =
|
||||||
|
this.generator.build(kind.variables(), List.of());
|
||||||
|
generator.testRandom(counter, 1, expr -> {
|
||||||
|
final String full = renderer.render(expr, NodeRenderer.FULL);
|
||||||
|
final String mini = renderer.render(expr, NodeRenderer.MINI);
|
||||||
|
final E expected = expectedRenderer.render(expr, Unit.INSTANCE);
|
||||||
|
final E actual = actualRenderer.render(expr, Unit.INSTANCE);
|
||||||
|
|
||||||
|
final List<Pair<String, E>> variables = expr.variables();
|
||||||
|
final List<String> names = Functional.map(
|
||||||
|
variables,
|
||||||
|
Pair::first
|
||||||
|
);
|
||||||
|
final List<C> values = Stream.generate(() ->
|
||||||
|
kind.randomValue(random())
|
||||||
|
)
|
||||||
|
.limit(variables.size())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
checkEqualsAndToString(
|
||||||
|
full,
|
||||||
|
mini,
|
||||||
|
actual,
|
||||||
|
copy.build().render(expr, Unit.INSTANCE)
|
||||||
|
);
|
||||||
|
check(full, expected, actual, names, values);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
java/expression/Floor.java
Normal file
75
java/expression/Floor.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Floor extends AbstractExpression {
|
||||||
|
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Floor(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int floorInt(int n) {
|
||||||
|
return Math.floorDiv(n, 1000) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
return floorInt(operand.evaluate(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
return floorInt(operand.evaluate(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return floorInt(operand.evaluate(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"floor not supported for BigInteger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"floor not supported for BigDecimal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "floor(" + operand + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "floor(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "floor " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Floor)) return false;
|
||||||
|
return operand.equals(((Floor) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return operand.hashCode() ^ 0x464C4F52;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
java/expression/High.java
Normal file
51
java/expression/High.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class High extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public High(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int highestBit(int n) {
|
||||||
|
if (n == 0) return 0;
|
||||||
|
n |= (n >> 1);
|
||||||
|
n |= (n >> 2);
|
||||||
|
n |= (n >> 4);
|
||||||
|
n |= (n >> 8);
|
||||||
|
n |= (n >> 16);
|
||||||
|
return n - (n >>> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return highestBit(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return highestBit(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return highestBit(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "high(" + operand + ")"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "high(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "high " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof High)) return false;
|
||||||
|
return operand.equals(((High) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x48494748; }
|
||||||
|
}
|
||||||
26
java/expression/ListExpression.java
Normal file
26
java/expression/ListExpression.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.Pair;
|
||||||
|
import expression.common.ExpressionKind;
|
||||||
|
import expression.common.Type;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ListExpression extends ToMiniString {
|
||||||
|
ExpressionKind<ListExpression, Integer> KIND = new ExpressionKind<>(
|
||||||
|
new Type<>(a -> a, ExtendedRandom::nextInt, int.class),
|
||||||
|
ListExpression.class,
|
||||||
|
(r, c) -> IntStream.range(0, c)
|
||||||
|
.mapToObj(name -> Pair.<String, ListExpression>of("$" + name, new Variable(name)))
|
||||||
|
.toList(),
|
||||||
|
(expr, variables, values) -> expr.evaluate(values)
|
||||||
|
);
|
||||||
|
|
||||||
|
int evaluate(List<Integer> variables);
|
||||||
|
}
|
||||||
46
java/expression/Log.java
Normal file
46
java/expression/Log.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Log extends AbstractBinaryOperation {
|
||||||
|
public Log(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||||
|
|
||||||
|
@Override protected String getOperator() { return "//"; }
|
||||||
|
@Override protected int getPriority() { return 3; }
|
||||||
|
@Override protected boolean isRightAssoc() { return true; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
if (a <= 0) throw new ArithmeticException("logarithm of non-positive number");
|
||||||
|
if (b <= 1) throw new ArithmeticException("logarithm base must be > 1");
|
||||||
|
int result = 0;
|
||||||
|
int power = 1;
|
||||||
|
while (power <= a / b) {
|
||||||
|
power *= b;
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
if (a.signum() <= 0) throw new ArithmeticException("logarithm of non-positive number");
|
||||||
|
if (b.compareTo(BigInteger.TWO) < 0) throw new ArithmeticException("logarithm base must be > 1");
|
||||||
|
int result = 0;
|
||||||
|
BigInteger power = BigInteger.ONE;
|
||||||
|
while (power.multiply(b).compareTo(a) <= 0) {
|
||||||
|
power = power.multiply(b);
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
return BigInteger.valueOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
throw new UnsupportedOperationException("log not supported for BigDecimal");
|
||||||
|
}
|
||||||
|
}
|
||||||
52
java/expression/Log2.java
Normal file
52
java/expression/Log2.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Log2 extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Log2(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int log2(int n) {
|
||||||
|
if (n <= 0) throw new ArithmeticException("log₂ of non-positive number: " + n);
|
||||||
|
int result = 0;
|
||||||
|
int v = n;
|
||||||
|
while (v > 1) {
|
||||||
|
v >>= 1;
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return log2(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return log2(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return log2(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "log₂(" + operand + ")"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "log₂(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "log₂ " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Log2)) return false;
|
||||||
|
return operand.equals(((Log2) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x4C4F4732; }
|
||||||
|
}
|
||||||
41
java/expression/Low.java
Normal file
41
java/expression/Low.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Low extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Low(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { int n = operand.evaluate(x); return n & -n; }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { int n = operand.evaluate(x,y,z); return n & -n; }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { int n = operand.evaluate(vars); return n & -n; }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "low(" + operand + ")"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "low(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "low " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Low)) return false;
|
||||||
|
return operand.equals(((Low) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x4C4F5700; }
|
||||||
|
}
|
||||||
44
java/expression/Max.java
Normal file
44
java/expression/Max.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Max extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Max(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "max";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return Math.max(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.max(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.max(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
java/expression/Min.java
Normal file
44
java/expression/Min.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Min extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Min(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "min";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return Math.min(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.min(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.min(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
java/expression/Multiply.java
Normal file
44
java/expression/Multiply.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Multiply extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Multiply(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.multiply(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.multiply(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
java/expression/OverflowException.java
Normal file
10
java/expression/OverflowException.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class OverflowException extends ArithmeticException {
|
||||||
|
public OverflowException(String operation) {
|
||||||
|
super("overflow in " + operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
java/expression/Pow2.java
Normal file
47
java/expression/Pow2.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Pow2 extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Pow2(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int pow2(int n) {
|
||||||
|
if (n < 0) throw new ArithmeticException("pow₂ of negative number: " + n);
|
||||||
|
if (n >= 31) throw new OverflowException("pow₂");
|
||||||
|
return 1 << n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return pow2(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return pow2(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return pow2(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { throw new UnsupportedOperationException(); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "pow₂(" + operand + ")"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "pow₂(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "pow₂ " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Pow2)) return false;
|
||||||
|
return operand.equals(((Pow2) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x504F5732; }
|
||||||
|
}
|
||||||
46
java/expression/Power.java
Normal file
46
java/expression/Power.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Power extends AbstractBinaryOperation {
|
||||||
|
public Power(AbstractExpression l, AbstractExpression r) { super(l, r); }
|
||||||
|
|
||||||
|
@Override protected String getOperator() { return "**"; }
|
||||||
|
@Override protected int getPriority() { return 3; }
|
||||||
|
@Override protected boolean isRightAssoc() { return true; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int base, int exp) {
|
||||||
|
if (exp < 0) throw new ArithmeticException("negative exponent");
|
||||||
|
if (base == 0 && exp == 0) throw new ArithmeticException("zero to the power of zero");
|
||||||
|
if (exp == 0) return 1;
|
||||||
|
if (base == 0) return 0;
|
||||||
|
int result = 1;
|
||||||
|
int b = base;
|
||||||
|
int e = exp;
|
||||||
|
while (e > 0) {
|
||||||
|
if ((e & 1) == 1) {
|
||||||
|
result = checkedMul(result, b);
|
||||||
|
}
|
||||||
|
if (e > 1) b = checkedMul(b, b);
|
||||||
|
e >>= 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int checkedMul(int a, int b) {
|
||||||
|
if (a == 0 || b == 0) return 0;
|
||||||
|
if (a == Integer.MIN_VALUE && b == -1) throw new OverflowException("power");
|
||||||
|
if (b == Integer.MIN_VALUE && a == -1) throw new OverflowException("power");
|
||||||
|
int result = a * b;
|
||||||
|
if (result / a != b) throw new OverflowException("power");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected BigInteger applyBi(BigInteger a, BigInteger b) { return a.pow(b.intValueExact()); }
|
||||||
|
@Override protected BigDecimal applyBd(BigDecimal a, BigDecimal b) { return a.pow(b.intValueExact()); }
|
||||||
|
}
|
||||||
84
java/expression/Reverse.java
Normal file
84
java/expression/Reverse.java
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Reverse extends AbstractExpression {
|
||||||
|
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Reverse(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int reverseInt(int n) {
|
||||||
|
boolean negative = n < 0;
|
||||||
|
long abs = Math.abs((long) n);
|
||||||
|
long reversed = 0;
|
||||||
|
while (abs > 0) {
|
||||||
|
reversed = reversed * 10 + (abs % 10);
|
||||||
|
abs /= 10;
|
||||||
|
}
|
||||||
|
long result = negative ? -reversed : reversed;
|
||||||
|
return (int) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
return reverseInt(operand.evaluate(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
return reverseInt(operand.evaluate(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return reverseInt(operand.evaluate(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"reverse not supported for BigInteger"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"reverse not supported for BigDecimal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "reverse(" + operand + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "reverse(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "reverse " + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Reverse)) return false;
|
||||||
|
return operand.equals(((Reverse) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return operand.hashCode() ^ 0x52455645;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
java/expression/SetBit.java
Normal file
46
java/expression/SetBit.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class SetBit extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public SetBit(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "set";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return a | (1 << b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.setBit(b.intValueExact());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"set not supported for BigDecimal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
java/expression/Sqrt.java
Normal file
60
java/expression/Sqrt.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Sqrt extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Sqrt(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int sqrtInt(int n) {
|
||||||
|
if (n < 0) throw new ArithmeticException("sqrt of negative number: " + n);
|
||||||
|
if (n == 0) return 0;
|
||||||
|
// Binary search: floor(sqrt(n)), result <= 46340 (sqrt(MAX_INT) < 46341)
|
||||||
|
int lo = 0, hi = 46340;
|
||||||
|
while (lo < hi) {
|
||||||
|
int mid = (lo + hi + 1) / 2;
|
||||||
|
if (mid * mid <= n) {
|
||||||
|
lo = mid;
|
||||||
|
} else {
|
||||||
|
hi = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return sqrtInt(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return sqrtInt(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return sqrtInt(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).sqrt(); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
throw new UnsupportedOperationException("sqrt not supported for BigDecimal");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() { return "√(" + operand + ")"; }
|
||||||
|
// @Override public String toMiniString() { return "√" + operand.toMiniString(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
if (operand instanceof AbstractBinaryOperation) {
|
||||||
|
return "√(" + operand.toMiniString() + ")";
|
||||||
|
}
|
||||||
|
return "√" + operand.toMiniString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Sqrt)) return false;
|
||||||
|
return operand.equals(((Sqrt) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x53515254; }
|
||||||
|
}
|
||||||
51
java/expression/Square.java
Normal file
51
java/expression/Square.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Square extends AbstractExpression {
|
||||||
|
private final AbstractExpression operand;
|
||||||
|
|
||||||
|
public Square(AbstractExpression operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int squareInt(int n) {
|
||||||
|
if (n == Integer.MIN_VALUE) throw new OverflowException("square");
|
||||||
|
int result = n * n;
|
||||||
|
// overflow check: result / n should == n (if n != 0)
|
||||||
|
if (n != 0 && result / n != n) throw new OverflowException("square");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int evaluate(int x) { return squareInt(operand.evaluate(x)); }
|
||||||
|
@Override public int evaluate(int x, int y, int z) { return squareInt(operand.evaluate(x,y,z)); }
|
||||||
|
@Override public int evaluate(List<Integer> vars) { return squareInt(operand.evaluate(vars)); }
|
||||||
|
@Override public BigInteger evaluateBi(List<BigInteger> vars) { return operand.evaluateBi(vars).pow(2); }
|
||||||
|
@Override public BigDecimal evaluateBd(List<BigDecimal> vars) { return operand.evaluateBd(vars).pow(2); }
|
||||||
|
|
||||||
|
@Override public String toString() { return "(" + operand + ")²"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
String s = operand.toMiniString();
|
||||||
|
boolean needParens = operand instanceof AbstractBinaryOperation
|
||||||
|
|| operand instanceof Cbrt
|
||||||
|
|| operand instanceof Sqrt
|
||||||
|
|| (s.startsWith("-") && (s.length() < 2 || !Character.isDigit(s.charAt(1))));
|
||||||
|
return needParens ? "(" + s + ")²" : s + "²";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Square)) return false;
|
||||||
|
return operand.equals(((Square) obj).operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() { return operand.hashCode() ^ 0x53515232; }
|
||||||
|
}
|
||||||
44
java/expression/Subtract.java
Normal file
44
java/expression/Subtract.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Subtract extends AbstractBinaryOperation {
|
||||||
|
|
||||||
|
public Subtract(AbstractExpression l, AbstractExpression r) {
|
||||||
|
super(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getOperator() {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getPriority() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isRightAssoc() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int applyInt(int a, int b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigInteger applyBi(BigInteger a, BigInteger b) {
|
||||||
|
return a.subtract(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BigDecimal applyBd(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.subtract(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
java/expression/ToMiniString.java
Normal file
10
java/expression/ToMiniString.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public interface ToMiniString {
|
||||||
|
default String toMiniString() {
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
317
java/expression/TripleExpression.java
Normal file
317
java/expression/TripleExpression.java
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.Pair;
|
||||||
|
import base.Selector;
|
||||||
|
import base.TestCounter;
|
||||||
|
import expression.common.ExpressionKind;
|
||||||
|
import expression.common.Type;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Three-argument arithmetic expression over integers.
|
||||||
|
*
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
@SuppressWarnings("ClassReferencesSubclass")
|
||||||
|
public interface TripleExpression extends ToMiniString {
|
||||||
|
int evaluate(int x, int y, int z);
|
||||||
|
|
||||||
|
// Tests follow. You may temporarily remove everything til the end.
|
||||||
|
|
||||||
|
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
|
||||||
|
ExpressionKind<TripleExpression, Integer> KIND = new ExpressionKind<>(
|
||||||
|
TYPE,
|
||||||
|
TripleExpression.class,
|
||||||
|
List.of(
|
||||||
|
Pair.of("x", new Variable("x")),
|
||||||
|
Pair.of("y", new Variable("y")),
|
||||||
|
Pair.of("z", new Variable("z"))
|
||||||
|
),
|
||||||
|
(expr, variables, values) ->
|
||||||
|
expr.evaluate(values.get(0), values.get(1), values.get(2))
|
||||||
|
);
|
||||||
|
|
||||||
|
@SuppressWarnings("PointlessArithmeticExpression")
|
||||||
|
static ExpressionTester<?, ?> tester(final TestCounter counter) {
|
||||||
|
final Variable vx = new Variable("x");
|
||||||
|
final Variable vy = new Variable("y");
|
||||||
|
final Variable vz = new Variable("z");
|
||||||
|
|
||||||
|
return new ExpressionTester<>(
|
||||||
|
counter,
|
||||||
|
KIND,
|
||||||
|
c -> (x, y, z) -> c,
|
||||||
|
(op, a, b) ->
|
||||||
|
(x, y, z) -> op.apply(a.evaluate(x, y, z), b.evaluate(x, y, z)),
|
||||||
|
Integer::sum,
|
||||||
|
(a, b) -> a - b,
|
||||||
|
(a, b) -> a * b,
|
||||||
|
(a, b) -> a / b
|
||||||
|
)
|
||||||
|
.basic("10", "10", (x, y, z) -> 10, c(10))
|
||||||
|
.basic("x", "x", (x, y, z) -> x, vx)
|
||||||
|
.basic("y", "y", (x, y, z) -> y, vy)
|
||||||
|
.basic("z", "z", (x, y, z) -> z, vz)
|
||||||
|
.basic("(x + 2)", "x + 2", (x, y, z) -> x + 2, new Add(vx, c(2)))
|
||||||
|
.basic(
|
||||||
|
"(2 - y)",
|
||||||
|
"2 - y",
|
||||||
|
(x, y, z) -> 2 - y,
|
||||||
|
new Subtract(c(2), vy)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(3 * z)",
|
||||||
|
"3 * z",
|
||||||
|
(x, y, z) -> 3 * z,
|
||||||
|
new Multiply(c(3), vz)
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(x / -2)",
|
||||||
|
"x / -2",
|
||||||
|
(x, y, z) -> -x / 2,
|
||||||
|
new Divide(vx, c(-2))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 + 2) + 3)",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
(x, y, z) -> 6,
|
||||||
|
new Add(new Add(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 + (2 + 3))",
|
||||||
|
"1 + 2 + 3",
|
||||||
|
(x, y, z) -> 6,
|
||||||
|
new Add(c(1), new Add(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 - 2) - 3)",
|
||||||
|
"1 - 2 - 3",
|
||||||
|
(x, y, z) -> -4,
|
||||||
|
new Subtract(new Subtract(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 - (2 - 3))",
|
||||||
|
"1 - (2 - 3)",
|
||||||
|
(x, y, z) -> 2,
|
||||||
|
new Subtract(c(1), new Subtract(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((1 * 2) * 3)",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
(x, y, z) -> 6,
|
||||||
|
new Multiply(new Multiply(c(1), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(1 * (2 * 3))",
|
||||||
|
"1 * 2 * 3",
|
||||||
|
(x, y, z) -> 6,
|
||||||
|
new Multiply(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((10 / 2) / 3)",
|
||||||
|
"10 / 2 / 3",
|
||||||
|
(x, y, z) -> 10 / 2 / 3,
|
||||||
|
new Divide(new Divide(c(10), c(2)), c(3))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"(10 / (3 / 2))",
|
||||||
|
"10 / (3 / 2)",
|
||||||
|
(x, y, z) -> 10,
|
||||||
|
new Divide(c(10), new Divide(c(3), c(2)))
|
||||||
|
)
|
||||||
|
.basic(
|
||||||
|
"((x * y) + ((z - 1) / 10))",
|
||||||
|
"x * y + (z - 1) / 10",
|
||||||
|
(x, y, z) -> x * y + (z - 1) / 10,
|
||||||
|
new Add(
|
||||||
|
new Multiply(vx, vy),
|
||||||
|
new Divide(new Subtract(vz, c(1)), c(10))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.basic("(x + y)", "x + y", (x, y, z) -> x + y, new Add(vx, vy))
|
||||||
|
.basic("(y + x)", "y + x", (x, y, z) -> y + x, new Add(vy, vx))
|
||||||
|
.advanced(
|
||||||
|
"(1 + 1)",
|
||||||
|
"1 + 1",
|
||||||
|
(x, y, z) -> 1 + 1,
|
||||||
|
new Add(c(1), c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(y - x)",
|
||||||
|
"y - x",
|
||||||
|
(x, y, z) -> y - x,
|
||||||
|
new Subtract(vy, vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 * x)",
|
||||||
|
"2 * x",
|
||||||
|
(x, y, z) -> 2 * x,
|
||||||
|
new Multiply(c(2), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 / x)",
|
||||||
|
"2 / x",
|
||||||
|
(x, y, z) -> 2 / x,
|
||||||
|
new Divide(c(2), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(z + (1 + 1))",
|
||||||
|
"z + 1 + 1",
|
||||||
|
(x, y, z) -> z + 1 + 1,
|
||||||
|
new Add(vz, new Add(c(1), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 - (y - x))",
|
||||||
|
"2 - (y - x)",
|
||||||
|
(x, y, z) -> 2 - (y - x),
|
||||||
|
new Subtract(c(2), new Subtract(vy, vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(z * (2 / x))",
|
||||||
|
"z * (2 / x)",
|
||||||
|
(x, y, z) -> z * (2 / x),
|
||||||
|
new Multiply(vz, new Divide(c(2), vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(z / (y - x))",
|
||||||
|
"z / (y - x)",
|
||||||
|
(x, y, z) -> z / (y - x),
|
||||||
|
new Divide(vz, new Subtract(vy, vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 * x) + y)",
|
||||||
|
"2 * x + y",
|
||||||
|
(x, y, z) -> 2 * x + y,
|
||||||
|
new Add(new Multiply(c(2), vx), vy)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((y - x) - 2)",
|
||||||
|
"y - x - 2",
|
||||||
|
(x, y, z) -> y - x - 2,
|
||||||
|
new Subtract(new Subtract(vy, vx), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 / x) * y)",
|
||||||
|
"2 / x * y",
|
||||||
|
(x, y, z) -> (2 / x) * y,
|
||||||
|
new Multiply(new Divide(c(2), vx), vy)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 + 1) / x)",
|
||||||
|
"(1 + 1) / x",
|
||||||
|
(x, y, z) -> (1 + 1) / x,
|
||||||
|
new Divide(new Add(c(1), c(1)), vx)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (2 * 3))",
|
||||||
|
"1 + 2 * 3",
|
||||||
|
(x, y, z) -> 7,
|
||||||
|
new Add(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 - (2 * 3))",
|
||||||
|
"1 - 2 * 3",
|
||||||
|
(x, y, z) -> -5,
|
||||||
|
new Subtract(c(1), new Multiply(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 + (2 / 3))",
|
||||||
|
"1 + 2 / 3",
|
||||||
|
(x, y, z) -> 1,
|
||||||
|
new Add(c(1), new Divide(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 - (2 / 3))",
|
||||||
|
"1 - 2 / 3",
|
||||||
|
(x, y, z) -> 1,
|
||||||
|
new Subtract(c(1), new Divide(c(2), c(3)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(2 + (z + (1 + 1)))",
|
||||||
|
"2 + z + 1 + 1",
|
||||||
|
(x, y, z) -> 2 + z + 1 + 1,
|
||||||
|
new Add(c(2), new Add(vz, new Add(c(1), c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 - ((2 * x) + y))",
|
||||||
|
"1 - (2 * x + y)",
|
||||||
|
(x, y, z) -> 1 - (2 * x + y),
|
||||||
|
new Subtract(c(1), new Add(new Multiply(c(2), vx), vy))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(1 * (z / (y - x)))",
|
||||||
|
"1 * (z / (y - x))",
|
||||||
|
(x, y, z) -> 1 * (z / (y - x)),
|
||||||
|
new Multiply(c(1), new Divide(vz, new Subtract(vy, vx)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(z / (z + (1 + 1)))",
|
||||||
|
"z / (z + 1 + 1)",
|
||||||
|
(x, y, z) -> z / (z + 1 + 1),
|
||||||
|
new Divide(vz, new Add(vz, new Add(c(1), c(1))))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((2 * x) + (1 + 1))",
|
||||||
|
"2 * x + 1 + 1",
|
||||||
|
(x, y, z) -> 2 * x + 1 + 1,
|
||||||
|
new Add(new Multiply(c(2), vx), new Add(c(1), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((1 + 1) - (1 + 1))",
|
||||||
|
"1 + 1 - (1 + 1)",
|
||||||
|
(x, y, z) -> 1 + 1 - (1 + 1),
|
||||||
|
new Subtract(new Add(c(1), c(1)), new Add(c(1), c(1)))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((y - x) * (2 / x))",
|
||||||
|
"(y - x) * (2 / x)",
|
||||||
|
(x, y, z) -> (y - x) * (2 / x),
|
||||||
|
new Multiply(new Subtract(vy, vx), new Divide(c(2), vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((y - x) / (2 * x))",
|
||||||
|
"(y - x) / (2 * x)",
|
||||||
|
(x, y, z) -> (y - x) / (2 * x),
|
||||||
|
new Divide(new Subtract(vy, vx), new Multiply(c(2), vx))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((y - x) - 2) + 1)",
|
||||||
|
"y - x - 2 + 1",
|
||||||
|
(x, y, z) -> y - x - 2 + 1,
|
||||||
|
new Add(new Subtract(new Subtract(vy, vx), c(2)), c(1))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((2 * x) + y) - z)",
|
||||||
|
"2 * x + y - z",
|
||||||
|
(x, y, z) -> 2 * x + y - z,
|
||||||
|
new Subtract(new Add(new Multiply(c(2), vx), vy), vz)
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"(((1 + 1) / x) * 2)",
|
||||||
|
"(1 + 1) / x * 2",
|
||||||
|
(x, y, z) -> ((1 + 1) / x) * 2,
|
||||||
|
new Multiply(new Divide(new Add(c(1), c(1)), vx), c(2))
|
||||||
|
)
|
||||||
|
.advanced(
|
||||||
|
"((z / (y - x)) / x)",
|
||||||
|
"z / (y - x) / x",
|
||||||
|
(x, y, z) -> z / (y - x) / x,
|
||||||
|
new Divide(new Divide(vz, new Subtract(vy, vx)), vx)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Const c(final Integer c) {
|
||||||
|
return TYPE.constant(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Selector SELECTOR = ExpressionTest.SELECTOR.variant(
|
||||||
|
"Triple",
|
||||||
|
ExpressionTest.v(TripleExpression::tester)
|
||||||
|
);
|
||||||
|
|
||||||
|
static void main(final String... args) {
|
||||||
|
TripleExpression.SELECTOR.main(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
118
java/expression/Variable.java
Normal file
118
java/expression/Variable.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package expression;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Doschennikov Nikita (me@fymio.us)
|
||||||
|
*/
|
||||||
|
public class Variable extends AbstractExpression {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
public Variable(String name) {
|
||||||
|
this.name = name;
|
||||||
|
this.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable(int index) {
|
||||||
|
this.index = index;
|
||||||
|
this.name = "$" + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable(int index, String name) {
|
||||||
|
this.index = index;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x) {
|
||||||
|
if (index >= 0) {
|
||||||
|
if (index == 0) return x;
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Positional variable $" +
|
||||||
|
index +
|
||||||
|
" cannot be evaluated with a single value"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ("x".equals(name)) return x;
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Variable '" + name + "' is not 'x'; use evaluate(x, y, z) instead"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(int x, int y, int z) {
|
||||||
|
if (index >= 0) {
|
||||||
|
return switch (index) {
|
||||||
|
case 0 -> x;
|
||||||
|
case 1 -> y;
|
||||||
|
case 2 -> z;
|
||||||
|
default -> throw new IndexOutOfBoundsException(
|
||||||
|
"Variable index " +
|
||||||
|
index +
|
||||||
|
" out of range for triple evaluate"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return switch (name) {
|
||||||
|
case "x" -> x;
|
||||||
|
case "y" -> y;
|
||||||
|
case "z" -> z;
|
||||||
|
default -> throw new IllegalStateException(
|
||||||
|
"Unknown variable: " + name
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int evaluate(List<Integer> vars) {
|
||||||
|
return vars.get(resolvedIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger evaluateBi(List<BigInteger> vars) {
|
||||||
|
return vars.get(resolvedIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal evaluateBd(List<BigDecimal> vars) {
|
||||||
|
return vars.get(resolvedIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolvedIndex() {
|
||||||
|
if (index >= 0) return index;
|
||||||
|
return switch (name) {
|
||||||
|
case "x" -> 0;
|
||||||
|
case "y" -> 1;
|
||||||
|
case "z" -> 2;
|
||||||
|
default -> throw new IllegalStateException(
|
||||||
|
"Unknown variable: " + name
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toMiniString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Variable)) return false;
|
||||||
|
return name.equals(((Variable) obj).name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
32
java/expression/common/Expr.java
Normal file
32
java/expression/common/Expr.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package expression.common;
|
||||||
|
|
||||||
|
import base.Functional;
|
||||||
|
import base.Pair;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public record Expr<C, V>(Node<C> node, List<Pair<String, V>> variables) {
|
||||||
|
public <T> List<Pair<String, T>> variables(final BiFunction<String, V, T> f) {
|
||||||
|
return Functional.map(
|
||||||
|
variables,
|
||||||
|
variable -> variable.second(f.apply(variable.first(), variable.second()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Expr<C, T> convert(final BiFunction<String, V, T> f) {
|
||||||
|
return of(node, variables(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expr<C, V> node(final Function<Node<C>, Node<C>> f) {
|
||||||
|
return of(f.apply(node), variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <C, V> Expr<C, V> of(final Node<C> node, final List<Pair<String, V>> variables) {
|
||||||
|
return new Expr<>(node, variables);
|
||||||
|
}
|
||||||
|
}
|
||||||
94
java/expression/common/ExpressionKind.java
Normal file
94
java/expression/common/ExpressionKind.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package expression.common;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.Functional;
|
||||||
|
import base.Pair;
|
||||||
|
import expression.ToMiniString;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class ExpressionKind<E extends ToMiniString, C> {
|
||||||
|
private final Type<C> type;
|
||||||
|
private final Class<E> kind;
|
||||||
|
private final Variables<E> variables;
|
||||||
|
private final Evaluator<E, C> evaluator;
|
||||||
|
|
||||||
|
public ExpressionKind(
|
||||||
|
final Type<C> type,
|
||||||
|
final Class<E> kind,
|
||||||
|
final Variables<E> variables,
|
||||||
|
final Evaluator<E, C> evaluator
|
||||||
|
) {
|
||||||
|
this.type = type;
|
||||||
|
this.kind = kind;
|
||||||
|
this.variables = variables;
|
||||||
|
this.evaluator = evaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionKind(
|
||||||
|
final Type<C> type,
|
||||||
|
final Class<E> kind,
|
||||||
|
final List<Pair<String, E>> variables,
|
||||||
|
final Evaluator<E, C> evaluator
|
||||||
|
) {
|
||||||
|
this(type, kind, (r, c) -> variables, evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public C evaluate(final E expression, final List<String> variables, final List<C> values) throws Exception {
|
||||||
|
return evaluator.evaluate(expression, variables, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public E cast(final Object expression) {
|
||||||
|
return kind.cast(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return kind.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public E constant(final C value) {
|
||||||
|
return cast(type.constant(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public C randomValue(final ExtendedRandom random) {
|
||||||
|
return type.randomValue(random);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<List<C>> allValues(final int length, final List<Integer> values) {
|
||||||
|
return Functional.allValues(fromInts(values), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<C> fromInts(final List<Integer> values) {
|
||||||
|
return Functional.map(values, this::fromInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public C fromInt(final int value) {
|
||||||
|
return type.fromInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return kind.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionKind<E, C> withVariables(final Variables<E> variables) {
|
||||||
|
return new ExpressionKind<>(type, kind, variables, evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variables<E> variables() {
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Variables<E> {
|
||||||
|
List<Pair<String, E>> generate(final ExtendedRandom random, final int count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Evaluator<E, R> {
|
||||||
|
R evaluate(final E expression, final List<String> vars, final List<R> values) throws Exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
173
java/expression/common/Generator.java
Normal file
173
java/expression/common/Generator.java
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package expression.common;
|
||||||
|
|
||||||
|
import base.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class Generator<C, E> {
|
||||||
|
private final Supplier<C> constant;
|
||||||
|
private final List<Named<Integer>> ops;
|
||||||
|
private final ExpressionKind.Variables<E> variables;
|
||||||
|
private final Set<String> forbidden;
|
||||||
|
private final ExtendedRandom random;
|
||||||
|
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests;
|
||||||
|
|
||||||
|
public Generator(
|
||||||
|
final Supplier<C> constant,
|
||||||
|
final List<Named<Integer>> ops,
|
||||||
|
final ExpressionKind.Variables<E> variables,
|
||||||
|
final Set<String> forbidden,
|
||||||
|
final ExtendedRandom random,
|
||||||
|
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
|
||||||
|
) {
|
||||||
|
this.constant = constant;
|
||||||
|
this.ops = List.copyOf(ops);
|
||||||
|
this.variables = variables;
|
||||||
|
this.forbidden = Set.copyOf(forbidden);
|
||||||
|
this.random = random;
|
||||||
|
this.basicTests = List.copyOf(basicTests);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <C> Builder<C> builder(final Supplier<C> constant, final ExtendedRandom random) {
|
||||||
|
return new Builder<>(random, constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRandom(
|
||||||
|
final TestCounter counter,
|
||||||
|
final int denominator,
|
||||||
|
final Consumer<Expr<C, E>> consumer
|
||||||
|
) {
|
||||||
|
final int d = Math.max(TestCounter.DENOMINATOR, denominator);
|
||||||
|
testRandom(counter, consumer, 1, 100, 100 / d, (vars, depth) -> generateFullDepth(vars, Math.min(depth, 3)));
|
||||||
|
testRandom(counter, consumer, 2, 1000 / d, 1, this::generateSize);
|
||||||
|
testRandom(counter, consumer, 3, 12, 100 / d, this::generateFullDepth);
|
||||||
|
testRandom(counter, consumer, 4, 777 / d, 1, this::generatePartialDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRandom(
|
||||||
|
final TestCounter counter,
|
||||||
|
final Consumer<Expr<C, E>> consumer,
|
||||||
|
final int seq,
|
||||||
|
final int levels,
|
||||||
|
final int perLevel,
|
||||||
|
final BiFunction<List<Node<C>>, Integer, Node<C>> generator
|
||||||
|
) {
|
||||||
|
counter.scope("Random tests #" + seq, () -> {
|
||||||
|
final int total = levels * perLevel;
|
||||||
|
int generated = 0;
|
||||||
|
for (int level = 0; level < levels; level++) {
|
||||||
|
for (int j = 0; j < perLevel; j++) {
|
||||||
|
if (generated % 100 == 0) {
|
||||||
|
progress(counter, total, generated);
|
||||||
|
}
|
||||||
|
generated++;
|
||||||
|
|
||||||
|
final List<Pair<String, E>> vars = variables(random.nextInt(10) + 1);
|
||||||
|
consumer.accept(Expr.of(generator.apply(Functional.map(vars, v -> Node.op(v.first())), level), vars));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress(counter, generated, total);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void progress(final TestCounter counter, final int total, final int generated) {
|
||||||
|
counter.format("Completed %4d out of %d%n", generated, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> generate(
|
||||||
|
final List<Node<C>> variables,
|
||||||
|
final boolean nullary,
|
||||||
|
final Supplier<Node<C>> unary,
|
||||||
|
final Supplier<Pair<Node<C>, Node<C>>> binary
|
||||||
|
) {
|
||||||
|
if (nullary || ops.isEmpty()) {
|
||||||
|
return random.nextBoolean() ? random.randomItem(variables) : Node.constant(constant.get());
|
||||||
|
} else {
|
||||||
|
final Named<Integer> op = random.randomItem(ops);
|
||||||
|
if (Math.abs(op.value()) == 1) {
|
||||||
|
return Node.op(op.name(), (op.value() + 1) >> 1, unary.get());
|
||||||
|
} else {
|
||||||
|
final Pair<Node<C>, Node<C>> pair = binary.get();
|
||||||
|
return Node.op(op.name(), pair.first(), pair.second());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> generate(final List<Node<C>> variables, final boolean nullary, final Supplier<Node<C>> child) {
|
||||||
|
return generate(variables, nullary, child, () -> Pair.of(child.get(), child.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> generateFullDepth(final List<Node<C>> variables, final int depth) {
|
||||||
|
return generate(variables, depth == 0, () -> generateFullDepth(variables, depth - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> generatePartialDepth(final List<Node<C>> variables, final int depth) {
|
||||||
|
return generate(variables, depth == 0, () -> generatePartialDepth(variables, random.nextInt(depth)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> generateSize(final List<Node<C>> variables, final int size) {
|
||||||
|
final int first = size <= 1 ? 0 : random.nextInt(size);
|
||||||
|
return generate(
|
||||||
|
variables,
|
||||||
|
size == 0,
|
||||||
|
() -> generateSize(variables, size - 1),
|
||||||
|
() -> Pair.of(
|
||||||
|
generateSize(variables, first),
|
||||||
|
generateSize(variables, size - 1 - first)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBasic(final Consumer<Expr<C, E>> consumer) {
|
||||||
|
basicTests.forEach(test -> {
|
||||||
|
final List<Pair<String, E>> vars = variables(random.nextInt(5) + 3);
|
||||||
|
test.apply(Functional.map(vars, v -> Node.op(v.first())))
|
||||||
|
.map(node -> Expr.of(node, vars))
|
||||||
|
.forEachOrdered(consumer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Pair<String, E>> variables(final int count) {
|
||||||
|
List<Pair<String, E>> vars;
|
||||||
|
do {
|
||||||
|
vars = variables.generate(random, count);
|
||||||
|
} while (vars.stream().map(Pair::first).anyMatch(forbidden::contains));
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public static final class Builder<C> {
|
||||||
|
private final ExtendedRandom random;
|
||||||
|
private final Supplier<C> constant;
|
||||||
|
|
||||||
|
private final List<Named<Integer>> ops = new ArrayList<>();
|
||||||
|
private final Set<String> forbidden = new HashSet<>();
|
||||||
|
|
||||||
|
private Builder(final ExtendedRandom random, final Supplier<C> constant) {
|
||||||
|
this.random = random;
|
||||||
|
this.constant = constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final String name, final int arity) {
|
||||||
|
ops.add(Named.of(name, arity));
|
||||||
|
forbidden.add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E> Generator<C, E> build(
|
||||||
|
final ExpressionKind.Variables<E> variables,
|
||||||
|
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
|
||||||
|
) {
|
||||||
|
return new Generator<>(constant, ops, variables, forbidden, random, basicTests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
java/expression/common/Node.java
Normal file
106
java/expression/common/Node.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package expression.common;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public abstract class Node<T> {
|
||||||
|
private Node() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract <R> R get(Const<T, R> con, Nullary<R> nul, Unary<Node<T>, R> un, Binary<Node<T>, R> bin);
|
||||||
|
public abstract <R> R cata(Const<T, R> con, Nullary<R> nul, Unary<R, R> un, Binary<R, R> bin);
|
||||||
|
|
||||||
|
public final String toPolish() {
|
||||||
|
return cata(
|
||||||
|
T::toString,
|
||||||
|
name -> name,
|
||||||
|
(name, priority, a) -> a + " " + name + ":1",
|
||||||
|
(name, a1, a2) -> a1 + " " + a2 + " " + name + ":2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return cata(
|
||||||
|
T::toString,
|
||||||
|
name -> name,
|
||||||
|
(name, priority, a) -> name.equals("[") ? "[" + a + "]" :
|
||||||
|
(priority & 1) == 1 ? "(" + name + " " + a + ")" : "(" + a + " " + name + ")",
|
||||||
|
(name, a1, a2) -> "(" + a1 + " " + name + " " + a2 + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Node<T> constant(final T value) {
|
||||||
|
return new Node<>() {
|
||||||
|
@Override
|
||||||
|
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||||
|
return con.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||||
|
return con.apply(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Node<T> op(final String name) {
|
||||||
|
return new Node<>() {
|
||||||
|
@Override
|
||||||
|
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||||
|
return nul.apply(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||||
|
return nul.apply(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Node<T> op(final String name, final int priority, final Node<T> arg) {
|
||||||
|
return new Node<>() {
|
||||||
|
@Override
|
||||||
|
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||||
|
return un.apply(name, priority, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||||
|
return un.apply(name, priority, arg.cata(con, nul, un, bin));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Node<T> op(final String name, final Node<T> arg1, final Node<T> arg2) {
|
||||||
|
return new Node<>() {
|
||||||
|
@Override
|
||||||
|
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
|
||||||
|
return bin.apply(name, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
|
||||||
|
return bin.apply(name, arg1.cata(con, nul, un, bin), arg2.cata(con, nul, un, bin));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Const<T, R> extends Function<T, R> {}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Nullary<R> extends Function<String, R> {}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Unary<T, R> {
|
||||||
|
R apply(String name, int priority, T arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Binary<T, R> {
|
||||||
|
R apply(String name, T arg1, T arg2);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
java/expression/common/NodeRenderer.java
Normal file
96
java/expression/common/NodeRenderer.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package expression.common;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
public class NodeRenderer<C> {
|
||||||
|
public static final String PAREN = "[";
|
||||||
|
public static final List<Paren> DEFAULT_PARENS = List.of(paren("(", ")"));
|
||||||
|
|
||||||
|
public static final Mode MINI_MODE = Mode.SIMPLE_MINI; // Replace by TRUE_MINI for some challenge;
|
||||||
|
public static final Settings FULL = Mode.FULL.settings(0);
|
||||||
|
public static final Settings FULL_EXTRA = Mode.FULL.settings(Integer.MAX_VALUE / 4);
|
||||||
|
public static final Settings SAME = Mode.SAME.settings(0);
|
||||||
|
public static final Settings MINI = MINI_MODE.settings(0);
|
||||||
|
public static final Settings TRUE_MINI = Mode.TRUE_MINI.settings(0);
|
||||||
|
|
||||||
|
private final Renderer<C, Settings, Node<C>> renderer;
|
||||||
|
private final Map<String, String> brackets;
|
||||||
|
private final ExtendedRandom random;
|
||||||
|
|
||||||
|
public NodeRenderer(
|
||||||
|
final Renderer<C, Settings, Node<C>> renderer,
|
||||||
|
final Map<String, String> brackets,
|
||||||
|
final ExtendedRandom random
|
||||||
|
) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
this.brackets = Map.copyOf(brackets);
|
||||||
|
this.random = random;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <C> Node<C> paren(final boolean condition, final Node<C> node) {
|
||||||
|
return condition ? Node.op(PAREN, 1, node) : node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Paren paren(final String open, final String close) {
|
||||||
|
return new Paren(open, close);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node<C> renderToNode(final Settings settings, final Expr<C, ?> expr) {
|
||||||
|
final Expr<C, Node<C>> convert = expr.convert((name, variable) -> Node.op(name));
|
||||||
|
return renderer.render(convert, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String render(final Node<C> node, final List<Paren> parens) {
|
||||||
|
return node.cata(
|
||||||
|
String::valueOf,
|
||||||
|
name -> name,
|
||||||
|
(name, priority, arg) ->
|
||||||
|
name == PAREN ? random.randomItem(parens).apply(arg) :
|
||||||
|
priority == Integer.MAX_VALUE ? name + arg + brackets.get(name) :
|
||||||
|
(priority & 1) == 1 ? name + arg :
|
||||||
|
arg + name,
|
||||||
|
(name, a, b) -> a + " " + name + " " + b
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String render(final Expr<C, ?> expr, final Settings settings) {
|
||||||
|
return render(renderToNode(settings, expr), settings.parens());
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Mode {
|
||||||
|
FULL, SAME, TRUE_MINI, SIMPLE_MINI;
|
||||||
|
|
||||||
|
public Settings settings(final int limit) {
|
||||||
|
return new Settings(this, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Paren(String open, String close) {
|
||||||
|
String apply(final String expression) {
|
||||||
|
return open() + expression + close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Settings(Mode mode, int limit, List<Paren> parens) {
|
||||||
|
public Settings(final Mode mode, final int limit) {
|
||||||
|
this(mode, limit, DEFAULT_PARENS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <C> Node<C> extra(Node<C> node, final ExtendedRandom random) {
|
||||||
|
while (random.nextInt(Integer.MAX_VALUE) < limit) {
|
||||||
|
node = paren(true, node);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Settings withParens(final List<Paren> parens) {
|
||||||
|
return this.parens.equals(parens) ? this : new Settings(mode, limit, List.copyOf(parens));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
java/expression/common/NodeRendererBuilder.java
Normal file
145
java/expression/common/NodeRendererBuilder.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package expression.common;
|
||||||
|
|
||||||
|
import base.ExtendedRandom;
|
||||||
|
import base.Functional;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||||
|
public class NodeRendererBuilder<C> {
|
||||||
|
private final Renderer.Builder<C, NodeRenderer.Settings, Node<C>> nodeRenderer = Renderer.builder(Node::constant);
|
||||||
|
private final Map<String, Priority> priorities = new HashMap<>();
|
||||||
|
private final Map<String, String> brackets = new HashMap<>();
|
||||||
|
private final ExtendedRandom random;
|
||||||
|
|
||||||
|
public NodeRendererBuilder(final ExtendedRandom random) {
|
||||||
|
this.random = random;
|
||||||
|
nodeRenderer.unary(NodeRenderer.PAREN, (mode, arg) -> NodeRenderer.paren(true, arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unary(final String name, final int priority) {
|
||||||
|
final String space = name.equals("-") || Character.isLetter(name.charAt(0)) ? " " : "";
|
||||||
|
nodeRenderer.unary(
|
||||||
|
name,
|
||||||
|
(settings, arg) -> settings.extra(Node.op(name, priority, inner(settings, priority, arg, space)), random)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unary(final String left, final String right) {
|
||||||
|
brackets.put(left, right);
|
||||||
|
nodeRenderer.unary(
|
||||||
|
left,
|
||||||
|
(settings, arg) -> settings.extra(Node.op(left, Integer.MAX_VALUE, arg), random)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> inner(final NodeRenderer.Settings settings, final int priority, final Node<C> arg, final String space) {
|
||||||
|
if (settings.mode() == NodeRenderer.Mode.FULL) {
|
||||||
|
return NodeRenderer.paren(true, arg);
|
||||||
|
} else {
|
||||||
|
final String op = arg.get(
|
||||||
|
c -> space,
|
||||||
|
n -> space,
|
||||||
|
(n, p, a) ->
|
||||||
|
priority > unaryPriority(arg) ? NodeRenderer.PAREN :
|
||||||
|
NodeRenderer.PAREN.equals(n) ? "" :
|
||||||
|
space,
|
||||||
|
(n, a, b) -> NodeRenderer.PAREN
|
||||||
|
);
|
||||||
|
return op.isEmpty() ? arg : Node.op(op, Priority.MAX.priority | 1, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <C> Integer unaryPriority(final Node<C> node) {
|
||||||
|
return node.get(c -> Integer.MAX_VALUE, n -> Integer.MAX_VALUE, (n, p, a) -> p, (n, a, b) -> Integer.MIN_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void binary(final String name, final int priority) {
|
||||||
|
final Priority mp = new Priority(name, priority);
|
||||||
|
priorities.put(name, mp);
|
||||||
|
|
||||||
|
nodeRenderer.binary(name, (settings, l, r) -> settings.extra(process(settings, mp, l, r), random));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> process(final NodeRenderer.Settings settings, final Priority mp, final Node<C> l, final Node<C> r) {
|
||||||
|
if (settings.mode() == NodeRenderer.Mode.FULL) {
|
||||||
|
return NodeRenderer.paren(true, op(mp, l, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Priority lp = priority(l);
|
||||||
|
final Priority rp = priority(r);
|
||||||
|
|
||||||
|
final int rc = rp.compareLevels(mp);
|
||||||
|
|
||||||
|
// :NOTE: Especially ugly code, do not replicate
|
||||||
|
final boolean advanced = settings.mode() == NodeRenderer.Mode.SAME
|
||||||
|
|| mp.has(2)
|
||||||
|
|| mp.has(1) && (mp != rp || (settings.mode() == NodeRenderer.Mode.TRUE_MINI && hasOther(r, rp)));
|
||||||
|
|
||||||
|
final Node<C> al = NodeRenderer.paren(lp.compareLevels(mp) < 0, l);
|
||||||
|
if (rc == 0 && !advanced) {
|
||||||
|
return get(r, null, (n, a, b) -> rp.op(mp.op(al, a), b));
|
||||||
|
} else {
|
||||||
|
return mp.op(al, NodeRenderer.paren(rc == 0 && advanced || rc < 0, r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasOther(final Node<C> node, final Priority priority) {
|
||||||
|
return get(node, () -> false, (name, l, r) -> {
|
||||||
|
final Priority p = Functional.get(priorities, name);
|
||||||
|
if (p.compareLevels(priority) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return p != priority || hasOther(l, priority);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<C> op(final Priority mp, final Node<C> l, final Node<C> r) {
|
||||||
|
return mp.op(l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Priority priority(final Node<C> node) {
|
||||||
|
return get(node, () -> Priority.MAX, (n, a, b) -> Functional.get(priorities, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <R> R get(final Node<C> node, final Supplier<R> common, final Node.Binary<Node<C>, R> binary) {
|
||||||
|
return node.get(
|
||||||
|
c -> common.get(),
|
||||||
|
n -> common.get(),
|
||||||
|
(n, p, a) -> common.get(),
|
||||||
|
binary
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeRenderer<C> build() {
|
||||||
|
return new NodeRenderer<>(nodeRenderer.build(), brackets, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
// :NOTE: Especially ugly bit-fiddling, do not replicate
|
||||||
|
private record Priority(String op, int priority) {
|
||||||
|
private static final int Q = 3;
|
||||||
|
private static final Priority MAX = new Priority("MAX", Integer.MAX_VALUE - Q);
|
||||||
|
|
||||||
|
private int compareLevels(final Priority that) {
|
||||||
|
return (priority | Q) - (that.priority | Q);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Priority(%s, %d, %d)", op, priority | Q, priority & Q);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <C> Node<C> op(final Node<C> l, final Node<C> r) {
|
||||||
|
return Node.op(op, l, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean has(final int value) {
|
||||||
|
return (priority & Q) == value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user