migration

This commit is contained in:
root
2026-04-13 20:12:01 +03:00
commit 46ab1753a5
201 changed files with 16685 additions and 0 deletions

458
README.md Normal file
View File

@@ -0,0 +1,458 @@
# Тесты к курсу «Парадигмы программирования»
[Условия домашних заданий](https://www.kgeorgiy.info/courses/paradigms/homeworks.html)
## Домашнее задание 9. Линейная алгебра на Clojure [![Clojure Linear Tests](https://git.fym.su/code.java/paradigms/actions/workflows/clj-linear.yml/badge.svg)](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 [![Clojure Exmaple Tests](https://git.fym.su/code.java/paradigms/actions/workflows/clj-example.yml/badge.svg)](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 [![JavaScript Prefix Tests](https://git.fym.su/code.java/paradigms/actions/workflows/js-prefix.yml/badge.svg)](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 [![JavaScript Objective Expressions Tests](https://git.fym.su/code.java/paradigms/actions/workflows/js-objective-expressions.yml/badge.svg)](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 [![JavaScript Expressions Tests](https://git.fym.su/code.java/paradigms/actions/workflows/js-expressions.yml/badge.svg)](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 [![JavaScript Example Tests](https://git.fym.su/code.java/paradigms/actions/workflows/js-example.yml/badge.svg)](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. Вычисления в различных типах [![Generic Tests](https://git.fym.su/code.java/paradigms/actions/workflows/generic.yml/badge.svg)](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. Очереди [![Queues Tests](https://git.fym.su/code.java/paradigms/actions/workflows/queues.yml/badge.svg)](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. Очередь на массиве [![ArrayQueue Tests](https://git.fym.su/code.java/paradigms/actions/workflows/array-queue.yml/badge.svg)](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. Бинарный поиск [![BinarySearch Tests](https://git.fym.su/code.java/paradigms/actions/workflows/search.yml/badge.svg)](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. Обработка ошибок [![Exception Tests](https://git.fym.su/code.java/paradigms/actions/workflows/exception.yml/badge.svg)](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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
clojure/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.sh text eol=lf

1
clojure/RunClojure.cmd Normal file
View File

@@ -0,0 +1 @@
@java --class-path "%~dp0lib/*" clojure.main %*

4
clojure/RunClojure.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
java \
--class-path "$(dirname "0")/lib/*" \
clojure.main "$@"

23
clojure/TestClojure.cmd Normal file
View 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
View 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

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}
}

View 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);
}
}

View 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
View File

@@ -0,0 +1,7 @@
(defn hello [name]
(let [message (str "Hello, " name "!")]
(println " " message)
message))
(def add +)

8
clojure/examples.clj Normal file
View 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")

View 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)))

View 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))

View 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))

View 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)))

View 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))

View 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))

Binary file not shown.

Binary file not shown.

Binary file not shown.

92
clojure/linear.clj Normal file
View 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
View 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);
}
}

View 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
View 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);
}
};
}
}

View 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);
}
}

View 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
View 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;
}
}

View 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
View 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
View 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
View 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
View 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;
}
}
}

View 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
View 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
View 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";
}
}

View 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
View 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();
}
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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");
}
});
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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;
}
}

View 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> {
}

View 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;
}));
};
}
}

View 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();
}

View 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
View 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; }
}

View 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()
);
}
}

View 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
View 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);
}
}

View 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);
}
}

View 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
View 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; }
}

View 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;
}
}

View 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"
);
}
}

View 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
View 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; }
}

View 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;
}
}

View 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);
}
}

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Doschennikov Nikita (me@fymio.us)
*/
public class DivisionByZeroException extends ArithmeticException {
public DivisionByZeroException() {
super("division by zero");
}
}

View 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)
);
}
}

View 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);
}
}

View 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);
});
}
}
}

View 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
View 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; }
}

View 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
View 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
View 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
View 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
View 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
View 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);
}
}

View 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);
}
}

View 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
View 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; }
}

View 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()); }
}

View 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;
}
}

View 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
View 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; }
}

View 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; }
}

View 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);
}
}

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface ToMiniString {
default String toMiniString() {
return toString();
}
}

View 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);
}
}

View 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();
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}
}

View 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);
}
}

View 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));
}
}
}

View 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