update
This commit is contained in:
22
.gitea/workflows/expression.yml
Normal file
22
.gitea/workflows/expression.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Expression Tests
|
||||
|
||||
on:
|
||||
# push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Expression tests
|
||||
run: |
|
||||
java -ea -cp out expression.ExpressionTest Base
|
||||
22
.gitea/workflows/fast-reverse.yml
Normal file
22
.gitea/workflows/fast-reverse.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Fast Reverse Tests
|
||||
|
||||
on:
|
||||
# push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Fast Reverse tests
|
||||
run: |
|
||||
java -ea -cp out reverse.FastReverseTest Base 3233
|
||||
26
.gitea/workflows/markup.yml
Normal file
26
.gitea/workflows/markup.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Markup Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Markup tests
|
||||
run: |
|
||||
java -ea -cp out markup.MarkupTest Base 3233 3435 3637 3839 4142 4749
|
||||
|
||||
- name: Run Markup List tests
|
||||
run: |
|
||||
java -ea -cp out markup.MarkupListTest 3637 3839 4142 4749
|
||||
22
.gitea/workflows/md2html.yml
Normal file
22
.gitea/workflows/md2html.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Markdown to Html Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Markdown to Html tests
|
||||
run: |
|
||||
java -ea -cp out md2html.Md2HtmlTest Base
|
||||
22
.gitea/workflows/reverse.yml
Normal file
22
.gitea/workflows/reverse.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Reverse Tests
|
||||
|
||||
on:
|
||||
# push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Reverse tests
|
||||
run: |
|
||||
java -ea -cp out reverse.ReverseTest Base 3233 3435 3637 3839 4142 4749
|
||||
22
.gitea/workflows/sum.yml
Normal file
22
.gitea/workflows/sum.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Sum Tests
|
||||
|
||||
on:
|
||||
# push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Sum tests
|
||||
run: |
|
||||
java -ea -cp out sum.SumTest Base 3233 3435 3637 3839 4142 4749
|
||||
22
.gitea/workflows/word-stat.yml
Normal file
22
.gitea/workflows/word-stat.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Word Stat Tests
|
||||
|
||||
on:
|
||||
# push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Word Stat tests
|
||||
run: |
|
||||
java -ea -cp out wordStat.WordStatTest Base 3233 3435 3637 3839 4142 4749
|
||||
22
.gitea/workflows/wspp.yml
Normal file
22
.gitea/workflows/wspp.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Word Stat++ Tests
|
||||
|
||||
on:
|
||||
# push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Compile Java
|
||||
run: |
|
||||
mkdir -p out
|
||||
javac -d out $(find java -name "*.java")
|
||||
|
||||
- name: Run Word Stat++ tests
|
||||
run: |
|
||||
java -ea -cp out wspp.WsppTest Base 3233 3435 3637 3839 4142 4749
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
*.xml
|
||||
java/out/*
|
||||
*.iml
|
||||
*.idea*
|
||||
*.class
|
||||
554
README.md
Normal file
554
README.md
Normal file
@@ -0,0 +1,554 @@
|
||||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
# Тесты к курсу «Введение в программирование»
|
||||
|
||||
[Условия домашних заданий](https://www.kgeorgiy.info/courses/prog-intro/homeworks.html)
|
||||
|
||||
<!--
|
||||
## Домашнее задание 14. Обработка ошибок
|
||||
|
||||
Модификации
|
||||
* *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* Дополнительно реализуйте бинарные операции (максимальный приоритет):
|
||||
* `**` – возведение в степень, `2 ** 3` равно 8;
|
||||
* `//` – логарифм, `10 // 2` равно 3.
|
||||
* *3839* Дополнительно реализуйте
|
||||
* бинарные операции (максимальный приоритет):
|
||||
* `**` – возведение в степень, `2 ** 3` равно 8;
|
||||
* `//` – логарифм, `10 // 2` равно 3.
|
||||
* унарные операции:
|
||||
* `log₂` – логарифм по основанию 2, `log₂ 10` равно 3;
|
||||
* `pow₂` – два в степени, `pow₂ 4` равно 16.
|
||||
* *3435* Дополнительно реализуйте унарные операции:
|
||||
* `log₂` – логарифм по основанию 2, `log₂ 10` равно 3;
|
||||
* `pow₂` – два в степени, `pow₂ 4` равно 16.
|
||||
* *3233*
|
||||
* Дополнительно реализуйте унарные операции:
|
||||
* `low` – младший установленный бит, `low 123456` равно 64;
|
||||
* `high` – старший установленный бит, `high 123456` равно 65536.
|
||||
|
||||
|
||||
## Домашнее задание 13. Разбор выражений
|
||||
|
||||
Модификации
|
||||
* *Base*
|
||||
* Класс `ExpressionParser` должен реализовывать интерфейс
|
||||
[ListParser](java/expression/parser/ListParser.java)
|
||||
* Результат разбора должен реализовывать интерфейс
|
||||
[ListExpression](java/expression/ListExpression.java)
|
||||
* [Исходный код тестов](java/expression/parser/ParserTest.java)
|
||||
* Первый аргумент: `easy` или `hard`
|
||||
* Последующие аргументы: модификации
|
||||
* *3637*. Дополнительно реализуйте:
|
||||
* бинарные операции (минимальный приоритет):
|
||||
* `min` – минимум, `2 min 3` равно 2;
|
||||
* `max` – максимум, `2 max 3` равно 3.
|
||||
* унарную операцию
|
||||
`reverse` – число с переставленными цифрами,
|
||||
`reverse -12345` равно `-54321`.
|
||||
* *3839*. * Дополнительно реализуйте:
|
||||
* бинарные операции (минимальный приоритет):
|
||||
* `min` – минимум, `2 min 3` равно 2;
|
||||
* `max` – максимум, `2 max 3` равно 3;
|
||||
* унарные операции
|
||||
* `reverse` – число с переставленными цифрами,
|
||||
`reverse -12345` равно `-54321`;
|
||||
* `digits` – сумма цифр числа, `digits -12345` равно 15.
|
||||
* *3435*
|
||||
* унарные операции
|
||||
* `floor` – округление вниз до числа кратного 1000,
|
||||
`floor 1234` равно 1000;
|
||||
* `ceiling` – округление вверх до числа кратного 1000,
|
||||
`ceiling 1234` равно 2000.
|
||||
* бинарные операции (минимальный приоритет):
|
||||
* `set` – установка бита, `2 set 3` равно 10;
|
||||
* `clear` – сброс бита, `10 clear 3` равно 2.
|
||||
* *3233*. Дополнительно реализуйте унарные операции
|
||||
* `floor` – округление вниз до числа кратного 1000,
|
||||
`floor 1234` равно 1000;
|
||||
* `ceiling` – округление вверх до числа кратного 1000,
|
||||
`ceiling 1234` равно 2000.
|
||||
|
||||
## Домашнее задание 12. Выражения
|
||||
|
||||
Модификации
|
||||
* *Base*
|
||||
* Реализуйте интерфейс [Expression](java/expression/Expression.java)
|
||||
* [Исходный код тестов](java/expression/ExpressionTest.java)
|
||||
* Первый аргумент: `easy` или `hard`
|
||||
* Последующие аргументы: модификации
|
||||
* *Triple* (32-39)
|
||||
* Дополнительно реализуйте поддержку выражений с тремя переменными: `x`, `y` и `z`.
|
||||
* Например, для `expr = new Subtract(new Add(new Variable("x"), new Variable("y")), new Const(1))`:
|
||||
* `expr.evaluate(2, 3, 5)` должно быть равно 4;
|
||||
* `expr.toString()` должно быть равно `((x + y) - 1)`.
|
||||
* Интерфейс/тесты [TripleExpression](java/expression/TripleExpression.java).
|
||||
* *BigIntegerList* (36, 37). Дополнительно реализуйте поддержку вычисления
|
||||
выражений в типе `BigInteger` с позиционными переменными.
|
||||
* Конструктор позиционной переменной получает индекс переменной.
|
||||
* При выводе позиционная переменная должна иметь вид `$index`.
|
||||
* Метод вычисления выражения должен называться `evaluateBi`,
|
||||
ему передаётся список значений переменных.
|
||||
* Например, для `expr = new Subtract(new Add(new Variable(0), new Variable(1)), new Const(BigInteger.ONE))`:
|
||||
* `expr.evaluateBi(List.of(BigInteger.TWO, BigInteger.THREE))` должно быть равно 4;
|
||||
* `expr.toString()` должно быть равно `(($0 + $1) - 1)`.
|
||||
* Интерфейс/тесты [BigIntegerListExpression](java/expression/BigIntegerListExpression.java).
|
||||
* *BigDecimalList* (38, 39). Дополнительно реализуйте поддержку вычисления
|
||||
выражений в типе `BigDecimal` с позиционными переменными.
|
||||
* Конструктор позиционной переменной получает индекс переменной.
|
||||
* При выводе позиционная переменная должна иметь вид `$index`.
|
||||
* Метод вычисления выражения должен называться `evaluateBd`,
|
||||
ему передаётся список значений переменных.
|
||||
* Например, для `expr = new Subtract(new Add(new Variable(0), new Variable(1)), new Const(BigDecimal.ONE))`:
|
||||
* `expr.evaluateBd(List.of(BigDecimal.TWO, BigDecimal.THREE))` должно быть равно 4;
|
||||
* `expr.toString()` должно быть равно `(($0 + $1) - 1)`.
|
||||
* Интерфейс/тесты [BigDecimalListExpression](java/expression/BigDecimalListExpression.java).
|
||||
* *List* (34, 35). Дополнительно реализуйте поддержку выражений вычисления
|
||||
выражений с позиционными переменными.
|
||||
* Конструктор позиционной переменной получает индекс переменной.
|
||||
* При вычислении выражения передаётся список значений переменных.
|
||||
* При выводе позиционная переменная должна иметь вид `$index`.
|
||||
* Например, для `expr = new Subtract(new Add(new Variable(0), new Variable(1)), new Const(1))`:
|
||||
* `expr.evaluate(List.of(2, 3))` должно быть равно 4;
|
||||
* `expr.toString()` должно быть равно `(($0 + $1) - 1)`.
|
||||
* Интерфейс/тесты [ListExpression](java/expression/ListExpression.java).
|
||||
|
||||
|
||||
## Домашнее задание 11. Игра m,n,k
|
||||
|
||||
Решение должно находиться в пакете `game`.
|
||||
|
||||
Модификации
|
||||
* *Base*
|
||||
* Тестов не существует, так как они зависят от вашего кода.
|
||||
* *Двукруговой турнир* (32-39)
|
||||
* Добавьте поддержку двукругового турнира для нескольких участников.
|
||||
* В рамках турнира каждый с каждым должен сыграть две партии,
|
||||
по одной каждым цветом.
|
||||
* Выведите таблицу очков по схеме:
|
||||
* 3 очка за победу;
|
||||
* 1 очко за ничью;
|
||||
* 0 очков за поражение.
|
||||
* *Гекс* (36-39)
|
||||
* Добавьте поддержку ромбической доски для
|
||||
[игры Гекс](https://ru.wikipedia.org/wiki/Гекс)
|
||||
(с тремя направлениями линий).
|
||||
* В качестве примера, сделайте доску размером 11×11.
|
||||
* *Дополнительные ходы* (34-37)
|
||||
* Если в результате хода игрока на доске появляется новая последовательность
|
||||
из 4+ одинаковых символов, то он делает дополнительный ход.
|
||||
* Игрок не может сделать несколько дополнительных ходов подряд.
|
||||
* *Multiplayer* (38, 39)
|
||||
* Добавьте возможность игры для 3 и 4 игроков (значки `@` и `#`);
|
||||
* Если игрок проигрывает из-за ошибочного хода,
|
||||
то игра продолжается без него.
|
||||
Если остался только один игрок, он объявляется победителем.
|
||||
-->
|
||||
|
||||
## Домашнее задание 9. Markdown to HTML [](https://git.fymio.us/me/prog-intro-2025/actions)
|
||||
|
||||
Модификации
|
||||
* *Base* ✅
|
||||
* [Исходный код тестов](java/md2html/Md2HtmlTester.java)
|
||||
* [Откомпилированные тесты](artifacts/Md2HtmlTest.jar)
|
||||
* Аргументы командной строки: модификации
|
||||
* *3637*
|
||||
* Добавьте поддержку
|
||||
`<<вставок>>`: `<ins>вставок</ins>` и
|
||||
`}}удалений{{`: `<del>удалений</del>`
|
||||
* *3839*
|
||||
* Добавьте поддержку
|
||||
\`\`\``кода __без__ форматирования`\`\`\`:
|
||||
`<pre>кода __без__ форматирования</pre>`
|
||||
* *3233*
|
||||
* Добавьте поддержку `%переменных%%`: `<var>переменных</var>`
|
||||
* *3435*
|
||||
* Добавьте поддержку `!!примеров!!`: `<samp>примеров</samp>`
|
||||
|
||||
|
||||
## Домашнее задание 7. Разметка [](https://git.fymio.us/me/prog-intro-2025/actions)
|
||||
|
||||
Модификации
|
||||
* *Base* ✅
|
||||
* Исходный код тестов:
|
||||
* [MarkupTester.java](java/markup/MarkupTester.java)
|
||||
* [MarkupTest.java](java/markup/MarkupTest.java)
|
||||
* Аргументы командной строки: модификации
|
||||
* Откомпилированных тестов не существует,
|
||||
так как они зависят от вашего кода
|
||||
* *3637*, *3839*, *4142*, *4749* ✅
|
||||
* Дополнительно реализуйте метод `toTex`, генерирующий TeX-разметку:
|
||||
* Абзацы предваряются командой `\par{}`
|
||||
* Выделенный текст заключается в `\emph{` и `}`
|
||||
* Сильно выделенный текст заключается в `\textbf{` и `}`
|
||||
* Зачеркнутый текст заключается в `\textst{` и `}`
|
||||
* Добавьте поддержку:
|
||||
* Нумерованных списков (класс `OrderedList`, окружение `enumerate`): последовательность элементов
|
||||
* Ненумерованных списков (класс `UnorderedList`, окружение `itemize`): последовательность элементов
|
||||
* Элементов списка (класс `ListItem`, тег `\item`: последовательность абзацев и списков
|
||||
* Для новых классов поддержка Markdown не требуется
|
||||
* [Исходный код тестов](java/markup/MarkupListTest.java)
|
||||
* *3233*, *3435* ✅
|
||||
* Дополнительно реализуйте метод `toHtml`, генерирующий HTML-разметку:
|
||||
* Абзацы окружаются тегом `p`
|
||||
* Выделенный текст окружается тегом `em`
|
||||
* Сильно выделенный текст окружается тегом `strong`
|
||||
* Зачеркнутый текст окружается тегом `s`
|
||||
|
||||
|
||||
## Домашнее задание 6. Подсчет слов++ [](https://git.fymio.us/me/prog-intro-2025/actions)
|
||||
|
||||
Модификации
|
||||
* *Base* ✅
|
||||
* Класс должен иметь имя `Wspp`
|
||||
* Исходный код тестов:
|
||||
[WsppTest.java](java/wspp/WsppTest.java),
|
||||
[WsppTester.java](java/wspp/WsppTester.java)
|
||||
* Откомпилированные тесты: [WsppTest.jar](artifacts/WsppTest.jar)
|
||||
* Аргументы командной строки: модификации
|
||||
* *3637* ✅
|
||||
* В выходном файле слова должны быть упорядочены
|
||||
по возрастанию длины, а при равной длине –
|
||||
по порядку первого вхождения во входной файл
|
||||
* Вместо всех вхождений в файле надо указывать
|
||||
только последнее вхождение в строке
|
||||
* В словах могут дополнительно встречаться
|
||||
цифры и символы `$` и `_`
|
||||
* Класс должен иметь имя `WsppLast`
|
||||
* *3839* ✅
|
||||
* В выходном файле слова должны быть упорядочены
|
||||
по возрастанию длины, а при равной длине –
|
||||
по порядку первого вхождения во входной файл
|
||||
* Вместо всех вхождений в файле надо указывать
|
||||
только среднее вхождение строке
|
||||
* В словах могут дополнительно встречаться
|
||||
цифры и символы `$` и `_`
|
||||
* Класс должен иметь имя `WsppMiddle`
|
||||
* *3435* ✅
|
||||
* В выходном файле слова должны быть упорядочены
|
||||
по возрастанию длины, а при равной длине –
|
||||
по порядку первого вхождения во входной файл
|
||||
* Вместо номеров вхождений во всем файле надо указывать
|
||||
`<номер строки>:<номер вхождения>`,
|
||||
где номер вхождения считается с конца файла
|
||||
* В словах могут дополнительно встречаться
|
||||
цифры и символы `$` и `_`
|
||||
* Класс должен иметь имя `WsppPosition`
|
||||
* *3233* ✅
|
||||
* В выходном файле слова должны быть упорядочены
|
||||
в порядке вхождения во входной файл
|
||||
* Вместо номеров вхождений во всем файле надо указывать
|
||||
`<номер строки>:<номер вхождения>`,
|
||||
где номер вхождения считается с конца файла
|
||||
* В словах могут дополнительно встречаться
|
||||
цифры и символы `$` и `_`
|
||||
* Класс должен иметь имя `WsppPos`
|
||||
* *4142* ✅
|
||||
* В выходном файле слова должны быть упорядочены
|
||||
по возрастанию длины, а при равной длине –
|
||||
по порядку первого вхождения во входной файл
|
||||
* Вместо всех вхождений в файле надо указывать
|
||||
только последнее вхождение в строке
|
||||
* В словах могут дополнительно встречаться
|
||||
цифры и символы `$` и `_`
|
||||
* Класс должен иметь имя `WsppLast`
|
||||
* *4749* ✅
|
||||
* В выходном файле слова должны быть упорядочены
|
||||
по возрастанию длины, а при равной длине –
|
||||
по порядку первого вхождения во входной файл
|
||||
* Вместо номеров вхождений во всем файле надо указывать
|
||||
`<номер строки>:<номер вхождения>`,
|
||||
где номер вхождения считается с конца файла
|
||||
* В словах могут дополнительно встречаться
|
||||
цифры и символы `$` и `_`
|
||||
* Класс должен иметь имя `WsppPosition`
|
||||
|
||||
<!--
|
||||
## Домашнее задание 5. Свой сканнер
|
||||
|
||||
|
||||
Модификации
|
||||
* *Base*
|
||||
* Исходный код тестов: [FastReverseTest.java](java/reverse/FastReverseTest.java)
|
||||
* Откомпилированные тесты: [FastReverseTest.jar](artifacts/FastReverseTest.jar)
|
||||
* Аргументы командной строки: модификации
|
||||
* *3637*
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите минимум из чисел,
|
||||
находящихся в его столбце в последующих строках, и его самого
|
||||
* Во вводе могут быть десятичные и восьмиричные числа
|
||||
* Числа дополнительно могут разделяться
|
||||
[открывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#START_PUNCTUATION)
|
||||
и [закрывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#END_PUNCTUATION)
|
||||
скобками
|
||||
* Класс должен иметь имя `ReverseMinC`
|
||||
* *3839*
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите минимум из чисел
|
||||
текущее число — правый нижний угол матрицы
|
||||
* Во вводе могут быть десятичные и восьмиричные числа
|
||||
* Числа дополнительно могут разделяться
|
||||
[открывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#START_PUNCTUATION)
|
||||
и [закрывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#END_PUNCTUATION)
|
||||
скобками
|
||||
* Класс должен иметь имя `ReverseMin`
|
||||
* *3435*
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
выведите ее поворот по часовой стрелке
|
||||
* Числа дополнительно могут разделяться
|
||||
[открывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#START_PUNCTUATION)
|
||||
и [закрывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#END_PUNCTUATION)
|
||||
скобками
|
||||
* Класс должен иметь имя `ReverseRotate`
|
||||
* *3233*
|
||||
* Выведите (в реверсивном порядке) только числа,
|
||||
у которых сумма номеров строки и столбца четная
|
||||
* Числа дополнительно могут разделяться
|
||||
[открывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#START_PUNCTUATION)
|
||||
и [закрывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#END_PUNCTUATION)
|
||||
скобками
|
||||
* Класс должен иметь имя `ReverseEven`
|
||||
* *4142*
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите среднее из чисел в его столбце и строке
|
||||
* Числа дополнительно могут разделяться
|
||||
[открывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#START_PUNCTUATION)
|
||||
и [закрывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#END_PUNCTUATION)
|
||||
скобками
|
||||
* Класс должен иметь имя `ReverseAvg`
|
||||
* *4749*
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите сумму чиселв его столбце и строке
|
||||
* Числа дополнительно могут разделяться
|
||||
[открывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#START_PUNCTUATION)
|
||||
и [закрывающими](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#END_PUNCTUATION)
|
||||
скобками
|
||||
* Класс должен иметь имя `ReverseSum`
|
||||
-->
|
||||
|
||||
## Домашнее задание 4. Подсчет слов [](https://git.fymio.us/me/prog-intro-2025/actions)
|
||||
|
||||
|
||||
Модификации
|
||||
* *Base* ✅
|
||||
* Класс должен иметь имя `WordStat`
|
||||
* Исходный код тестов:
|
||||
[WordStatTest.java](java/wordStat/WordStatTest.java),
|
||||
[WordStatTester.java](java/wordStat/WordStatTester.java),
|
||||
[WordStatChecker.java](java/wordStat/WordStatChecker.java)
|
||||
* Откомпилированные тесты: [WordStatTest.jar](artifacts/WordStatTest.jar)
|
||||
* Аргументы командной строки: модификации
|
||||
* *FastSort* ✅
|
||||
* Пусть _n_ – число слов во входном файле,
|
||||
тогда программа должна работать за O(_n_ log _n_).
|
||||
* *3637* ✅
|
||||
* Назовём _серединой слова_ подстроку, полученную удалением
|
||||
первых и последних 3 символов слова.
|
||||
Слова длины меньшей 7 игнорируются.
|
||||
* Выходной файл должен содержать все различные
|
||||
середины слов, встречающихся во входном файле,
|
||||
упорядоченные по возрастанию длины (при равенстве – по первому вхождению).
|
||||
* Класс должен иметь имя `WordStatLengthMiddle`
|
||||
* *3839* ✅
|
||||
* Назовём _аффиксами слова_
|
||||
его префикс и суффикс длины `n / 2`, где `n` — длина слова.
|
||||
Слова длины один игнорируются.
|
||||
* Выходной файл должен содержать все различные
|
||||
аффиксы слов, встречающихся во входном файле,
|
||||
упорядоченные по возрастанию длины (при равенстве – по первому вхождению).
|
||||
* Класс должен иметь имя `WordStatLengthAffix`
|
||||
* *3435* ✅
|
||||
* Назовём _суффиксом слова_ подстроку,
|
||||
состоящую из `n / 2` последних символов слова, где `n` — длина слова.
|
||||
Слова длины один игнорируются.
|
||||
* Выходной файл должен содержать все различные
|
||||
суффиксы слов, встречающихся во входном файле,
|
||||
упорядоченные по возрастанию длины (при равенстве – по первому вхождению).
|
||||
* Класс должен иметь имя `WordStatLengthSuffix`
|
||||
* *3233* ✅
|
||||
* Выходной файл должен содержать все различные
|
||||
слова встречающиеся во входном файле,
|
||||
упорядоченные по возрастанию длины (при равенстве – по первому вхождению).
|
||||
* Класс должен иметь имя `WordStatLength`
|
||||
* *4142* ✅
|
||||
* Назовём _серединой слова_ подстроку, полученную удалением
|
||||
первых и последних 3 символов слова.
|
||||
Слова длины меньшей 7 игнорируются.
|
||||
* Выходной файл должен содержать все различные
|
||||
середины слов, встречающихся во входном файле,
|
||||
упорядоченные по возрастанию длины (при равенстве – по первому вхождению).
|
||||
* Класс должен иметь имя `WordStatLengthMiddle`
|
||||
* *4749* ✅
|
||||
* Назовём _префиксом слова_ подстроку,
|
||||
состоящую из `n / 2` первых символов слова, где `n` — длина слова.
|
||||
Слова длины один игнорируются.
|
||||
* Выходной файл должен содержать все различные
|
||||
префиксы слов, встречающихся во входном файле,
|
||||
упорядоченные по возрастанию длины (при равенстве – по первому вхождению).
|
||||
* Класс должен иметь имя `WordStatLengthPrefix`
|
||||
|
||||
## Домашнее задание 3. Реверс [](https://git.fymio.us/me/prog-intro-2025/actions)
|
||||
|
||||
|
||||
Модификации
|
||||
* *Base* ✅
|
||||
* Исходный код тестов:
|
||||
[ReverseTest.java](java/reverse/ReverseTest.java),
|
||||
[ReverseTester.java](java/reverse/ReverseTester.java)
|
||||
* Откомпилированные тесты: [ReverseTest.jar](artifacts/ReverseTest.jar)
|
||||
* Аргументы командной строки: модификации
|
||||
* *Memory* ✅
|
||||
* Программа должна сначала считывать все данные в память,
|
||||
и только потом обрабатывать их.
|
||||
* Пусть _M_ – объём памяти, необходимый для сохранения ввода
|
||||
в двумерном массиве `int` минимального размера.
|
||||
Ваша программа должна использовать не более 4_M_ + 1024 байт памяти.
|
||||
* Накладные расходы на запуск вашей программы JVM не учитываются.
|
||||
* *3637* ✅
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите максимум из чисел,
|
||||
находящихся в его столбце в последующих строках, и его самого
|
||||
* Класс должен иметь имя `ReverseMaxC`
|
||||
* *3839*
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите максимум из чисел
|
||||
текущее число — правый нижний угол матрицы
|
||||
* Класс должен иметь имя `ReverseMax`
|
||||
* *3435* ✅
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
выведите ее поворот по часовой стрелке, например для ввода
|
||||
```
|
||||
1 2 3 4
|
||||
5 6
|
||||
7 8 9
|
||||
```
|
||||
вывод должен быть
|
||||
```
|
||||
7 5 1
|
||||
8 6 2
|
||||
9 3
|
||||
4
|
||||
```
|
||||
* Класс должен иметь имя `ReverseRotate`
|
||||
* *3233* ✅
|
||||
* Выведите (в реверсивном порядке) только числа,
|
||||
у которых сумма номеров строки и столбца четная
|
||||
* Класс должен иметь имя `ReverseEven`
|
||||
* *4142* ✅
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите среднее из чисел в его столбце и строке
|
||||
* Класс должен иметь имя `ReverseAvg`
|
||||
* *4749* ✅
|
||||
* Рассмотрим входные данные как (не полностью определенную) матрицу,
|
||||
вместо каждого числа выведите сумму чиселв его столбце и строке
|
||||
* Класс должен иметь имя `ReverseSum`
|
||||
|
||||
## Домашнее задание 2. Сумма чисел [](https://git.fymio.us/me/prog-intro-2025/actions)
|
||||
|
||||
Модификации
|
||||
* *Base* ✅
|
||||
* Исходный код тестов:
|
||||
[SumTest.java](java/sum/SumTest.java),
|
||||
[SumTester.java](java/sum/SumTester.java),
|
||||
[базовые классы](java/base/)
|
||||
* Откомпилированные тесты: [SumTest.jar](artifacts/SumTest.jar)
|
||||
* Аргументы командной строки: модификации
|
||||
* *3637* ✅
|
||||
* Входные данные являются 64-битными числами в формате с плавающей точкой
|
||||
* На вход подаются десятичные и шестнадцатеричные числа
|
||||
* Шестнадцатеричные числа имеют префикс `0x`,
|
||||
например `0xa.bp2` равно (10+11/16)·4 равно 42.75
|
||||
* Ввод регистронезависим
|
||||
* Класс должен иметь имя `SumDoubleHex`
|
||||
* *3839* ✅
|
||||
* Входные данные помещаются в тип [BigDecimal](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/math/BigDecimal.html)
|
||||
* На вход подаются десятичные и шестнадцатеричные числа
|
||||
* Шестнадцатеричные числа имеют префикс `0x`,
|
||||
например `0xbsc` равно 11·10⁻¹²
|
||||
(мантисса и порядок являются целыми числами)
|
||||
* Ввод регистронезависим
|
||||
* Класс должен иметь имя `SumBigDecimalHex`
|
||||
* *3435* ✅
|
||||
* На вход подаются десятичные и шестнадцатеричные числа
|
||||
* Шестнадцатеричные числа имеют префикс `0x`
|
||||
* Ввод регистронезависим
|
||||
* Класс должен иметь имя `SumHex`
|
||||
* *3233* ✅
|
||||
* Входные данные являются 64-битными числами в формате с плавающей точкой
|
||||
* Класс должен иметь имя `SumDouble`
|
||||
* *4142* ✅
|
||||
* Входные данные помещаются в тип [BigInteger](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/math/BigInteger.html)
|
||||
* восьмеричные числа имеют суффикс `o`
|
||||
* Класс должен иметь имя `SumBigIntegerOctal`
|
||||
* *4749* ✅
|
||||
* Входные данные являются 64-битными целыми числами
|
||||
* восьмеричные числа имеют суффикс `o`
|
||||
* Класс должен иметь имя `SumLongOctal`
|
||||
<!--
|
||||
|
||||
|
||||
Для того, чтобы протестировать программу:
|
||||
|
||||
1. Скачайте откомпилированные тесты ([SumTest.jar](artifacts/SumTest.jar))
|
||||
1. Откомпилируйте `Sum.java`
|
||||
1. Проверьте, что создался `Sum.class`
|
||||
1. В каталоге, в котором находится `Sum.class`, выполните команду
|
||||
```
|
||||
java -ea -jar <путь к SumTest.jar> Base
|
||||
```
|
||||
* Например, если `SumTest.jar` находится в текущем каталоге, выполните команду
|
||||
```
|
||||
java -ea -jar SumTest.jar Base
|
||||
```
|
||||
1. Для ускорения отладки рекомендуется сделать скрипт, выполняющий шаги 2−4.
|
||||
|
||||
|
||||
## Домашнее задание 1. Запусти меня!
|
||||
|
||||
Модификации
|
||||
* *RunMe*
|
||||
1. Скачайте исходный код [RunMe.java](java/RunMe.java).
|
||||
1. Создайте скрипт, компилирующий и запускающий `RunMe` из командной строки
|
||||
с выданными вам аргументами командной строки.
|
||||
1. Следуйте выведенной инструкции.
|
||||
|
||||
Рекомендации по выполнению модификации
|
||||
|
||||
1. Проверьте версию Java:
|
||||
1. Запустите `javac --version` и проверьте, что версия
|
||||
находится в диапазоне 21..24.
|
||||
1. Запустите `java --version` и проверьте, что версия
|
||||
такая же как и у `javac`.
|
||||
1. Скачайте [RunMe.java](java/RunMe.java)
|
||||
1. Откомпилируйте `RunMe.java`:
|
||||
1. Запустите `javac RunMe.java`
|
||||
1. Убедитесь, что компиляция завершилась без ошибок
|
||||
1. Проверьте, что появился `RunMe.class`
|
||||
1. Запустите `RunMe`:
|
||||
1. Запустите `java RunMe [шесть] [слов] [пароля] [пришедшего] [на] [email]`
|
||||
1. При правильном исполнении вы должны получить ссылку.
|
||||
Если получено сообщение об ошибке — исправьте её и запустите повторно
|
||||
1. Зайдите по полученной ссылке и убедитесь, что она правильная
|
||||
1. Напишите и протестируйте скрипт:
|
||||
1. Напишите скрипт, включающий команды компиляции и запуска.
|
||||
Если вы не умеете писать скрипты, воспользуйтесь одной из инструкций:
|
||||
[Windows](https://tutorialreference.com/batch-scripting/batch-script-files),
|
||||
[Linux](https://www.freecodecamp.org/news/shell-scripting-crash-course-how-to-write-bash-scripts-in-linux/),
|
||||
[macOS](https://rowannicholls.github.io/bash/intro/myscript.html)
|
||||
1. Запустите и проверьте, что вы получили ту же ссылку, что и в предыдущем пункте
|
||||
1. Сдайте скрипт преподавателю
|
||||
1. Вы можете получить больше плюсиков, модифицируя код `RunMe.java`
|
||||
|
||||
-->
|
||||
BIN
artifacts/FastReverseTest.jar
Normal file
BIN
artifacts/FastReverseTest.jar
Normal file
Binary file not shown.
BIN
artifacts/Md2HtmlTest.jar
Normal file
BIN
artifacts/Md2HtmlTest.jar
Normal file
Binary file not shown.
BIN
artifacts/ReverseTest.jar
Normal file
BIN
artifacts/ReverseTest.jar
Normal file
Binary file not shown.
BIN
artifacts/SumTest.jar
Normal file
BIN
artifacts/SumTest.jar
Normal file
Binary file not shown.
BIN
artifacts/WordStatTest.jar
Normal file
BIN
artifacts/WordStatTest.jar
Normal file
Binary file not shown.
BIN
artifacts/WsppTest.jar
Normal file
BIN
artifacts/WsppTest.jar
Normal file
Binary file not shown.
550
java/RunMe.java
Normal file
550
java/RunMe.java
Normal file
@@ -0,0 +1,550 @@
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Run this code with provided arguments.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public final class RunMe {
|
||||
private RunMe() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
final byte[] password = parseArgs(args);
|
||||
|
||||
flag0(password);
|
||||
System.out.println("The first flag was low-hanging fruit, can you find others?");
|
||||
System.out.println("Try to read, understand and modify code in flagX(...) functions");
|
||||
|
||||
flag1(password);
|
||||
flag2(password);
|
||||
flag3(password);
|
||||
flag4(password);
|
||||
flag5(password);
|
||||
flag6(password);
|
||||
flag7(password);
|
||||
flag8(password);
|
||||
flag9(password);
|
||||
flag10(password);
|
||||
flag12(password);
|
||||
flag13(password);
|
||||
flag14(password);
|
||||
flag15(password);
|
||||
flag16(password);
|
||||
flag17(password);
|
||||
flag18(password);
|
||||
flag19(password);
|
||||
flag20(password);
|
||||
}
|
||||
|
||||
private static void flag0(final byte[] password) {
|
||||
// The result of print(...) function depends only on explicit arguments
|
||||
print(0, 0, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag1(final byte[] password) {
|
||||
while ("true".length() == 4) {
|
||||
}
|
||||
|
||||
print(1, -5204358702485720348L, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag2(final byte[] password) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < 300_000; i++) {
|
||||
for (int j = 0; j < 300_000; j++) {
|
||||
for (int k = 0; k < 300_000; k++) {
|
||||
result ^= (i * 7) | (j + k);
|
||||
result ^= result << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print(2, -3458723408232943523L, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag3(final byte[] password) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < 2025; i++) {
|
||||
for (int j = 0; j < 2025; j++) {
|
||||
for (int k = 0; k < 2025; k++) {
|
||||
for (int p = 0; p < 12; p++) {
|
||||
result ^= (i * 17) | (j + k * 7) & ~p;
|
||||
result ^= result << 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print(3, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag4(final byte[] password) {
|
||||
final long target = 8504327508437503432L + getInt(password);
|
||||
for (long i = 0; i < Long.MAX_VALUE; i++) {
|
||||
if ((i ^ (i >>> 32)) == target) {
|
||||
print(4, i, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package-private */ static final long PRIME = 2025_2025_07;
|
||||
|
||||
private static void flag5(final byte[] password) {
|
||||
final long n = 1_000_000_000_000_000L + getInt(password);
|
||||
|
||||
long result = 0;
|
||||
for (long i = 0; i < n; i++) {
|
||||
result = (result + i / 3 + i / 5 + i / 7 + i / 2025) % PRIME;
|
||||
}
|
||||
|
||||
print(5, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag6(final byte[] password) {
|
||||
/***
|
||||
\u002a\u002f\u0077\u0068\u0069\u006c\u0065\u0020\u0028\u0022\u0031\u0022
|
||||
\u002e\u006c\u0065\u006e\u0067\u0074\u0068\u0028\u0029\u0020\u003d\u003d
|
||||
\u0020\u0031\u0029\u003b\u0020\u0020\u006c\u006f\u006e\u0067\u0020\u0009
|
||||
\u0020\u0020\u0072\u0065\u0073\u0075\u006c\u0074\u0020\u003d\u0020\u000a
|
||||
\u0035\u0037\u0034\u0038\u0035\u0037\u0030\u0032\u0034\u0038\u0033\u004c
|
||||
\u002b\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u005b\u0035\u005d
|
||||
\u002b\u0070\u0061\u0073\u0073\u0077\u006f\u0072\u0064\u005b\u0032\u005d
|
||||
\u003b\u002f\u002a
|
||||
***/
|
||||
print(6, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag7(final byte[] password) {
|
||||
// Count the number of occurrences of the most frequent noun at the following page:
|
||||
// https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html
|
||||
|
||||
// The singular form of the most frequent noun
|
||||
final String singular = "";
|
||||
// The plural form of the most frequent noun
|
||||
final String plural = "";
|
||||
// The total number of occurrences
|
||||
final int total = 0;
|
||||
if (total != 0) {
|
||||
print(7, (singular + ":" + plural + ":" + total).hashCode(), password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void flag8(final byte[] password) {
|
||||
// Count the number of bluish (#5984A1) pixels of this image:
|
||||
// https://dev.java/assets/images/java-affinity-logo-icode-lg.png
|
||||
|
||||
final int number = 0;
|
||||
if (number != 0) {
|
||||
print(8, number, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final String PATTERN = "Reading the documentation can be surprisingly helpful!";
|
||||
private static final int SMALL_REPEAT_COUNT = 10_000_000;
|
||||
|
||||
private static void flag9(final byte[] password) {
|
||||
String repeated = "";
|
||||
for (int i = 0; i < SMALL_REPEAT_COUNT; i++) {
|
||||
repeated += PATTERN;
|
||||
}
|
||||
|
||||
print(9, repeated.hashCode(), password);
|
||||
}
|
||||
|
||||
|
||||
private static final long LARGE_REPEAT_SHIFT = 29;
|
||||
private static final long LARGE_REPEAT_COUNT = 1L << LARGE_REPEAT_SHIFT;
|
||||
|
||||
private static void flag10(final byte[] password) {
|
||||
String repeated = "";
|
||||
for (long i = 0; i < LARGE_REPEAT_COUNT; i++) {
|
||||
repeated += PATTERN;
|
||||
}
|
||||
|
||||
print(10, repeated.hashCode(), password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag11(final byte[] password) {
|
||||
print(11, 5823470598324780581L, password);
|
||||
}
|
||||
|
||||
|
||||
private static void flag12(final byte[] password) {
|
||||
final BigInteger year = BigInteger.valueOf(-2025);
|
||||
final BigInteger term = BigInteger.valueOf(PRIME + Math.abs(getInt(password)) % PRIME);
|
||||
|
||||
final long result = Stream.iterate(BigInteger.ZERO, BigInteger.ONE::add)
|
||||
.filter(i -> year.multiply(i).add(term).multiply(i).compareTo(BigInteger.TEN) > 0)
|
||||
.mapToLong(i -> i.longValue() * password[i.intValue() % password.length])
|
||||
.sum();
|
||||
|
||||
print(12, result, password);
|
||||
}
|
||||
|
||||
|
||||
private static final long MAX_DEPTH = 100_000_000L;
|
||||
|
||||
private static void flag13(final byte[] password) {
|
||||
try {
|
||||
flag13(password, 0, 0);
|
||||
} catch (final StackOverflowError e) {
|
||||
System.err.println("Stack overflow :((");
|
||||
}
|
||||
}
|
||||
|
||||
private static void flag13(final byte[] password, final long depth, final long result) {
|
||||
if (depth < MAX_DEPTH) {
|
||||
flag13(password, depth + 1, (result ^ PRIME) | (result << 2) + depth * 17);
|
||||
} else {
|
||||
print(13, result, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void flag14(final byte[] password) {
|
||||
final Instant today = Instant.parse("2025-09-09T12:00:00Z");
|
||||
final BigInteger hours = BigInteger.valueOf(Duration.between(Instant.EPOCH, today).toHours() + password[1] + password[3]);
|
||||
|
||||
final long result = Stream.iterate(BigInteger.ZERO, hours::add)
|
||||
.reduce(BigInteger.ZERO, BigInteger::add)
|
||||
.longValue();
|
||||
|
||||
print(14, result, password);
|
||||
}
|
||||
|
||||
private static void flag15(final byte[] password) {
|
||||
// REDACTED
|
||||
}
|
||||
|
||||
private static void flag16(final byte[] password) {
|
||||
byte[] a = {
|
||||
(byte) (password[0] + password[3]),
|
||||
(byte) (password[1] + password[4]),
|
||||
(byte) (password[2] + password[5])
|
||||
};
|
||||
|
||||
for (long i = 1_000_000_000_000_000_000L + getInt(password); i >= 0; i--) {
|
||||
flag16Update(a);
|
||||
}
|
||||
|
||||
print(16, flag16Result(a), password);
|
||||
}
|
||||
|
||||
/* package-private */ static void flag16Update(byte[] a) {
|
||||
a[0] ^= a[1];
|
||||
a[1] -= a[2] | a[0];
|
||||
a[2] *= a[0];
|
||||
}
|
||||
|
||||
/* package-private */ static int flag16Result(byte[] a) {
|
||||
return (a[0] + " " + a[1] + " " + a[2]).hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Original idea by Alexei Shishkin.
|
||||
*/
|
||||
private static void flag17(final byte[] password) {
|
||||
final int n = Math.abs(getInt(password) % 2025) + 2025;
|
||||
print(17, calc17(n), password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write me
|
||||
* <pre>
|
||||
* 0: iconst_0
|
||||
* 1: istore_1
|
||||
* 2: iload_1
|
||||
* 3: iload_1
|
||||
* 4: imul
|
||||
* 5: sipush 2025
|
||||
* 8: idiv
|
||||
* 9: iload_0
|
||||
* 10: isub
|
||||
* 11: ifge 20
|
||||
* 14: iinc 1, 1
|
||||
* 17: goto 2
|
||||
* 20: iload_1
|
||||
* 21: ireturn
|
||||
* </pre>
|
||||
*/
|
||||
private static int calc17(final int n) {
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
private static void flag18(final byte[] password) {
|
||||
final int n = 2025 + getInt(password) % 2025;
|
||||
// Find the number of factors of n! modulo PRIME
|
||||
final int factors = 0;
|
||||
if (factors != 0) {
|
||||
print(18, factors, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void flag19(final byte[] password) {
|
||||
// Let n = 2025e25 + abs(getInt(password)).
|
||||
// Consider the sequence of numbers (n + i) ** 2.
|
||||
// Instead of each number, we write the number that is obtained from it by discarding the last 25 digits.
|
||||
// How many of the first numbers of the resulting sequence will form an arithmetic progression?
|
||||
final long result = 0;
|
||||
if (result != 0) {
|
||||
print(19, result, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Original idea by Dmitrii Liapin.
|
||||
*/
|
||||
private static void flag20(final byte[] password) {
|
||||
final Collection<Long> longs = new Random(getInt(password)).longs(2025_000)
|
||||
.map(n -> n % 1000)
|
||||
.boxed()
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
|
||||
// Calculate the number of objects (recursively) accessible by "longs" reference.
|
||||
final int result = 0;
|
||||
|
||||
if (result != 0) {
|
||||
print(20, result, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Original idea and implementation Igor Panasyuk.
|
||||
*/
|
||||
private static void flag21(final byte[] password) {
|
||||
record Pair(int x, int y) {
|
||||
}
|
||||
|
||||
final List<Object> items = new ArrayList<>(Arrays.asList(
|
||||
Byte.toUnsignedInt(password[0]),
|
||||
(long) getInt(password),
|
||||
new Pair(password[1], password[2]),
|
||||
"Java SE 21 " + Arrays.toString(password)
|
||||
));
|
||||
|
||||
for (int round = 0; round < 10; round++) {
|
||||
for (final Object item : List.copyOf(items)) {
|
||||
// TODO: complete the switch expression using Java 21 features:
|
||||
// items.add(
|
||||
// case Integer i -> square of i as long
|
||||
// case Long l and l is even -> l ^ 0x21L
|
||||
// case Long l and l is odd -> -l
|
||||
// case Pair(int x, int y) -> x << 8 ^ y
|
||||
// case String s -> s.hashCode()
|
||||
// default -> 0
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
long result = 0;
|
||||
for (final Object item : items) {
|
||||
result = result * 17 + item.toString().hashCode();
|
||||
}
|
||||
|
||||
print(21, result, password);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// You may ignore all code below this line.
|
||||
// It is not required to get any of the flags.
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
|
||||
private static void print(final int no, long result, final byte[] password) {
|
||||
System.out.format("flag %d: https://www.kgeorgiy.info/courses/prog-intro/hw1/%s%n", no, flag(result, password));
|
||||
}
|
||||
|
||||
/* package-private */ static String flag(long result, byte[] password) {
|
||||
final byte[] flag = password.clone();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
flag[i] ^= result;
|
||||
result >>>= 8;
|
||||
}
|
||||
|
||||
return flag(flag);
|
||||
}
|
||||
|
||||
/* package-private */ static String flag(final byte[] data) {
|
||||
final MessageDigest messageDigest = RunMe.DIGEST.get();
|
||||
messageDigest.update(SALT);
|
||||
messageDigest.update(data);
|
||||
messageDigest.update(SALT);
|
||||
final byte[] digest = messageDigest.digest();
|
||||
|
||||
return IntStream.range(0, 6)
|
||||
.map(i -> (((digest[i * 2] & 255) << 8) + (digest[i * 2 + 1] & 255)) % KEYWORDS.size())
|
||||
.mapToObj(KEYWORDS::get)
|
||||
.collect(Collectors.joining("-"));
|
||||
}
|
||||
|
||||
/* package-private */ static byte[] parseArgs(final String[] args) {
|
||||
if (args.length != 6) {
|
||||
throw error("Expected 6 command line arguments, found: %d", args.length);
|
||||
}
|
||||
|
||||
final byte[] bytes = new byte[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
final Byte value = VALUES.get(args[i].toLowerCase(Locale.US));
|
||||
if (value == null) {
|
||||
throw error("Expected keyword, found: %s", args[i]);
|
||||
}
|
||||
bytes[i] = value;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static AssertionError error(final String format, final Object... args) {
|
||||
System.err.format(format, args);
|
||||
System.err.println();
|
||||
System.exit(1);
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/* package-private */ static int getInt(byte[] password) {
|
||||
return IntStream.range(0, password.length)
|
||||
.map(i -> password[i])
|
||||
.reduce((a, b) -> a * KEYWORDS.size() + b)
|
||||
.getAsInt();
|
||||
}
|
||||
|
||||
private static final ThreadLocal<MessageDigest> DIGEST = ThreadLocal.withInitial(() -> {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("Cannot create SHA-256 digest", e);
|
||||
}
|
||||
});
|
||||
|
||||
public static final byte[] SALT = "fathdufimmonJiajFik3JeccafdaihoFrusthys9".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
private static final List<String> KEYWORDS = List.of(
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
"break",
|
||||
"byte",
|
||||
"case",
|
||||
"catch",
|
||||
"char",
|
||||
"class",
|
||||
"const",
|
||||
"new",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"return",
|
||||
"short",
|
||||
"static",
|
||||
"strictfp",
|
||||
"super",
|
||||
"for",
|
||||
"goto",
|
||||
"if",
|
||||
"implements",
|
||||
"import",
|
||||
"instanceof",
|
||||
"int",
|
||||
"interface",
|
||||
"long",
|
||||
"native",
|
||||
"continue",
|
||||
"default",
|
||||
"do",
|
||||
"double",
|
||||
"else",
|
||||
"enum",
|
||||
"extends",
|
||||
"final",
|
||||
"finally",
|
||||
"float",
|
||||
"switch",
|
||||
"synchronized",
|
||||
"this",
|
||||
"throw",
|
||||
"throws",
|
||||
"transient",
|
||||
"try",
|
||||
"void",
|
||||
"volatile",
|
||||
"while",
|
||||
"record",
|
||||
"Error",
|
||||
"AssertionError",
|
||||
"OutOfMemoryError",
|
||||
"StackOverflowError",
|
||||
"ArrayIndexOutOfBoundsException",
|
||||
"ArrayStoreException",
|
||||
"AutoCloseable",
|
||||
"Character",
|
||||
"CharSequence",
|
||||
"ClassCastException",
|
||||
"Comparable",
|
||||
"Exception",
|
||||
"IllegalArgumentException",
|
||||
"IllegalStateException",
|
||||
"IndexOutOfBoundsException",
|
||||
"Integer",
|
||||
"Iterable",
|
||||
"Math",
|
||||
"Module",
|
||||
"NegativeArraySizeException",
|
||||
"NullPointerException",
|
||||
"Number",
|
||||
"NumberFormatException",
|
||||
"Object",
|
||||
"Override",
|
||||
"RuntimeException",
|
||||
"StrictMath",
|
||||
"String",
|
||||
"StringBuilder",
|
||||
"StringIndexOutOfBoundsException",
|
||||
"SuppressWarnings",
|
||||
"System",
|
||||
"Thread",
|
||||
"Throwable",
|
||||
"ArithmeticException",
|
||||
"ClassLoader",
|
||||
"ClassNotFoundException",
|
||||
"Cloneable",
|
||||
"Deprecated",
|
||||
"FunctionalInterface",
|
||||
"InterruptedException",
|
||||
"Process",
|
||||
"ProcessBuilder",
|
||||
"Runnable",
|
||||
"SafeVarargs",
|
||||
"StackTraceElement",
|
||||
"Runtime",
|
||||
"ThreadLocal",
|
||||
"UnsupportedOperationException"
|
||||
);
|
||||
|
||||
private static final Map<String, Byte> VALUES = IntStream.range(0, KEYWORDS.size())
|
||||
.boxed()
|
||||
.collect(Collectors.toMap(index -> KEYWORDS.get(index).toLowerCase(Locale.US), Integer::byteValue));
|
||||
}
|
||||
84
java/base/Asserts.java
Normal file
84
java/base/Asserts.java
Normal file
@@ -0,0 +1,84 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static void assertSame(final String message, final Object expected, final Object actual) {
|
||||
assertTrue(String.format("%s: expected same objects: %s and %s", message, expected, actual), expected == actual);
|
||||
}
|
||||
|
||||
public static void checkAssert(final Class<?> c) {
|
||||
if (!c.desiredAssertionStatus()) {
|
||||
throw error("You should enable assertions by running 'java -ea %s'", c.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static AssertionError error(final String format, final Object... args) {
|
||||
final String message = String.format(format, args);
|
||||
return args.length > 0 && args[args.length - 1] instanceof Throwable
|
||||
? new AssertionError(message, (Throwable) args[args.length - 1])
|
||||
: new AssertionError(message);
|
||||
}
|
||||
|
||||
public static void printStackTrace(final String message) {
|
||||
new Exception(message).printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
20
java/base/BaseChecker.java
Normal file
20
java/base/BaseChecker.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class BaseChecker {
|
||||
protected final TestCounter counter;
|
||||
|
||||
protected BaseChecker(final TestCounter counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
public ExtendedRandom random() {
|
||||
return counter.random();
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
return counter.mode();
|
||||
}
|
||||
}
|
||||
95
java/base/Either.java
Normal file
95
java/base/Either.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package base;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public interface Either<L, R> {
|
||||
<NR> Either<L, NR> mapRight(final Function<? super R, NR> f);
|
||||
<NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f);
|
||||
<T> T either(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
|
||||
|
||||
boolean isRight();
|
||||
|
||||
L getLeft();
|
||||
R getRight();
|
||||
|
||||
static <L, R> Either<L, R> right(final R value) {
|
||||
return new Either<>() {
|
||||
@Override
|
||||
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||
return right(f.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||
return f.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||
return rf.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRight() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getRight() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Right(%s)", value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <L, R> Either<L, R> left(final L value) {
|
||||
return new Either<>() {
|
||||
@Override
|
||||
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
|
||||
return left(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
|
||||
return left(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
|
||||
return lf.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRight() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public L getLeft() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getRight() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Left(%s)", value);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
89
java/base/ExtendedRandom.java
Normal file
89
java/base/ExtendedRandom.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package base;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ExtendedRandom {
|
||||
public static final String ENGLISH = "abcdefghijklmnopqrstuvwxyz";
|
||||
public static final String RUSSIAN = "абвгдеежзийклмнопрстуфхцчшщъыьэюя";
|
||||
public static final String GREEK = "αβγŋδεζηθικλμνξοπρτυφχψω";
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public static final String SPACES = " \t\n\u000B\u2029\f";
|
||||
|
||||
private final Random random;
|
||||
|
||||
public ExtendedRandom(final Random random) {
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
public ExtendedRandom(final Class<?> owner) {
|
||||
this(new Random(7912736473497634913L + owner.getName().hashCode()));
|
||||
}
|
||||
|
||||
public String randomString(final String chars) {
|
||||
return randomChar(chars) + (random.nextBoolean() ? "" : randomString(chars));
|
||||
}
|
||||
|
||||
public char randomChar(final String chars) {
|
||||
return chars.charAt(nextInt(chars.length()));
|
||||
}
|
||||
|
||||
public String randomString(final String chars, final int length) {
|
||||
final StringBuilder string = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
string.append(randomChar(chars));
|
||||
}
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
public String randomString(final String chars, final int minLength, final int maxLength) {
|
||||
return randomString(chars, nextInt(minLength, maxLength));
|
||||
}
|
||||
|
||||
public boolean nextBoolean() {
|
||||
return random.nextBoolean();
|
||||
}
|
||||
|
||||
public int nextInt() {
|
||||
return random.nextInt();
|
||||
}
|
||||
|
||||
public int nextInt(final int min, final int max) {
|
||||
return nextInt(max - min + 1) + min;
|
||||
}
|
||||
|
||||
public int nextInt(final int n) {
|
||||
return random.nextInt(n);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final <T> T randomItem(final T... items) {
|
||||
return items[nextInt(items.length)];
|
||||
}
|
||||
|
||||
public <T> T randomItem(final List<T> items) {
|
||||
return items.get(nextInt(items.size()));
|
||||
}
|
||||
|
||||
public Random getRandom() {
|
||||
return random;
|
||||
}
|
||||
|
||||
public <T> List<T> random(final int list, final Function<ExtendedRandom, T> generator) {
|
||||
return Stream.generate(() -> generator.apply(this)).limit(list).toList();
|
||||
}
|
||||
|
||||
public double nextDouble() {
|
||||
return random.nextDouble();
|
||||
}
|
||||
|
||||
public <E> void shuffle(final List<E> all) {
|
||||
Collections.shuffle(all, random);
|
||||
}
|
||||
}
|
||||
92
java/base/Functional.java
Normal file
92
java/base/Functional.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Functional {
|
||||
private Functional() {}
|
||||
|
||||
public static <T, R> List<R> map(final Collection<T> items, final Function<? super T, ? extends R> f) {
|
||||
return items.stream().map(f).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(final List<T> items, final BiFunction<? super Integer, ? super T, ? extends R> f) {
|
||||
return IntStream.range(0, items.size())
|
||||
.mapToObj(i -> f.apply(i, items.get(i)))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
public static <K, T, R> Map<K, R> mapValues(final Map<K, T> map, final Function<T, R> f) {
|
||||
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue())));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <K, T> Map<K, T> mergeMaps(final Map<K, T>... maps) {
|
||||
return Stream.of(maps).flatMap(m -> m.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> List<T> concat(final Collection<? extends T>... items) {
|
||||
final List<T> result = new ArrayList<>();
|
||||
for (final Collection<? extends T> item : items) {
|
||||
result.addAll(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> append(final Collection<T> collection, final T item) {
|
||||
final List<T> list = new ArrayList<>(collection);
|
||||
list.add(item);
|
||||
return list;
|
||||
}
|
||||
|
||||
public static <T> List<List<T>> allValues(final List<T> vals, final int length) {
|
||||
return Stream.generate(() -> vals)
|
||||
.limit(length)
|
||||
.reduce(
|
||||
List.of(List.of()),
|
||||
(prev, next) -> next.stream()
|
||||
.flatMap(value -> prev.stream().map(list -> append(list, value)))
|
||||
.toList(),
|
||||
(prev, next) -> next.stream()
|
||||
.flatMap(suffix -> prev.stream().map(prefix -> concat(prefix, suffix)))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
public static <K, V> V get(final Map<K, V> map, final K key) {
|
||||
final V result = map.get(key);
|
||||
if (result == null) {
|
||||
throw new NullPointerException(key.toString() + " in " + map(map.keySet(), Objects::toString));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void addRange(final List<Integer> values, final int d, final int c) {
|
||||
for (int i = -d; i <= d; i++) {
|
||||
values.add(c + i);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void forEachPair(final T[] items, final BiConsumer<? super T, ? super T> consumer) {
|
||||
assert items.length % 2 == 0;
|
||||
IntStream.range(0, items.length / 2).forEach(i -> consumer.accept(items[i * 2], items[i * 2 + 1]));
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<Pair<T, T>> toPairs(final T[] items) {
|
||||
assert items.length % 2 == 0;
|
||||
return IntStream.range(0, items.length / 2)
|
||||
.mapToObj(i -> Pair.of(items[i * 2], items[i * 2 + 1]))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
56
java/base/Log.java
Normal file
56
java/base/Log.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package base;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Log {
|
||||
private final Pattern LINES = Pattern.compile("\n");
|
||||
private int indent;
|
||||
|
||||
public static Supplier<Void> action(final Runnable action) {
|
||||
return () -> {
|
||||
action.run();
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public void scope(final String name, final Runnable action) {
|
||||
scope(name, action(action));
|
||||
}
|
||||
|
||||
public <T> T scope(final String name, final Supplier<T> action) {
|
||||
println(name);
|
||||
indent++;
|
||||
try {
|
||||
return silentScope(name, action);
|
||||
} finally {
|
||||
indent--;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T silentScope(final String ignoredName, final Supplier<T> action) {
|
||||
return action.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public void println(final Object value) {
|
||||
for (final String line : LINES.split(String.valueOf(value))) {
|
||||
System.out.println(indent() + line);
|
||||
}
|
||||
}
|
||||
|
||||
public void format(final String format, final Object... args) {
|
||||
println(String.format(format,args));
|
||||
}
|
||||
|
||||
private String indent() {
|
||||
return " ".repeat(indent);
|
||||
}
|
||||
|
||||
protected int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
}
|
||||
28
java/base/MainChecker.java
Normal file
28
java/base/MainChecker.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package base;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
|
||||
public final class MainChecker {
|
||||
private final Runner runner;
|
||||
|
||||
public MainChecker(final Runner runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
public List<String> run(final TestCounter counter, final String... input) {
|
||||
return runner.run(counter, input);
|
||||
}
|
||||
|
||||
public List<String> run(final TestCounter counter, final List<String> input) {
|
||||
return runner.run(counter, input);
|
||||
}
|
||||
|
||||
public void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||
runner.testEquals(counter, input, expected);
|
||||
}
|
||||
|
||||
}
|
||||
15
java/base/Named.java
Normal file
15
java/base/Named.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public record Named<T>(String name, T value) {
|
||||
public static <T> Named<T> of(final String name, final T f) {
|
||||
return new Named<>(name, f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
44
java/base/Pair.java
Normal file
44
java/base/Pair.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package base;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused"})
|
||||
public record Pair<F, S>(F first, S second) {
|
||||
public static <F, S> Pair<F, S> of(final F first, final S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
public static <F, S> Pair<F, S> of(final Map.Entry<F, S> e) {
|
||||
return of(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
public static <F, S> UnaryOperator<Pair<F, S>> lift(final UnaryOperator<F> f, final UnaryOperator<S> s) {
|
||||
return p -> of(f.apply(p.first), s.apply(p.second));
|
||||
}
|
||||
|
||||
public static <F, S> BinaryOperator<Pair<F, S>> lift(final BinaryOperator<F> f, final BinaryOperator<S> s) {
|
||||
return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second));
|
||||
}
|
||||
|
||||
public static <T, F, S> Function<T, Pair<F, S>> tee(
|
||||
final Function<? super T, ? extends F> f,
|
||||
final Function<? super T, ? extends S> s
|
||||
) {
|
||||
return t -> of(f.apply(t), s.apply(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + first + ", " + second + ")";
|
||||
}
|
||||
|
||||
public <R> Pair<F, R> second(final R second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
}
|
||||
185
java/base/Runner.java
Normal file
185
java/base/Runner.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package base;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@FunctionalInterface
|
||||
public interface Runner {
|
||||
List<String> run(final TestCounter counter, final List<String> input);
|
||||
|
||||
default List<String> run(final TestCounter counter, final String... input) {
|
||||
return run(counter, List.of(input));
|
||||
}
|
||||
|
||||
default void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
|
||||
counter.test(() -> {
|
||||
final List<String> actual = run(counter, input);
|
||||
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
|
||||
final String exp = expected.get(i);
|
||||
final String act = actual.get(i);
|
||||
if (!exp.equalsIgnoreCase(act)) {
|
||||
Asserts.assertEquals("Line " + (i + 1), exp, act);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Asserts.assertEquals("Number of lines", expected.size(), actual.size());
|
||||
});
|
||||
}
|
||||
|
||||
static Packages packages(final String... packages) {
|
||||
return new Packages(List.of(packages));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface CommentRunner {
|
||||
List<String> run(String comment, TestCounter counter, List<String> input);
|
||||
}
|
||||
|
||||
final class Packages {
|
||||
private final List<String> packages;
|
||||
|
||||
private Packages(final List<String> packages) {
|
||||
this.packages = packages;
|
||||
}
|
||||
|
||||
public Runner std(final String className) {
|
||||
final CommentRunner main = main(className);
|
||||
return (counter, input) -> counter.call("io", () -> {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (final PrintWriter writer = new PrintWriter(baos, false, StandardCharsets.UTF_8)) {
|
||||
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
java/base/Selector.java
Normal file
143
java/base/Selector.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Selector {
|
||||
private final Class<?> owner;
|
||||
private final List<String> modes;
|
||||
private final Set<String> variantNames = new LinkedHashSet<>();
|
||||
private final Map<String, Consumer<TestCounter>> variants = new LinkedHashMap<>();
|
||||
|
||||
public Selector(final Class<?> owner, final String... modes) {
|
||||
this.owner = owner;
|
||||
this.modes = List.of(modes);
|
||||
}
|
||||
|
||||
public Selector variant(final String name, final Consumer<TestCounter> operations) {
|
||||
Asserts.assertTrue("Duplicate variant " + name, variants.put(name.toLowerCase(), operations) == null);
|
||||
variantNames.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void check(final boolean condition, final String format, final Object... args) {
|
||||
if (!condition) {
|
||||
throw new IllegalArgumentException(String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseOfSystemOutOrSystemErr")
|
||||
public void main(final String... args) {
|
||||
try {
|
||||
final String mode;
|
||||
if (modes.isEmpty()) {
|
||||
check(args.length >= 1, "At least one argument expected, found %s", args.length);
|
||||
mode = "";
|
||||
} else {
|
||||
check(args.length >= 2, "At least two arguments expected, found %s", args.length);
|
||||
mode = args[0];
|
||||
}
|
||||
|
||||
final List<String> vars = Arrays.stream(args).skip(modes.isEmpty() ? 0 : 1)
|
||||
.flatMap(arg -> Arrays.stream(arg.split("[ +]+")))
|
||||
.toList();
|
||||
|
||||
test(mode, vars);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
if (modes.isEmpty()) {
|
||||
System.err.println("Usage: " + owner.getName() + " VARIANT...");
|
||||
} else {
|
||||
System.err.println("Usage: " + owner.getName() + " MODE VARIANT...");
|
||||
System.err.println("Modes: " + String.join(", ", modes));
|
||||
}
|
||||
System.err.println("Variants: " + String.join(", ", variantNames));
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void test(final String mode, List<String> vars) {
|
||||
final int modeNo = modes.isEmpty() ? -1 : modes.indexOf(mode) ;
|
||||
check(modes.isEmpty() || modeNo >= 0, "Unknown mode '%s'", mode);
|
||||
if (variantNames.contains("Base") && !vars.contains("Base")) {
|
||||
vars = new ArrayList<>(vars);
|
||||
vars.add(0, "Base");
|
||||
}
|
||||
|
||||
vars.forEach(variant -> check(variants.containsKey(variant.toLowerCase()), "Unknown variant '%s'", variant));
|
||||
|
||||
final Map<String, String> properties = modes.isEmpty()
|
||||
? Map.of("variant", String.join("+", vars))
|
||||
: Map.of("variant", String.join("+", vars), "mode", mode);
|
||||
final TestCounter counter = new TestCounter(owner, modeNo, properties);
|
||||
counter.printHead();
|
||||
vars.forEach(variant -> counter.scope("Testing " + variant, () -> variants.get(variant.toLowerCase()).accept(counter)));
|
||||
counter.printStatus();
|
||||
}
|
||||
|
||||
public static <V extends Tester> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final String... modes) {
|
||||
return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes);
|
||||
}
|
||||
|
||||
public static <V> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||
return new Composite<>(owner, factory, tester, modes);
|
||||
}
|
||||
|
||||
public List<String> getModes() {
|
||||
return modes.isEmpty() ? List.of("~") : modes;
|
||||
}
|
||||
|
||||
public List<String> getVariants() {
|
||||
return List.copyOf(variants.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public static final class Composite<V> {
|
||||
private final Selector selector;
|
||||
private final Function<TestCounter, V> factory;
|
||||
private final BiConsumer<V, TestCounter> tester;
|
||||
private List<Consumer<? super V>> base;
|
||||
|
||||
private Composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
|
||||
selector = new Selector(owner, modes);
|
||||
this.factory = factory;
|
||||
this.tester = tester;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final Composite<V> variant(final String name, final Consumer<? super V>... parts) {
|
||||
if ("Base".equalsIgnoreCase(name)) {
|
||||
base = List.of(parts);
|
||||
return v(name.toLowerCase());
|
||||
} else {
|
||||
return v(name, parts);
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private Composite<V> v(final String name, final Consumer<? super V>... parts) {
|
||||
selector.variant(name, counter -> {
|
||||
final V variant = factory.apply(counter);
|
||||
for (final Consumer<? super V> part : base) {
|
||||
part.accept(variant);
|
||||
}
|
||||
for (final Consumer<? super V> part : parts) {
|
||||
part.accept(variant);
|
||||
}
|
||||
tester.accept(variant, counter);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Selector selector() {
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
184
java/base/TestCounter.java
Normal file
184
java/base/TestCounter.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package base;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class TestCounter extends Log {
|
||||
public static final int DENOMINATOR = Integer.getInteger("base.denominator", 1);
|
||||
public static final int DENOMINATOR2 = (int) Math.round(Math.sqrt(DENOMINATOR));
|
||||
|
||||
private static final String JAR_EXT = ".jar";
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
|
||||
|
||||
private final Class<?> owner;
|
||||
private final int mode;
|
||||
private final Map<String, ?> properties;
|
||||
private final ExtendedRandom random;
|
||||
|
||||
private final long start = System.currentTimeMillis();
|
||||
private int passed;
|
||||
|
||||
public TestCounter(final Class<?> owner, final int mode, final Map<String, ?> properties) {
|
||||
Locale.setDefault(Locale.US);
|
||||
Asserts.checkAssert(getClass());
|
||||
|
||||
this.owner = owner;
|
||||
this.mode = mode;
|
||||
this.properties = properties;
|
||||
random = new ExtendedRandom(owner);
|
||||
}
|
||||
|
||||
public int mode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public int getTestNo() {
|
||||
return passed + 1;
|
||||
}
|
||||
|
||||
public void test(final Runnable action) {
|
||||
testV(() -> {
|
||||
action.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public <T> void testForEach(final Iterable<? extends T> items, final Consumer<? super T> action) {
|
||||
for (final T item : items) {
|
||||
test(() -> action.accept(item));
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T testV(final Supplier<T> action) {
|
||||
return silentScope("Test " + getTestNo(), () -> {
|
||||
final T result = action.get();
|
||||
passed++;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private String getLine() {
|
||||
return getIndent() == 0 ? "=" : "-";
|
||||
}
|
||||
|
||||
public void printHead() {
|
||||
println("=== " + getTitle());
|
||||
}
|
||||
|
||||
public void printStatus() {
|
||||
format("%s%n%s%n", getLine().repeat(30), getTitle());
|
||||
format("%d tests passed in %dms%n", passed, System.currentTimeMillis() - start);
|
||||
println("Version: " + getVersion(owner));
|
||||
println("");
|
||||
}
|
||||
|
||||
private String getTitle() {
|
||||
return String.format("%s %s", owner.getSimpleName(), properties.isEmpty() ? "" : properties);
|
||||
}
|
||||
|
||||
private static String getVersion(final Class<?> clazz) {
|
||||
try {
|
||||
final ClassLoader cl = clazz.getClassLoader();
|
||||
final URL url = cl.getResource(clazz.getName().replace('.', '/') + ".class");
|
||||
if (url == null) {
|
||||
return "(no manifest)";
|
||||
}
|
||||
|
||||
final String path = url.getPath();
|
||||
final int index = path.indexOf(JAR_EXT);
|
||||
if (index == -1) {
|
||||
return DATE_FORMAT.format(new Date(new File(path).lastModified()));
|
||||
}
|
||||
|
||||
final String jarPath = path.substring(0, index + JAR_EXT.length());
|
||||
try (final JarFile jarFile = new JarFile(new File(new URI(jarPath)))) {
|
||||
final JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
|
||||
return DATE_FORMAT.format(new Date(entry.getTime()));
|
||||
}
|
||||
} catch (final IOException | URISyntaxException e) {
|
||||
return "error: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T call(final String message, final SupplierE<T> supplier) {
|
||||
return get(supplier).either(e -> fail(e, "%s", message), Function.identity());
|
||||
}
|
||||
|
||||
public void shouldFail(final String message, @SuppressWarnings("TypeMayBeWeakened") final RunnableE action) {
|
||||
test(() -> get(action).either(e -> null, v -> fail("%s", message)));
|
||||
}
|
||||
|
||||
public <T> T fail(final String format, final Object... args) {
|
||||
return fail(Asserts.error(format, args));
|
||||
}
|
||||
|
||||
public <T> T fail(final Throwable throwable) {
|
||||
return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage());
|
||||
}
|
||||
|
||||
public <T> T fail(final Throwable throwable, final String format, final Object... args) {
|
||||
final String message = String.format(format, args);
|
||||
println("ERROR: " + message);
|
||||
throw throwable instanceof Error ? (Error) throwable : new AssertionError(throwable);
|
||||
}
|
||||
|
||||
public void checkTrue(final boolean condition, final String message, final Object... args) {
|
||||
if (!condition) {
|
||||
fail(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Either<Exception, T> get(final SupplierE<T> supplier) {
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
public Path getFile(final String suffix) {
|
||||
return Paths.get(String.format("test%d.%s", getTestNo(), suffix));
|
||||
}
|
||||
|
||||
public ExtendedRandom random() {
|
||||
return random;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SupplierE<T> extends Supplier<Either<Exception, T>> {
|
||||
T getE() throws Exception;
|
||||
|
||||
@Override
|
||||
default Either<Exception, T> get() {
|
||||
try {
|
||||
return Either.right(getE());
|
||||
} catch (final Exception e) {
|
||||
return Either.left(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RunnableE extends SupplierE<Void> {
|
||||
void run() throws Exception;
|
||||
|
||||
@Override
|
||||
default Void getE() throws Exception {
|
||||
run();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
java/base/Tester.java
Normal file
18
java/base/Tester.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public abstract class Tester extends BaseChecker {
|
||||
protected Tester(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
public abstract void test();
|
||||
|
||||
public void run(final Class<?> test, final String... args) {
|
||||
System.out.println("=== Testing " + test.getSimpleName() + " " + String.join(" ", args));
|
||||
test();
|
||||
counter.printStatus();
|
||||
}
|
||||
}
|
||||
15
java/base/Unit.java
Normal file
15
java/base/Unit.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package base;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Unit {
|
||||
public static final Unit INSTANCE = new Unit();
|
||||
|
||||
private Unit() { }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "unit";
|
||||
}
|
||||
}
|
||||
7
java/base/package-info.java
Normal file
7
java/base/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Common homeworks test classes
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package base;
|
||||
47
java/markup/AbstractList.java
Normal file
47
java/markup/AbstractList.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public abstract class AbstractList implements ContainsInListItem {
|
||||
|
||||
private final List<ListItem> items;
|
||||
private final String highlight;
|
||||
private final String texBegin;
|
||||
private final String texEnd;
|
||||
|
||||
protected AbstractList(
|
||||
List<ListItem> items,
|
||||
String highlight,
|
||||
String texBegin,
|
||||
String texEnd
|
||||
) {
|
||||
this.items = items;
|
||||
this.highlight = highlight;
|
||||
this.texBegin = texBegin;
|
||||
this.texEnd = texEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toHtml(StringBuilder sb) {
|
||||
sb.append("<").append(highlight).append(">");
|
||||
for (ListItem item : items) {
|
||||
item.toHtml(sb);
|
||||
}
|
||||
sb.append("</").append(highlight).append(">");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toMarkdown(StringBuilder sb) {}
|
||||
|
||||
@Override
|
||||
public void toTex(StringBuilder sb) {
|
||||
sb.append(texBegin);
|
||||
for (ListItem item : items) {
|
||||
item.toTex(sb);
|
||||
}
|
||||
sb.append(texEnd);
|
||||
}
|
||||
}
|
||||
66
java/markup/AbstractMarkup.java
Normal file
66
java/markup/AbstractMarkup.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public abstract class AbstractMarkup implements Markdown, Html, Tex {
|
||||
|
||||
protected final List<? extends Markup> items;
|
||||
private final String highlightMarkdown;
|
||||
private final String highlightHtml;
|
||||
private final String highlightTexOpen;
|
||||
private final String highlightTexClose;
|
||||
|
||||
protected AbstractMarkup(
|
||||
List<? extends Markup> items,
|
||||
String highlightMarkdown,
|
||||
String highlightHtml,
|
||||
String highlightTexOpen,
|
||||
String highlightTexClose
|
||||
) {
|
||||
this.items = items;
|
||||
this.highlightMarkdown = highlightMarkdown;
|
||||
this.highlightHtml = highlightHtml;
|
||||
this.highlightTexOpen = highlightTexOpen;
|
||||
this.highlightTexClose = highlightTexClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toMarkdown(StringBuilder sb) {
|
||||
sb.append(highlightMarkdown);
|
||||
for (Markup item : items) {
|
||||
item.toMarkdown(sb);
|
||||
}
|
||||
sb.append(highlightMarkdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toHtml(StringBuilder sb) {
|
||||
if (!highlightHtml.isEmpty()) {
|
||||
sb.append("<").append(highlightHtml).append(">");
|
||||
}
|
||||
for (Markup item : items) {
|
||||
item.toHtml(sb);
|
||||
}
|
||||
if (!highlightHtml.isEmpty()) {
|
||||
sb.append("</").append(highlightHtml).append(">");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toTex(StringBuilder sb) {
|
||||
sb.append(highlightTexOpen);
|
||||
for (Markup item : items) {
|
||||
if (item instanceof Text) {
|
||||
((Text) item).toTex(sb);
|
||||
} else if (item instanceof AbstractMarkup) {
|
||||
((AbstractMarkup) item).toTex(sb);
|
||||
} else if (item instanceof AbstractList) {
|
||||
((AbstractList) item).toTex(sb);
|
||||
}
|
||||
}
|
||||
sb.append(highlightTexClose);
|
||||
}
|
||||
}
|
||||
6
java/markup/ContainsInListItem.java
Normal file
6
java/markup/ContainsInListItem.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface ContainsInListItem extends Markup {}
|
||||
13
java/markup/Emphasis.java
Normal file
13
java/markup/Emphasis.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Emphasis extends AbstractMarkup implements PartOfParagraph {
|
||||
|
||||
public Emphasis(List<PartOfParagraph> items) {
|
||||
super(items, "*", "em", "\\emph{", "}");
|
||||
}
|
||||
}
|
||||
8
java/markup/Html.java
Normal file
8
java/markup/Html.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface Html {
|
||||
void toHtml(StringBuilder sb);
|
||||
}
|
||||
13
java/markup/ListItem.java
Normal file
13
java/markup/ListItem.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ListItem extends AbstractMarkup implements Markup {
|
||||
|
||||
public ListItem(List<ContainsInListItem> items) {
|
||||
super(items, "", "li", "\\item ", "");
|
||||
}
|
||||
}
|
||||
8
java/markup/Markdown.java
Normal file
8
java/markup/Markdown.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface Markdown {
|
||||
void toMarkdown(StringBuilder sb);
|
||||
}
|
||||
6
java/markup/Markup.java
Normal file
6
java/markup/Markup.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface Markup extends Markdown, Html, Tex {}
|
||||
248
java/markup/MarkupListTest.java
Normal file
248
java/markup/MarkupListTest.java
Normal file
File diff suppressed because one or more lines are too long
97
java/markup/MarkupTest.java
Normal file
97
java/markup/MarkupTest.java
Normal file
File diff suppressed because one or more lines are too long
71
java/markup/MarkupTester.java
Normal file
71
java/markup/MarkupTester.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package markup;
|
||||
|
||||
import base.Asserts;
|
||||
import base.BaseChecker;
|
||||
import base.TestCounter;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class MarkupTester {
|
||||
private final Map<String, String> mapping;
|
||||
private final String toString;
|
||||
|
||||
private MarkupTester(final Map<String, String> mapping, final String toString) {
|
||||
this.mapping = mapping;
|
||||
this.toString = toString;
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(final Consumer<Checker> checker, final String name, final Map<String, String> mapping) {
|
||||
return counter -> test(checker).accept(new MarkupTester(mapping, "to" + name), counter);
|
||||
}
|
||||
|
||||
public static BiConsumer<MarkupTester, TestCounter> test(final Consumer<Checker> tester) {
|
||||
return (checker, counter) -> tester.accept(checker.new Checker(counter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString;
|
||||
}
|
||||
|
||||
public class Checker extends BaseChecker {
|
||||
public Checker(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
private <T> MethodHandle findMethod(final T value) {
|
||||
try {
|
||||
return MethodHandles.publicLookup().findVirtual(value.getClass(), toString, MethodType.methodType(void.class, StringBuilder.class));
|
||||
} catch (final NoSuchMethodException | IllegalAccessException e) {
|
||||
throw Asserts.error("Cannot find method 'void %s(StringBuilder)' for %s", toString, value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void test(final T value, String expectedTemplate) {
|
||||
final MethodHandle method = findMethod(value);
|
||||
for (final Map.Entry<String, String> entry : mapping.entrySet()) {
|
||||
expectedTemplate = expectedTemplate.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
final String expected = expectedTemplate;
|
||||
counter.println("Test " + counter.getTestNo());
|
||||
counter.test(() -> {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
method.invoke(value, sb);
|
||||
} catch (final Throwable e) {
|
||||
throw Asserts.error("%s(StringBuilder) for %s thrown exception: %s", toString, value.getClass(), e);
|
||||
}
|
||||
Asserts.assertEquals("Result", expected, sb.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
13
java/markup/OrderedList.java
Normal file
13
java/markup/OrderedList.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class OrderedList extends AbstractList {
|
||||
|
||||
public OrderedList(List<ListItem> items) {
|
||||
super(items, "ol", "\\begin{enumerate}", "\\end{enumerate}");
|
||||
}
|
||||
}
|
||||
16
java/markup/Paragraph.java
Normal file
16
java/markup/Paragraph.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Paragraph
|
||||
extends AbstractMarkup
|
||||
implements ContainsInListItem, PrimePart
|
||||
{
|
||||
|
||||
public Paragraph(List<PartOfParagraph> items) {
|
||||
super(items, "", "p", "\\par{}", "");
|
||||
}
|
||||
}
|
||||
6
java/markup/PartOfParagraph.java
Normal file
6
java/markup/PartOfParagraph.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface PartOfParagraph extends Markup {}
|
||||
6
java/markup/PrimePart.java
Normal file
6
java/markup/PrimePart.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface PrimePart extends Markup {}
|
||||
13
java/markup/Strikeout.java
Normal file
13
java/markup/Strikeout.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Strikeout extends AbstractMarkup implements PartOfParagraph {
|
||||
|
||||
public Strikeout(List<PartOfParagraph> items) {
|
||||
super(items, "~", "s", "\\textst{", "}");
|
||||
}
|
||||
}
|
||||
13
java/markup/Strong.java
Normal file
13
java/markup/Strong.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Strong extends AbstractMarkup implements PartOfParagraph {
|
||||
|
||||
public Strong(List<PartOfParagraph> items) {
|
||||
super(items, "__", "strong", "\\textbf{", "}");
|
||||
}
|
||||
}
|
||||
8
java/markup/Tex.java
Normal file
8
java/markup/Tex.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public interface Tex {
|
||||
void toTex(StringBuilder sb);
|
||||
}
|
||||
27
java/markup/Text.java
Normal file
27
java/markup/Text.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package markup;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Text implements PartOfParagraph {
|
||||
|
||||
private final String text;
|
||||
|
||||
public Text(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toHtml(StringBuilder sb) {
|
||||
sb.append(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toMarkdown(StringBuilder sb) {
|
||||
sb.append(text);
|
||||
}
|
||||
|
||||
public void toTex(StringBuilder sb) {
|
||||
sb.append(text);
|
||||
}
|
||||
}
|
||||
13
java/markup/UnorderedList.java
Normal file
13
java/markup/UnorderedList.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package markup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class UnorderedList extends AbstractList {
|
||||
|
||||
public UnorderedList(List<ListItem> items) {
|
||||
super(items, "ul", "\\begin{itemize}", "\\end{itemize}");
|
||||
}
|
||||
}
|
||||
BIN
java/markup/assets/diagram.png
Normal file
BIN
java/markup/assets/diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 MiB |
1
java/markup/assets/diagram.svg
Normal file
1
java/markup/assets/diagram.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 88 KiB |
157
java/markup/assets/mermaid/diagram.mmd
Normal file
157
java/markup/assets/mermaid/diagram.mmd
Normal file
@@ -0,0 +1,157 @@
|
||||
classDiagram
|
||||
%% Интерфейсы
|
||||
class Markdown {
|
||||
<<interface>>
|
||||
+toMarkdown(StringBuilder)
|
||||
}
|
||||
|
||||
class Html {
|
||||
<<interface>>
|
||||
+toHtml(StringBuilder)
|
||||
}
|
||||
|
||||
class Markup {
|
||||
<<interface>>
|
||||
+toMarkdown(StringBuilder)
|
||||
+toHtml(StringBuilder)
|
||||
}
|
||||
|
||||
class PartOfParagraph {
|
||||
<<interface>>
|
||||
+toMarkdown(StringBuilder)
|
||||
+toHtml(StringBuilder)
|
||||
}
|
||||
|
||||
class ContainsInListItem {
|
||||
<<interface>>
|
||||
+toMarkdown(StringBuilder)
|
||||
+toHtml(StringBuilder)
|
||||
}
|
||||
|
||||
class PrimePart {
|
||||
<<interface>>
|
||||
+toMarkdown(StringBuilder)
|
||||
+toHtml(StringBuilder)
|
||||
}
|
||||
|
||||
%% Наследование интерфейсов
|
||||
Markup --|> Markdown
|
||||
Markup --|> Html
|
||||
PartOfParagraph --|> Markup
|
||||
ContainsInListItem --|> Markup
|
||||
PrimePart --|> Markup
|
||||
|
||||
%% Абстрактные классы
|
||||
class AbstractMarkup {
|
||||
<<abstract>>
|
||||
#List~Markup~ items
|
||||
-String highlightMarkdown
|
||||
-String highlightHtml
|
||||
-String highlightTexOpen
|
||||
-String highlightTexClose
|
||||
+AbstractMarkup(List, String, String, String, String)
|
||||
+toMarkdown(StringBuilder)
|
||||
+toHtml(StringBuilder)
|
||||
+toTex(StringBuilder)
|
||||
}
|
||||
|
||||
class AbstractList {
|
||||
<<abstract>>
|
||||
-List~ListItem~ items
|
||||
-String highlight
|
||||
-String texBegin
|
||||
-String texEnd
|
||||
+AbstractList(List, String, String, String)
|
||||
+toHtml(StringBuilder)
|
||||
+toMarkdown(StringBuilder)
|
||||
+toTex(StringBuilder)
|
||||
}
|
||||
|
||||
%% Абстрактные классы реализуют интерфейсы
|
||||
AbstractMarkup ..|> Markdown
|
||||
AbstractMarkup ..|> Html
|
||||
AbstractList ..|> ContainsInListItem
|
||||
|
||||
%% Inline элементы
|
||||
class Text {
|
||||
-String text
|
||||
+Text(String)
|
||||
+toMarkdown(StringBuilder)
|
||||
+toHtml(StringBuilder)
|
||||
+toTex(StringBuilder)
|
||||
}
|
||||
|
||||
class Emphasis {
|
||||
+Emphasis(List~PartOfParagraph~)
|
||||
}
|
||||
|
||||
class Strong {
|
||||
+Strong(List~PartOfParagraph~)
|
||||
}
|
||||
|
||||
class Strikeout {
|
||||
+Strikeout(List~PartOfParagraph~)
|
||||
}
|
||||
|
||||
%% Inline элементы наследуются и реализуют
|
||||
Text ..|> PartOfParagraph
|
||||
Emphasis --|> AbstractMarkup
|
||||
Emphasis ..|> PartOfParagraph
|
||||
Strong --|> AbstractMarkup
|
||||
Strong ..|> PartOfParagraph
|
||||
Strikeout --|> AbstractMarkup
|
||||
Strikeout ..|> PartOfParagraph
|
||||
|
||||
%% Paragraph
|
||||
class Paragraph {
|
||||
+Paragraph(List~PartOfParagraph~)
|
||||
}
|
||||
|
||||
Paragraph --|> AbstractMarkup
|
||||
Paragraph ..|> ContainsInListItem
|
||||
Paragraph ..|> PrimePart
|
||||
|
||||
%% Списки
|
||||
class OrderedList {
|
||||
+OrderedList(List~ListItem~)
|
||||
}
|
||||
|
||||
class UnorderedList {
|
||||
+UnorderedList(List~ListItem~)
|
||||
}
|
||||
|
||||
class ListItem {
|
||||
+ListItem(List~ContainsInListItem~)
|
||||
}
|
||||
|
||||
OrderedList --|> AbstractList
|
||||
UnorderedList --|> AbstractList
|
||||
ListItem --|> AbstractMarkup
|
||||
ListItem ..|> Markup
|
||||
|
||||
%% Зависимости (что может содержать что)
|
||||
AbstractMarkup o-- Markup : contains
|
||||
AbstractList o-- ListItem : contains
|
||||
Emphasis o-- PartOfParagraph : contains
|
||||
Strong o-- PartOfParagraph : contains
|
||||
Strikeout o-- PartOfParagraph : contains
|
||||
Paragraph o-- PartOfParagraph : contains
|
||||
ListItem o-- ContainsInListItem : contains
|
||||
|
||||
%% Стили
|
||||
style Markdown fill:#e1f5ff
|
||||
style Html fill:#e1f5ff
|
||||
style Markup fill:#e1f5ff
|
||||
style PartOfParagraph fill:#fff4e1
|
||||
style ContainsInListItem fill:#fff4e1
|
||||
style PrimePart fill:#fff4e1
|
||||
style AbstractMarkup fill:#ffe1f5
|
||||
style AbstractList fill:#ffe1f5
|
||||
style Text fill:#e1ffe1
|
||||
style Emphasis fill:#e1ffe1
|
||||
style Strong fill:#e1ffe1
|
||||
style Strikeout fill:#e1ffe1
|
||||
style Paragraph fill:#ffe1e1
|
||||
style OrderedList fill:#f5e1ff
|
||||
style UnorderedList fill:#f5e1ff
|
||||
style ListItem fill:#f5e1ff
|
||||
7
java/markup/package-info.java
Normal file
7
java/markup/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#markup">Markup</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package markup;
|
||||
61
java/md2html/BlockCreator.java
Normal file
61
java/md2html/BlockCreator.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package md2html;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BlockCreator {
|
||||
private final String text;
|
||||
private final List<String> blocks;
|
||||
|
||||
public BlockCreator(String text) {
|
||||
this.text = text;
|
||||
blocks = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<String> divideByBlocks() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
if (isNewLine(text.charAt(i))) {
|
||||
i = newLine(i);
|
||||
if (i < text.length() && isNewLine(text.charAt(i))) {
|
||||
i = newLine(i);
|
||||
addToBlock(sb);
|
||||
} else if (i < text.length()){
|
||||
if (!sb.isEmpty()) {
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
sb.append(text.charAt(i++));
|
||||
}
|
||||
} else {
|
||||
sb.append(text.charAt(i++));
|
||||
}
|
||||
}
|
||||
addToBlock(sb);
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private void addToBlock(StringBuilder item) {
|
||||
if (!item.isEmpty()) {
|
||||
blocks.add(item.toString());
|
||||
item.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
private int newLine(int i) {
|
||||
if (i < text.length() && text.charAt(i) == '\r') {
|
||||
i++;
|
||||
if (i < text.length() && text.charAt(i) == '\n') {
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static boolean isNewLine(char ch) {
|
||||
return (ch == '\u2028') || (ch == '\u2029') ||
|
||||
(ch == '\u0085') || (ch == '\n') || (ch == '\r');
|
||||
}
|
||||
}
|
||||
11
java/md2html/Code.java
Normal file
11
java/md2html/Code.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package md2html;
|
||||
|
||||
import java.util.List;
|
||||
import markup.*;
|
||||
|
||||
public class Code extends AbstractMarkup implements PartOfParagraph {
|
||||
|
||||
public Code(List<PartOfParagraph> items) {
|
||||
super(items, "'", "code", "", "");
|
||||
}
|
||||
}
|
||||
14
java/md2html/Header.java
Normal file
14
java/md2html/Header.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package md2html;
|
||||
|
||||
import java.util.List;
|
||||
import markup.*;
|
||||
|
||||
public class Header extends AbstractMarkup implements PrimePart {
|
||||
public Header(List<PartOfParagraph> items, int level) {
|
||||
super(items,
|
||||
"#".repeat(level),
|
||||
"h" + level,
|
||||
"",
|
||||
"");
|
||||
}
|
||||
}
|
||||
64
java/md2html/Md2Html.java
Normal file
64
java/md2html/Md2Html.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package md2html;
|
||||
|
||||
import markup.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Md2Html {
|
||||
public static void main(String[] args) {
|
||||
String text = readFile(args[0]);
|
||||
BlockCreator creator = new BlockCreator(text);
|
||||
List<String> blocks = creator.divideByBlocks();
|
||||
List<PrimePart> parts = new ArrayList<>();
|
||||
for (String block : blocks) {
|
||||
PrimePartCreator creatorPrime = new PrimePartCreator(block);
|
||||
parts.add(creatorPrime.createPart());
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
parts.get(i).toHtml(sb);
|
||||
if (i < parts.size() - 1) {
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
}
|
||||
writeToFile(args[1], sb.toString());
|
||||
}
|
||||
|
||||
public static String readFile(String nameOfFile) {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(nameOfFile), StandardCharsets.UTF_8
|
||||
)
|
||||
)) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
int read = reader.read();
|
||||
while (read != -1) {
|
||||
text.append((char) read);
|
||||
read = reader.read();
|
||||
}
|
||||
return text.toString();
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("Input file not found: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException file not found: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void writeToFile(String nameOfFile, String text) {
|
||||
try (BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(nameOfFile), StandardCharsets.UTF_8
|
||||
)
|
||||
)) {
|
||||
writer.write(text);
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("Output file not found: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.out.println("IOException file not found: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
71
java/md2html/Md2HtmlTest.java
Normal file
71
java/md2html/Md2HtmlTest.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package md2html;
|
||||
|
||||
import base.Selector;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class Md2HtmlTest {
|
||||
// === 3637
|
||||
private static final Consumer<? super Md2HtmlTester> INS = tester -> tester
|
||||
.test("<<вставка>>", "<p><ins>вставка</ins></p>")
|
||||
.test("Это <<вставка>>, вложенная в текст", "<p>Это <ins>вставка</ins>, вложенная в текст</p>")
|
||||
.spoiled("Это не <<вставка>>", "<p>Это не <<вставка>></p>", "<", ">")
|
||||
.spoiled("Это не <<вставка>> 2", "<p>Это не <<вставка>> 2</p>", "<", ">")
|
||||
.addElement("ins", "<<", ">>");
|
||||
private static final Consumer<? super Md2HtmlTester> DEL = tester -> tester
|
||||
.test("}}удаление{{", "<p><del>удаление</del></p>")
|
||||
.test("Это }}удаление{{, вложенное в текст", "<p>Это <del>удаление</del>, вложенное в текст</p>")
|
||||
.spoiled("Это не }}удаление{{", "<p>Это не }}удаление{{</p>", "{")
|
||||
.spoiled("Это не }}удаление{{ 2", "<p>Это не }}удаление{{ 2</p>", "{")
|
||||
.addElement("del", "}}", "{{");
|
||||
|
||||
// === 3839
|
||||
private static final Consumer<? super Md2HtmlTester> PRE = tester -> tester
|
||||
.test("```код __без__ форматирования```", "<p><pre>код __без__ форматирования</pre></p>")
|
||||
.test(
|
||||
"Это не `\\``код __без__ форматирования``\\`",
|
||||
"<p>Это не <code>`</code>код <strong>без</strong> форматирования<code></code>`</p>"
|
||||
)
|
||||
.addElement("pre", "```", (checker, markup, input, output) -> {
|
||||
final String contentS = checker.generateInput(markup).replace("`", "");
|
||||
|
||||
input.append("```").append(contentS).append("```");
|
||||
output.append("<pre>").append(contentS.replace("<", "<").replace(">", "")).append("</pre>");
|
||||
});
|
||||
|
||||
// === 3435
|
||||
private static final Consumer<? super Md2HtmlTester> SAMP = tester -> tester
|
||||
.test("!!пример!!", "<p><samp>пример</samp></p>")
|
||||
.test("Это !!пример!!, вложенный в текст", "<p>Это <samp>пример</samp>, вложенный в текст</p>")
|
||||
.spoiled("Это не !!пример!!", "<p>Это не !!пример!!</p>", "!")
|
||||
.spoiled("Это не !!пример!! 2", "<p>Это не !!пример!! 2</p>", "!")
|
||||
.addElement("samp", "!!");
|
||||
|
||||
// === 3233
|
||||
private static final Consumer<Md2HtmlTester> VAR = tester -> tester
|
||||
.test("%переменная%", "<p><var>переменная</var></p>")
|
||||
.test("Это %переменная%, вложенная в текст", "<p>Это <var>переменная</var>, вложенная в текст</p>")
|
||||
.spoiled("Это не %переменная%", "<p>Это не %переменная%</p>", "%")
|
||||
.spoiled("Это не %переменная% 2", "<p>Это не %переменная% 2</p>", "%")
|
||||
.addElement("var", "%");
|
||||
|
||||
// === Common
|
||||
|
||||
public static final Selector SELECTOR = Selector.composite(Md2HtmlTest.class, c -> new Md2HtmlTester(), Md2HtmlTester::test)
|
||||
.variant("Base")
|
||||
.variant("3637", INS, DEL)
|
||||
.variant("3839", PRE)
|
||||
.variant("3435", SAMP)
|
||||
.variant("3233", VAR)
|
||||
.selector();
|
||||
|
||||
private Md2HtmlTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
355
java/md2html/Md2HtmlTester.java
Normal file
355
java/md2html/Md2HtmlTester.java
Normal file
@@ -0,0 +1,355 @@
|
||||
package md2html;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class Md2HtmlTester {
|
||||
private static final Map<String, String> ESCAPES = Map.of("<", "<", ">", ">");
|
||||
|
||||
private final Map<String, Generator> elements = new HashMap<>();
|
||||
private final Map<String, List<String>> tags = new LinkedHashMap<>();
|
||||
private final List<Pair<String, String>> tests = new ArrayList<>();
|
||||
|
||||
public Md2HtmlTester() {
|
||||
addElement("em", "*");
|
||||
addElement("em", "_");
|
||||
addElement("strong", "**");
|
||||
addElement("strong", "__");
|
||||
addElement("s", "--");
|
||||
addElement("code", "`");
|
||||
|
||||
test(
|
||||
"# Заголовок первого уровня\n\n",
|
||||
"<h1>Заголовок первого уровня</h1>"
|
||||
);
|
||||
test(
|
||||
"## Второго\n\n",
|
||||
"<h2>Второго</h2>"
|
||||
);
|
||||
test(
|
||||
"### Третьего ## уровня\n\n",
|
||||
"<h3>Третьего ## уровня</h3>"
|
||||
);
|
||||
test(
|
||||
"#### Четвертого\n# Все еще четвертого\n\n",
|
||||
"<h4>Четвертого\n# Все еще четвертого</h4>"
|
||||
);
|
||||
test(
|
||||
"Этот абзац текста\nсодержит две строки.",
|
||||
"<p>Этот абзац текста\nсодержит две строки.</p>"
|
||||
);
|
||||
test(
|
||||
" # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с `#`.\n\n",
|
||||
"<p> # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с <code>#</code>.</p>"
|
||||
);
|
||||
test(
|
||||
"#И это не заголовок.\n\n",
|
||||
"<p>#И это не заголовок.</p>"
|
||||
);
|
||||
test(
|
||||
"###### Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)\n\n",
|
||||
"<h6>Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)</h6>"
|
||||
);
|
||||
test(
|
||||
"Мы все любим *выделять* текст _разными_ способами.\n**Сильное выделение**, используется гораздо реже,\nно __почему бы и нет__?\nНемного --зачеркивания-- еще никому не вредило.\nКод представляется элементом `code`.\n\n",
|
||||
"<p>Мы все любим <em>выделять</em> текст <em>разными</em> способами.\n<strong>Сильное выделение</strong>, используется гораздо реже,\nно <strong>почему бы и нет</strong>?\nНемного <s>зачеркивания</s> еще никому не вредило.\nКод представляется элементом <code>code</code>.</p>"
|
||||
);
|
||||
test(
|
||||
"Обратите внимание, как экранируются специальные\nHTML-символы, такие как `<`, `>` и `&`.\n\n",
|
||||
"<p>Обратите внимание, как экранируются специальные\nHTML-символы, такие как <code><</code>, <code>></code> и <code>&</code>.</p>"
|
||||
);
|
||||
test(
|
||||
"Экранирование должно работать во всех местах: <>&.\n\n",
|
||||
"<p>Экранирование должно работать во всех местах: <>&.</p>"
|
||||
);
|
||||
test(
|
||||
"Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: \\*.",
|
||||
"<p>Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: *.</p>"
|
||||
);
|
||||
test(
|
||||
"\n\n\nЛишние пустые строки должны игнорироваться.\n\n\n\n",
|
||||
"<p>Лишние пустые строки должны игнорироваться.</p>"
|
||||
);
|
||||
test(
|
||||
"Любите ли вы *вложенные __выделения__* так,\nкак __--люблю--__ их я?",
|
||||
"<p>Любите ли вы <em>вложенные <strong>выделения</strong></em> так,\nкак <strong><s>люблю</s></strong> их я?</p>"
|
||||
);
|
||||
|
||||
test(
|
||||
"""
|
||||
# Заголовок первого уровня
|
||||
|
||||
## Второго
|
||||
|
||||
### Третьего ## уровня
|
||||
|
||||
#### Четвертого
|
||||
# Все еще четвертого
|
||||
|
||||
Этот абзац текста
|
||||
содержит две строки.
|
||||
|
||||
# Может показаться, что это заголовок.
|
||||
Но нет, это абзац, начинающийся с `#`.
|
||||
|
||||
#И это не заголовок.
|
||||
|
||||
###### Заголовки могут быть многострочными
|
||||
(и с пропуском заголовков предыдущих уровней)
|
||||
|
||||
Мы все любим *выделять* текст _разными_ способами.
|
||||
**Сильное выделение**, используется гораздо реже,
|
||||
но __почему бы и нет__?
|
||||
Немного --зачеркивания-- еще никому не вредило.
|
||||
Код представляется элементом `code`.
|
||||
|
||||
Обратите внимание, как экранируются специальные
|
||||
HTML-символы, такие как `<`, `>` и `&`.
|
||||
|
||||
Знаете ли вы, что в Markdown, одиночные * и _
|
||||
не означают выделение?
|
||||
Они так же могут быть заэкранированы
|
||||
при помощи обратного слэша: \\*.
|
||||
|
||||
|
||||
|
||||
Лишние пустые строки должны игнорироваться.
|
||||
|
||||
Любите ли вы *вложенные __выделения__* так,
|
||||
как __--люблю--__ их я?
|
||||
""",
|
||||
"""
|
||||
<h1>Заголовок первого уровня</h1>
|
||||
<h2>Второго</h2>
|
||||
<h3>Третьего ## уровня</h3>
|
||||
<h4>Четвертого
|
||||
# Все еще четвертого</h4>
|
||||
<p>Этот абзац текста
|
||||
содержит две строки.</p>
|
||||
<p> # Может показаться, что это заголовок.
|
||||
Но нет, это абзац, начинающийся с <code>#</code>.</p>
|
||||
<p>#И это не заголовок.</p>
|
||||
<h6>Заголовки могут быть многострочными
|
||||
(и с пропуском заголовков предыдущих уровней)</h6>
|
||||
<p>Мы все любим <em>выделять</em> текст <em>разными</em> способами.
|
||||
<strong>Сильное выделение</strong>, используется гораздо реже,
|
||||
но <strong>почему бы и нет</strong>?
|
||||
Немного <s>зачеркивания</s> еще никому не вредило.
|
||||
Код представляется элементом <code>code</code>.</p>
|
||||
<p>Обратите внимание, как экранируются специальные
|
||||
HTML-символы, такие как <code><</code>, <code>></code> и <code>&</code>.</p>
|
||||
<p>Знаете ли вы, что в Markdown, одиночные * и _
|
||||
не означают выделение?
|
||||
Они так же могут быть заэкранированы
|
||||
при помощи обратного слэша: *.</p>
|
||||
<p>Лишние пустые строки должны игнорироваться.</p>
|
||||
<p>Любите ли вы <em>вложенные <strong>выделения</strong></em> так,
|
||||
как <strong><s>люблю</s></strong> их я?</p>
|
||||
"""
|
||||
);
|
||||
|
||||
test("# Без перевода строки в конце", "<h1>Без перевода строки в конце</h1>");
|
||||
test("# Один перевод строки в конце\n", "<h1>Один перевод строки в конце</h1>");
|
||||
test("# Два перевода строки в конце\n\n", "<h1>Два перевода строки в конце</h1>");
|
||||
test(
|
||||
"Выделение может *начинаться на одной строке,\n а заканчиваться* на другой",
|
||||
"<p>Выделение может <em>начинаться на одной строке,\n а заканчиваться</em> на другой</p>"
|
||||
);
|
||||
test("# *Выделение* и `код` в заголовках", "<h1><em>Выделение</em> и <code>код</code> в заголовках</h1>");
|
||||
}
|
||||
|
||||
protected void addElement(final String tag, final String markup) {
|
||||
addElement(tag, markup, markup);
|
||||
}
|
||||
|
||||
protected void addElement(final String tag, final String begin, final String end) {
|
||||
addElement(tag, begin, (checker, markup, input, output) -> {
|
||||
checker.space(input, output);
|
||||
input.append(begin);
|
||||
open(output, tag);
|
||||
|
||||
checker.word(input, output);
|
||||
checker.generate(markup, input, output);
|
||||
checker.word(input, output);
|
||||
|
||||
input.append(end);
|
||||
close(output, tag);
|
||||
checker.space(input, output);
|
||||
});
|
||||
}
|
||||
|
||||
public void addElement(final String tag, final String begin, final Generator generator) {
|
||||
Asserts.assertTrue("Duplicate element " + begin, elements.put(begin, generator) == null);
|
||||
tags.computeIfAbsent(tag, k -> new ArrayList<>()).add(begin);
|
||||
}
|
||||
|
||||
private final Runner runner = Runner.packages("md2html").files("Md2Html");
|
||||
|
||||
protected Md2HtmlTester test(final String input, final String output) {
|
||||
tests.add(Pair.of(input, output));
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Md2HtmlTester spoiled(final String input, final String output, final String... spoilers) {
|
||||
for (final String spoiler : spoilers) {
|
||||
final Indexer in = new Indexer(input, spoiler);
|
||||
final Indexer out = new Indexer(output, ESCAPES.getOrDefault(spoiler, spoiler));
|
||||
while (in.next() && out.next()) {
|
||||
tests.add(Pair.of(in.cut(), out.cut()));
|
||||
tests.add(Pair.of(in.escape(), output));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class Indexer {
|
||||
private final String string;
|
||||
private final String spoiler;
|
||||
private int index = - 1;
|
||||
|
||||
public Indexer(final String string, final String spoiler) {
|
||||
this.string = string;
|
||||
this.spoiler = spoiler;
|
||||
}
|
||||
|
||||
public boolean next() {
|
||||
index = string.indexOf(spoiler, index + 1);
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
public String cut() {
|
||||
return string.substring(0, index) + string.substring(index + spoiler.length());
|
||||
}
|
||||
|
||||
public String escape() {
|
||||
return string.substring(0, index) + "\\" + string.substring(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static void open(final StringBuilder output, final String tag) {
|
||||
output.append("<").append(tag).append(">");
|
||||
}
|
||||
|
||||
private static void close(final StringBuilder output, final String tag) {
|
||||
output.append("</").append(tag).append(">");
|
||||
}
|
||||
|
||||
public void test(final TestCounter counter) {
|
||||
counter.scope("Testing " + String.join(", ", tags.keySet()), () -> new Checker(counter).test());
|
||||
}
|
||||
|
||||
public class Checker extends BaseChecker {
|
||||
public Checker(final TestCounter counter) {
|
||||
super(counter);
|
||||
}
|
||||
|
||||
protected void test() {
|
||||
for (final Pair<String, String> test : tests) {
|
||||
test(test);
|
||||
}
|
||||
|
||||
for (final String markup : elements.keySet()) {
|
||||
randomTest(3, 10, List.of(markup));
|
||||
}
|
||||
|
||||
final int d = TestCounter.DENOMINATOR;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
randomTest(100, 1000, randomMarkup());
|
||||
}
|
||||
randomTest(100, 100_000 / d, randomMarkup());
|
||||
}
|
||||
|
||||
private void test(final Pair<String, String> test) {
|
||||
runner.testEquals(counter, Arrays.asList(test.first().split("\n")), Arrays.asList(test.second().split("\n")));
|
||||
}
|
||||
|
||||
private List<String> randomMarkup() {
|
||||
return Functional.map(tags.values(), random()::randomItem);
|
||||
}
|
||||
|
||||
private void randomTest(final int paragraphs, final int length, final List<String> markup) {
|
||||
final StringBuilder input = new StringBuilder();
|
||||
final StringBuilder output = new StringBuilder();
|
||||
emptyLines(input);
|
||||
final List<String> markupList = new ArrayList<>(markup);
|
||||
for (int i = 0; i < paragraphs; i++) {
|
||||
final StringBuilder inputSB = new StringBuilder();
|
||||
paragraph(length, inputSB, output, markupList);
|
||||
input.append(inputSB);
|
||||
emptyLines(input);
|
||||
}
|
||||
test(Pair.of(input.toString(), output.toString()));
|
||||
}
|
||||
|
||||
private void paragraph(final int length, final StringBuilder input, final StringBuilder output, final List<String> markup) {
|
||||
final int h = random().nextInt(0, 6);
|
||||
final String tag = h == 0 ? "p" : "h" + h;
|
||||
if (h > 0) {
|
||||
input.append(new String(new char[h]).replace('\0', '#')).append(" ");
|
||||
}
|
||||
|
||||
open(output, tag);
|
||||
while (input.length() < length) {
|
||||
generate(markup, input, output);
|
||||
final String middle = random().randomString(ExtendedRandom.ENGLISH);
|
||||
input.append(middle).append("\n");
|
||||
output.append(middle).append("\n");
|
||||
}
|
||||
output.setLength(output.length() - 1);
|
||||
close(output, tag);
|
||||
|
||||
output.append("\n");
|
||||
input.append("\n");
|
||||
}
|
||||
|
||||
private void space(final StringBuilder input, final StringBuilder output) {
|
||||
if (random().nextBoolean()) {
|
||||
final String space = random().nextBoolean() ? " " : "\n";
|
||||
input.append(space);
|
||||
output.append(space);
|
||||
}
|
||||
}
|
||||
|
||||
public void generate(final List<String> markup, final StringBuilder input, final StringBuilder output) {
|
||||
word(input, output);
|
||||
if (markup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final String type = random().randomItem(markup);
|
||||
|
||||
markup.remove(type);
|
||||
elements.get(type).generate(this, markup, input, output);
|
||||
markup.add(type);
|
||||
}
|
||||
|
||||
protected void word(final StringBuilder input, final StringBuilder output) {
|
||||
final String word = random().randomString(random().randomItem(ExtendedRandom.ENGLISH, ExtendedRandom.GREEK, ExtendedRandom.RUSSIAN));
|
||||
input.append(word);
|
||||
output.append(word);
|
||||
}
|
||||
|
||||
private void emptyLines(final StringBuilder sb) {
|
||||
while (random().nextBoolean()) {
|
||||
sb.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
String generateInput(final List<String> markup) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
generate(markup, sb, new StringBuilder());
|
||||
return sb.toString()
|
||||
.replace("<", "")
|
||||
.replace(">", "")
|
||||
.replace("]", "");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Generator {
|
||||
void generate(Checker checker, List<String> markup, StringBuilder input, StringBuilder output);
|
||||
}
|
||||
}
|
||||
253
java/md2html/PrimePartCreator.java
Normal file
253
java/md2html/PrimePartCreator.java
Normal file
@@ -0,0 +1,253 @@
|
||||
package md2html;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import markup.*;
|
||||
|
||||
public class PrimePartCreator {
|
||||
|
||||
private final String block;
|
||||
private int currentChar;
|
||||
|
||||
public enum MarkdownToken {
|
||||
WORD,
|
||||
EMPHASIS_STAR,
|
||||
STRONG_STAR,
|
||||
EMPHASIS_UNDERLINE,
|
||||
STRONG_UNDERLINE,
|
||||
STRIKEOUT,
|
||||
CODE,
|
||||
SCREENING,
|
||||
NOTHING,
|
||||
}
|
||||
|
||||
public PrimePartCreator(String block) {
|
||||
this.block = block;
|
||||
currentChar = 0;
|
||||
}
|
||||
|
||||
public PrimePart createPart() {
|
||||
PrimePart result;
|
||||
if (isHeader()) {
|
||||
int levelOfHeader = 0;
|
||||
while (
|
||||
levelOfHeader < block.length() &&
|
||||
block.charAt(levelOfHeader) == '#'
|
||||
) {
|
||||
levelOfHeader++;
|
||||
}
|
||||
currentChar = levelOfHeader + 1;
|
||||
result = new Header(buildPart(MarkdownToken.WORD), levelOfHeader);
|
||||
} else {
|
||||
currentChar = 0;
|
||||
result = new Paragraph(buildPart(MarkdownToken.WORD));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<PartOfParagraph> buildPart(MarkdownToken currentToken) {
|
||||
List<PartOfParagraph> items = new ArrayList<>();
|
||||
MarkdownToken token = nextMarkdownToken();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (token != MarkdownToken.NOTHING) {
|
||||
if (
|
||||
(token == currentToken && currentToken != MarkdownToken.WORD) ||
|
||||
isSuffix(currentToken)
|
||||
) {
|
||||
addToList(items, sb);
|
||||
if (
|
||||
!(token == currentToken &&
|
||||
currentToken != MarkdownToken.WORD)
|
||||
) {
|
||||
currentChar++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (token) {
|
||||
case STRIKEOUT -> {
|
||||
addToList(items, sb);
|
||||
Strikeout strikeout = new Strikeout(buildPart(token));
|
||||
items.add(strikeout);
|
||||
}
|
||||
case STRONG_STAR, STRONG_UNDERLINE -> {
|
||||
addToList(items, sb);
|
||||
Strong strong = new Strong(buildPart(token));
|
||||
items.add(strong);
|
||||
}
|
||||
case EMPHASIS_STAR, EMPHASIS_UNDERLINE -> {
|
||||
addToList(items, sb);
|
||||
Emphasis emphasis = new Emphasis(buildPart(token));
|
||||
items.add(emphasis);
|
||||
}
|
||||
case SCREENING -> {
|
||||
addToList(items, sb);
|
||||
items.add(
|
||||
new Text(String.valueOf(block.charAt(currentChar)))
|
||||
);
|
||||
currentChar++;
|
||||
}
|
||||
case CODE -> {
|
||||
addToList(items, sb);
|
||||
Code code = new Code(buildPart(token));
|
||||
items.add(code);
|
||||
}
|
||||
default -> {
|
||||
if (currentChar < block.length()) {
|
||||
if (block.charAt(currentChar) == '<') {
|
||||
sb.append("<");
|
||||
} else if (block.charAt(currentChar) == '>') {
|
||||
sb.append(">");
|
||||
} else if (block.charAt(currentChar) == '&') {
|
||||
sb.append("&");
|
||||
} else {
|
||||
sb.append(block.charAt(currentChar));
|
||||
}
|
||||
currentChar++;
|
||||
}
|
||||
}
|
||||
}
|
||||
token = nextMarkdownToken();
|
||||
}
|
||||
if (token == MarkdownToken.NOTHING) {
|
||||
addToList(items, sb);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private MarkdownToken nextMarkdownToken() {
|
||||
if (currentChar == block.length()) {
|
||||
return MarkdownToken.NOTHING;
|
||||
}
|
||||
switch (block.charAt(currentChar)) {
|
||||
case '*' -> {
|
||||
if (isPrefix("**")) {
|
||||
currentChar += 2;
|
||||
return MarkdownToken.STRONG_STAR;
|
||||
} else if (isPrefix("*")) {
|
||||
currentChar += 1;
|
||||
return MarkdownToken.EMPHASIS_STAR;
|
||||
}
|
||||
}
|
||||
case '_' -> {
|
||||
if (isPrefix("__")) {
|
||||
currentChar += 2;
|
||||
return MarkdownToken.STRONG_UNDERLINE;
|
||||
} else if (isPrefix("_")) {
|
||||
currentChar += 1;
|
||||
return MarkdownToken.EMPHASIS_UNDERLINE;
|
||||
}
|
||||
}
|
||||
case '-' -> {
|
||||
if (isPrefix("--")) {
|
||||
currentChar += 2;
|
||||
return MarkdownToken.STRIKEOUT;
|
||||
}
|
||||
}
|
||||
case '`' -> {
|
||||
if (isPrefix("`")) {
|
||||
currentChar += 1;
|
||||
return MarkdownToken.CODE;
|
||||
}
|
||||
}
|
||||
case '\\' -> {
|
||||
if (isScreening(block.charAt(currentChar + 1))) {
|
||||
currentChar++;
|
||||
return MarkdownToken.SCREENING;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
return MarkdownToken.WORD;
|
||||
}
|
||||
}
|
||||
return MarkdownToken.WORD;
|
||||
}
|
||||
|
||||
private static boolean isScreening(char ch) {
|
||||
return ch == '*' || ch == '_';
|
||||
}
|
||||
|
||||
private boolean isSuffix(MarkdownToken token) {
|
||||
if (token == MarkdownToken.WORD) {
|
||||
return false;
|
||||
}
|
||||
String suffix = getHighlight(token);
|
||||
if (isWordInBlock(suffix, currentChar, currentChar + suffix.length())) {
|
||||
currentChar += suffix.length() - 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPrefix(String prefix) {
|
||||
int i = currentChar + prefix.length();
|
||||
return (
|
||||
isValidIndexInBlock(i + prefix.length()) &&
|
||||
!isWordInBlock(prefix, i, i + prefix.length()) &&
|
||||
isWordInBlock(prefix, i - prefix.length(), i) &&
|
||||
!Character.isWhitespace(block.charAt(i))
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isWordInBlock(String word, int start, int end) {
|
||||
return word.equals(block.substring(start, end));
|
||||
}
|
||||
|
||||
private boolean isValidIndexInBlock(int index) {
|
||||
return index < block.length();
|
||||
}
|
||||
|
||||
private boolean isHeader() {
|
||||
int levelOfHeader = 0;
|
||||
if (block.charAt(levelOfHeader) != '#') {
|
||||
return false;
|
||||
}
|
||||
while (
|
||||
levelOfHeader < block.length() && block.charAt(levelOfHeader) == '#'
|
||||
) {
|
||||
levelOfHeader++;
|
||||
}
|
||||
return (
|
||||
levelOfHeader < block.length() &&
|
||||
Character.isWhitespace(block.charAt(levelOfHeader))
|
||||
);
|
||||
}
|
||||
|
||||
private static String getHighlight(MarkdownToken token) {
|
||||
switch (token) {
|
||||
case STRONG_STAR -> {
|
||||
return "**";
|
||||
}
|
||||
case STRONG_UNDERLINE -> {
|
||||
return "__";
|
||||
}
|
||||
case EMPHASIS_STAR -> {
|
||||
return "*";
|
||||
}
|
||||
case EMPHASIS_UNDERLINE -> {
|
||||
return "_";
|
||||
}
|
||||
case CODE -> {
|
||||
return "`";
|
||||
}
|
||||
case STRIKEOUT -> {
|
||||
return "--";
|
||||
}
|
||||
case SCREENING -> {
|
||||
return "\\";
|
||||
}
|
||||
default -> {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addToList(
|
||||
List<PartOfParagraph> items,
|
||||
StringBuilder sb
|
||||
) {
|
||||
if (!sb.isEmpty()) {
|
||||
items.add(new Text(sb.toString()));
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
java/md2html/package-info.java
Normal file
8
java/md2html/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#md2html">Markdown to HTML</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package md2html;
|
||||
87
java/reverse/FastReverseTest.java
Normal file
87
java/reverse/FastReverseTest.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package reverse;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import wordStat.WordStatTest;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class FastReverseTest {
|
||||
// === 3637
|
||||
|
||||
private static final Named<BiFunction<ExtendedRandom, Integer, String>> OCT = Named.of("",
|
||||
(r, i) -> r.nextBoolean() ? Integer.toString(i) : Integer.toOctalString(i) + (r.nextBoolean() ? "o" : "O")
|
||||
);
|
||||
private static final Named<BiFunction<ExtendedRandom, Integer, String>> DEC = Named.of("", (r, i) -> Integer.toString(i));
|
||||
|
||||
private static final Named<String> PUNCT = Named.of(
|
||||
"",
|
||||
IntStream.range(0, Character.MAX_VALUE)
|
||||
.filter(ch -> ch == ' ' || Character.getType(ch) == Character.START_PUNCTUATION || Character.getType(ch) == Character.END_PUNCTUATION)
|
||||
.filter(ch -> ch != 13 && ch != 10)
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining())
|
||||
);
|
||||
|
||||
public static final Named<ReverseTester.Op> MIN_C = Named.of("MinC", scan2((a, b) -> b));
|
||||
public static final Named<ReverseTester.Op> MIN = Named.of("Min", scan2(Math::min));
|
||||
|
||||
private static ReverseTester.Op scan2(final LongBinaryOperator reduce) {
|
||||
return ints -> {
|
||||
// This code is intentionally obscure
|
||||
final int length = Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0);
|
||||
final long[] cs = new long[length];
|
||||
final long[] cc = new long[length + 1];
|
||||
Arrays.fill(cs, Integer.MAX_VALUE);
|
||||
Arrays.fill(cc, Integer.MAX_VALUE);
|
||||
//noinspection NestedAssignment
|
||||
final long[][] rows = range(ints.length).mapToObj(i -> {
|
||||
range(ints[i].length).forEachOrdered(j -> cc[j] = reduce.applyAsLong(
|
||||
cc[j + 1],
|
||||
cs[j] = Math.min(cs[j], ints[i][j])
|
||||
));
|
||||
return Arrays.copyOf(cc, ints[i].length);
|
||||
})
|
||||
.toArray(long[][]::new);
|
||||
return range(ints.length).mapToObj(i -> rows[i]).toArray(long[][]::new);
|
||||
};
|
||||
}
|
||||
|
||||
private static IntStream range(final int length) {
|
||||
return IntStream.iterate(length - 1, i -> i >= 0, i -> i - 1);
|
||||
}
|
||||
|
||||
|
||||
// === Common
|
||||
|
||||
public static final int MAX_SIZE = 1_000_000 / TestCounter.DENOMINATOR / TestCounter.DENOMINATOR;
|
||||
|
||||
public static final Selector SELECTOR = new Selector(FastReverseTest.class)
|
||||
.variant("Base", ReverseTester.variant(MAX_SIZE, ReverseTest.REVERSE))
|
||||
.variant("3637", ReverseTester.variant(MAX_SIZE, "", MIN_C, OCT, DEC, PUNCT))
|
||||
.variant("3839", ReverseTester.variant(MAX_SIZE, "", MIN, OCT, DEC, PUNCT))
|
||||
.variant("3435", ReverseTester.variant(MAX_SIZE, ReverseTest.ROTATE, PUNCT))
|
||||
.variant("3233", ReverseTester.variant(MAX_SIZE, ReverseTest.EVEN, PUNCT))
|
||||
.variant("4142", ReverseTester.variant(MAX_SIZE, ReverseTest.AVG, PUNCT))
|
||||
.variant("4749", ReverseTester.variant(MAX_SIZE, ReverseTest.SUM, PUNCT))
|
||||
|
||||
;
|
||||
|
||||
|
||||
private FastReverseTest() {
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
WordStatTest.main(args);
|
||||
}
|
||||
}
|
||||
58
java/reverse/FastScanner.java
Normal file
58
java/reverse/FastScanner.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class FastScanner {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
String line = null;
|
||||
int pos = 0;
|
||||
|
||||
boolean hasNextLine() throws IOException {
|
||||
if (line != null && pos < line.length()) return true;
|
||||
line = br.readLine();
|
||||
pos = 0;
|
||||
return line != null;
|
||||
}
|
||||
|
||||
boolean hasNextInt() {
|
||||
if (line == null) return false;
|
||||
while (pos < line.length() && (Character.isWhitespace(line.charAt(pos)) ||
|
||||
Character.getType(line.charAt(pos)) == Character.START_PUNCTUATION ||
|
||||
Character.getType(line.charAt(pos)) == Character.END_PUNCTUATION)) {
|
||||
pos++;
|
||||
}
|
||||
return pos < line.length() && (Character.isDigit(line.charAt(pos)) || line.charAt(pos) == '-');
|
||||
}
|
||||
|
||||
int nextInt() {
|
||||
while (pos < line.length() && (Character.isWhitespace(line.charAt(pos)) ||
|
||||
Character.getType(line.charAt(pos)) == Character.START_PUNCTUATION ||
|
||||
Character.getType(line.charAt(pos)) == Character.END_PUNCTUATION)) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
int start = pos;
|
||||
boolean negative = line.charAt(pos) == '-';
|
||||
if (negative) pos++;
|
||||
|
||||
while (pos < line.length() && Character.isDigit(line.charAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
for (int i = negative ? start + 1 : start; i < pos; i++) {
|
||||
result = result * 10 + (line.charAt(i) - '0');
|
||||
}
|
||||
return negative ? -result : result;
|
||||
}
|
||||
|
||||
void nextLine() {
|
||||
pos = line.length();
|
||||
}
|
||||
}
|
||||
47
java/reverse/Reverse.java
Normal file
47
java/reverse/Reverse.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Reverse {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
}
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
for (int i = linesCount - 1; i >= 0; i--) {
|
||||
int[] line = lines[i];
|
||||
for (int j = line.length - 1; j >= 0; j--) {
|
||||
if (j < line.length - 1) out.print(" ");
|
||||
out.print(line[j]);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
71
java/reverse/ReverseAvg.java
Normal file
71
java/reverse/ReverseAvg.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ReverseAvg {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
}
|
||||
|
||||
printResult(linesCount, lines);
|
||||
}
|
||||
|
||||
private static void printResult(int linesCount, int[][] lines) {
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
int[] line = lines[i];
|
||||
|
||||
for (int j = 0; j < line.length; j++) {
|
||||
if (j > 0) out.print(" ");
|
||||
|
||||
long sum = 0;
|
||||
long count = 0;
|
||||
|
||||
for (int m = 0; m < lines[i].length; m++) {
|
||||
sum += lines[i][m];
|
||||
count++;
|
||||
}
|
||||
|
||||
for (int k = 0; k < linesCount; k++) {
|
||||
if (k != i && lines[k].length > j) {
|
||||
sum += lines[k][j];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
out.print(sum / count);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
50
java/reverse/ReverseEven.java
Normal file
50
java/reverse/ReverseEven.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ReverseEven {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
}
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
for (int i = linesCount - 1; i >= 0; i--) {
|
||||
int[] line = lines[i];
|
||||
for (int j = line.length - 1; j >= 0; j--) {
|
||||
if ((i + j) % 2 == 0) {
|
||||
if (j < line.length - 1) out.print(" ");
|
||||
out.print(line[j]);
|
||||
}
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
66
java/reverse/ReverseMax.java
Normal file
66
java/reverse/ReverseMax.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ReverseMax {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
}
|
||||
|
||||
PrintWriter out = getPrintWriter(linesCount, lines);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private static PrintWriter getPrintWriter(int linesCount, int[][] lines) {
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
int[] line = lines[i];
|
||||
|
||||
for (int j = 0; j < line.length; j++) {
|
||||
|
||||
if (j > 0) out.print(" ");
|
||||
|
||||
int maxValue = lines[i][j];
|
||||
|
||||
for (int k = i; k < linesCount; k++) {
|
||||
for (int m = j; m < lines[k].length; m++) {
|
||||
if (lines[k][m] > maxValue) {
|
||||
maxValue = lines[k][m];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.print(maxValue);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
65
java/reverse/ReverseMaxC.java
Normal file
65
java/reverse/ReverseMaxC.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ReverseMaxC {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
}
|
||||
|
||||
PrintWriter out = getPrintWriter(linesCount, lines);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private static PrintWriter getPrintWriter(int linesCount, int[][] lines) {
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
int[] line = lines[i];
|
||||
|
||||
for (int j = 0; j < line.length; j++) {
|
||||
|
||||
if (j > 0) out.print(" ");
|
||||
|
||||
int maxRow = lines[i][j];
|
||||
|
||||
for (int k = i + 1; k < linesCount; k++) {
|
||||
if (lines[k].length > j && lines[k][j] > maxRow) {
|
||||
maxRow = lines[k][j];
|
||||
}
|
||||
}
|
||||
|
||||
out.print(maxRow);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
56
java/reverse/ReverseRotate.java
Normal file
56
java/reverse/ReverseRotate.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ReverseRotate {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
int maxCols = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
|
||||
if (count > maxCols) {
|
||||
maxCols = count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
|
||||
for (int j = 0; j < maxCols; j++) {
|
||||
for (int i = linesCount - 1; i >= 0; i--) {
|
||||
if (lines[i].length > j) {
|
||||
out.print(lines[i][j] + " ");
|
||||
}
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
69
java/reverse/ReverseSum.java
Normal file
69
java/reverse/ReverseSum.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package reverse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class ReverseSum {
|
||||
public static void main(String[] args) throws IOException {
|
||||
FastScanner sc = new FastScanner();
|
||||
int[][] lines = new int[8][];
|
||||
int linesCount = 0;
|
||||
|
||||
while (sc.hasNextLine()) {
|
||||
int[] line = new int[8];
|
||||
int count = 0;
|
||||
|
||||
while (sc.hasNextInt()) {
|
||||
if (count >= line.length) {
|
||||
line = Arrays.copyOf(line, line.length * 2);
|
||||
}
|
||||
line[count++] = sc.nextInt();
|
||||
}
|
||||
sc.nextLine();
|
||||
|
||||
line = Arrays.copyOf(line, count);
|
||||
|
||||
if (linesCount >= lines.length) {
|
||||
lines = Arrays.copyOf(lines, lines.length * 2);
|
||||
}
|
||||
lines[linesCount++] = line;
|
||||
}
|
||||
|
||||
printResult(linesCount, lines);
|
||||
}
|
||||
|
||||
private static void printResult(int linesCount, int[][] lines) {
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
int[] line = lines[i];
|
||||
|
||||
for (int j = 0; j < line.length; j++) {
|
||||
if (j > 0) out.print(" ");
|
||||
|
||||
int sum = 0;
|
||||
|
||||
|
||||
for (int m = 0; m < lines[i].length; m++) {
|
||||
sum += lines[i][m];
|
||||
}
|
||||
|
||||
for (int k = 0; k < linesCount; k++) {
|
||||
if (k != i && lines[k].length > j) {
|
||||
sum += lines[k][j];
|
||||
}
|
||||
}
|
||||
|
||||
out.print(sum);
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
165
java/reverse/ReverseTest.java
Normal file
165
java/reverse/ReverseTest.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package reverse;
|
||||
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
import base.TestCounter;
|
||||
import reverse.ReverseTester.Op;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.IntToLongFunction;
|
||||
import java.util.function.LongBinaryOperator;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Tests for {@code Reverse} homework.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ReverseTest {
|
||||
// === Base
|
||||
public static final Named<Op> REVERSE = Named.of("", ReverseTester::transform);
|
||||
|
||||
|
||||
// === Max
|
||||
|
||||
public static final Named<Op> MAX_C = Named.of("MaxC", scan2((a, b) -> b));
|
||||
public static final Named<Op> MAX = Named.of("Max", scan2(Math::max));
|
||||
|
||||
private static Op scan2(final LongBinaryOperator reduce) {
|
||||
return ints -> {
|
||||
// This code is intentionally obscure
|
||||
final int length = Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0);
|
||||
final long[] cs = new long[length];
|
||||
final long[] cc = new long[length + 1];
|
||||
Arrays.fill(cs, Integer.MIN_VALUE);
|
||||
Arrays.fill(cc, Integer.MIN_VALUE);
|
||||
//noinspection NestedAssignment
|
||||
final long[][] rows = range(ints.length).mapToObj(i -> {
|
||||
range(ints[i].length).forEachOrdered(j -> cc[j] = reduce.applyAsLong(
|
||||
cc[j + 1],
|
||||
cs[j] = Math.max(cs[j], ints[i][j])
|
||||
));
|
||||
return Arrays.copyOf(cc, ints[i].length);
|
||||
})
|
||||
.toArray(long[][]::new);
|
||||
return range(ints.length).mapToObj(i -> rows[i]).toArray(long[][]::new);
|
||||
};
|
||||
}
|
||||
|
||||
private static IntStream range(final int length) {
|
||||
return IntStream.iterate(length - 1, i -> i >= 0, i -> i - 1);
|
||||
}
|
||||
|
||||
|
||||
// === Rotate
|
||||
public static final Named<Op> ROTATE = Named.of("Rotate", ints -> {
|
||||
final List<int[]> rows = new ArrayList<>(List.of(ints));
|
||||
return IntStream.range(0, Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0))
|
||||
.mapToObj(c -> {
|
||||
rows.removeIf(r -> r.length <= c);
|
||||
return range(rows.size()).mapToObj(rows::get).mapToLong(r -> r[c]).toArray();
|
||||
})
|
||||
.toArray(long[][]::new);
|
||||
});
|
||||
|
||||
|
||||
// === Even
|
||||
public static final Named<Op> EVEN = Named.of(
|
||||
"Even",
|
||||
ints -> ReverseTester.transform(IntStream.range(0, ints.length)
|
||||
.mapToObj(i -> IntStream.range(0, ints[i].length)
|
||||
.filter(j -> (i + j) % 2 == 0)
|
||||
.map(j -> ints[i][j]))
|
||||
.map(IntStream::toArray).toArray(int[][]::new))
|
||||
);
|
||||
|
||||
// Sum
|
||||
@FunctionalInterface
|
||||
interface LongTernaryOperator {
|
||||
long applyAsLong(long a, long b, long c);
|
||||
}
|
||||
|
||||
public static final Named<Op> SUM = cross("Sum", 0, Long::sum, (r, c, v) -> r + c - v);
|
||||
|
||||
private static long[][] cross(
|
||||
final int[][] ints,
|
||||
final IntToLongFunction map,
|
||||
final LongBinaryOperator reduce,
|
||||
final int zero,
|
||||
final LongTernaryOperator get
|
||||
) {
|
||||
// This code is intentionally obscure
|
||||
final long[] rt = Arrays.stream(ints)
|
||||
.map(Arrays::stream)
|
||||
.mapToLong(row -> row.mapToLong(map).reduce(zero, reduce))
|
||||
.toArray();
|
||||
final long[] ct = new long[Arrays.stream(ints).mapToInt(r -> r.length).max().orElse(0)];
|
||||
Arrays.fill(ct, zero);
|
||||
Arrays.stream(ints).forEach(r -> IntStream.range(0, r.length)
|
||||
.forEach(i -> ct[i] = reduce.applyAsLong(ct[i], map.applyAsLong(r[i]))));
|
||||
return IntStream.range(0, ints.length)
|
||||
.mapToObj(r -> IntStream.range(0, ints[r].length)
|
||||
.mapToLong(c -> get.applyAsLong(rt[r], ct[c], ints[r][c]))
|
||||
.toArray())
|
||||
.toArray(long[][]::new);
|
||||
}
|
||||
|
||||
private static Named<Op> cross(
|
||||
final String name,
|
||||
final int zero,
|
||||
final LongBinaryOperator reduce,
|
||||
final LongTernaryOperator get
|
||||
) {
|
||||
return Named.of(name, ints -> cross(ints, n -> n, reduce, zero, get));
|
||||
}
|
||||
|
||||
public static final Named<Op> AVG = avg(
|
||||
"Avg",
|
||||
ints -> cross(ints, n -> n, Long::sum, 0, (r, c, v) -> r + c - v),
|
||||
ints -> cross(ints, n -> 1, Long::sum, 0, (r1, c1, v1) -> r1 + c1 - 1)
|
||||
);
|
||||
|
||||
private static Named<Op> avg(
|
||||
final String name,
|
||||
final Op fs,
|
||||
final Op fc
|
||||
) {
|
||||
return Named.of(name, ints -> avg(ints, fs.apply(ints), fc.apply(ints)));
|
||||
}
|
||||
|
||||
private static long[][] avg(final int[][] ints, final long[][] as, final long[][] ac) {
|
||||
return IntStream.range(0, ints.length).mapToObj(i -> IntStream.range(0, ints[i].length)
|
||||
.mapToLong(j -> as[i][j] / ac[i][j])
|
||||
.toArray())
|
||||
.toArray(long[][]::new);
|
||||
}
|
||||
|
||||
|
||||
// === Common
|
||||
|
||||
public static final int MAX_SIZE = 10_000 / TestCounter.DENOMINATOR;
|
||||
|
||||
public static final Selector SELECTOR = selector(ReverseTest.class, MAX_SIZE);
|
||||
|
||||
private ReverseTest() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static Selector selector(final Class<?> owner, final int maxSize) {
|
||||
return new Selector(owner)
|
||||
.variant("Base", ReverseTester.variant(maxSize, REVERSE))
|
||||
.variant("3637", ReverseTester.variant(maxSize, MAX_C))
|
||||
.variant("3839", ReverseTester.variant(maxSize, MAX))
|
||||
.variant("3435", ReverseTester.variant(maxSize, ROTATE))
|
||||
.variant("3233", ReverseTester.variant(maxSize, EVEN))
|
||||
.variant("4142", ReverseTester.variant(maxSize, AVG))
|
||||
.variant("4749", ReverseTester.variant(maxSize, SUM))
|
||||
;
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
299
java/reverse/ReverseTester.java
Normal file
299
java/reverse/ReverseTester.java
Normal file
@@ -0,0 +1,299 @@
|
||||
package reverse;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class ReverseTester {
|
||||
|
||||
public static final Named<Op> TRANSFORM = Named.of("", ReverseTester::transform);
|
||||
public static final Named<String> SPACE = Named.of("", " ");
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Op extends Function<int[][], long[][]> {}
|
||||
|
||||
private static final int[] DIVISORS = {100, 10, 1};
|
||||
|
||||
private final Op transform;
|
||||
private final BiFunction<ExtendedRandom, Integer, String> inputToString;
|
||||
private final BiFunction<ExtendedRandom, Integer, String> outputToString;
|
||||
private final String name;
|
||||
private final String spaces;
|
||||
|
||||
private ReverseTester(final String className, final Op transform, final String spaces) {
|
||||
this(className, transform, spaces, (r, i) -> Integer.toString(i), (r, i) -> Long.toString(i));
|
||||
}
|
||||
|
||||
private ReverseTester(
|
||||
final String className,
|
||||
final Op transform,
|
||||
final String spaces,
|
||||
final BiFunction<ExtendedRandom, Integer, String> inputToString,
|
||||
final BiFunction<ExtendedRandom, Integer, String> outputToString
|
||||
) {
|
||||
name = className;
|
||||
this.transform = transform;
|
||||
this.spaces = spaces;
|
||||
this.inputToString = inputToString;
|
||||
this.outputToString = outputToString;
|
||||
}
|
||||
|
||||
private static Consumer<TestCounter> variant(final int maxSize, final Supplier<ReverseTester> tester) {
|
||||
return counter -> tester.get().run(counter, maxSize);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(final int maxSize, final Named<Op> transform) {
|
||||
return variant(maxSize, transform, SPACE);
|
||||
}
|
||||
|
||||
|
||||
public static Consumer<TestCounter> variant(final int maxSize, final Named<Op> transform, final Named<String> spaces) {
|
||||
Objects.requireNonNull(transform);
|
||||
Objects.requireNonNull(spaces);
|
||||
return variant(
|
||||
maxSize,
|
||||
() -> new ReverseTester("Reverse" + transform.name() + spaces.name(), transform.value(), spaces.value())
|
||||
);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(
|
||||
final int maxSize,
|
||||
final String suffix,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> input,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> output
|
||||
) {
|
||||
return variant(maxSize, suffix, TRANSFORM, input, output);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(
|
||||
final int maxSize,
|
||||
final String suffix,
|
||||
final Named<Op> op,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> input,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> output
|
||||
) {
|
||||
return variant(maxSize, suffix, op, input, output, SPACE);
|
||||
}
|
||||
|
||||
public static Consumer<TestCounter> variant(
|
||||
final int maxSize,
|
||||
final String suffix,
|
||||
final Named<Op> op,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> input,
|
||||
final Named<BiFunction<ExtendedRandom, Integer, String>> output,
|
||||
final Named<String> spaces
|
||||
) {
|
||||
final String out = input.name().contains(output.name()) ? "" : output.name();
|
||||
return variant(maxSize, () -> new ReverseTester(
|
||||
"Reverse" + op.name() + input.name() + out + suffix + spaces.name(),
|
||||
op.value(),
|
||||
spaces.value(),
|
||||
input.value(),
|
||||
output.value()
|
||||
));
|
||||
}
|
||||
|
||||
private void run(final TestCounter counter, final int maxSize) {
|
||||
new Checker(counter, maxSize, Runner.packages("", "reverse").std(name), spaces).test();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static long[][] transform(final int[][] ints) {
|
||||
return IntStream.range(1, ints.length + 1)
|
||||
.mapToObj(i -> ints[ints.length - i])
|
||||
.map(is -> IntStream.range(1, is.length + 1).mapToLong(i -> is[is.length - i]).toArray())
|
||||
.toArray(long[][]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
private class Checker extends BaseChecker {
|
||||
private final int maxSize;
|
||||
private final Runner runner;
|
||||
private final String spaces;
|
||||
private final Set<String> manualTests = new HashSet<>();
|
||||
|
||||
Checker(final TestCounter counter, final int maxSize, final Runner runner, final String spaces) {
|
||||
super(counter);
|
||||
this.maxSize = maxSize;
|
||||
this.runner = runner;
|
||||
this.spaces = spaces;
|
||||
}
|
||||
|
||||
public void manualTest(final int[][] ints) {
|
||||
for (final List<int[]> permutation : permutations(new ArrayList<>(Arrays.asList(ints)))) {
|
||||
final int[][] input = permutation.toArray(int[][]::new);
|
||||
final String[][] lines = toString(input, inputToString);
|
||||
if (manualTests.add(Arrays.deepToString(lines))) {
|
||||
test(lines, toString(transform.apply(input), outputToString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void test(final int[][] ints) {
|
||||
test(toString(ints, inputToString), toString(transform.apply(ints), outputToString));
|
||||
}
|
||||
|
||||
public void test(final String[][] input, final String[][] output) {
|
||||
final List<String> inputLines = toLines(input, random().randomString(spaces, 1, 10));
|
||||
final List<String> outputLines = toLines(output, " ");
|
||||
runner.testEquals(counter, inputLines, outputLines);
|
||||
}
|
||||
|
||||
private String[][] toString(final int[][] ints, final BiFunction<ExtendedRandom, Integer, String> toString) {
|
||||
return Arrays.stream(ints)
|
||||
.map(row -> Arrays.stream(row).mapToObj(i -> toString.apply(random(), i)).toArray(String[]::new))
|
||||
.toArray(String[][]::new);
|
||||
}
|
||||
|
||||
private String[][] toString(final long[][] ints, final BiFunction<ExtendedRandom, Integer, String> toString) {
|
||||
return Arrays.stream(ints)
|
||||
.map(row -> Arrays.stream(row).mapToObj(i -> toString.apply(random(), (int) i)).toArray(String[]::new))
|
||||
.toArray(String[][]::new);
|
||||
}
|
||||
|
||||
private List<String> toLines(final String[][] data, final String delimiter) {
|
||||
if (data.length == 0) {
|
||||
return Collections.singletonList("");
|
||||
}
|
||||
return Arrays.stream(data)
|
||||
.map(row -> String.join(delimiter, row))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int[][] random(final int[] profile) {
|
||||
final int col = random().nextInt(Arrays.stream(profile).max().orElse(0));
|
||||
final int row = random().nextInt(profile.length);
|
||||
final int m = random().nextInt(5) - 2;
|
||||
final int[][] ints = Arrays.stream(profile).mapToObj(random().getRandom()::ints).map(IntStream::toArray).toArray(int[][]::new);
|
||||
Arrays.stream(ints).filter(r -> col < r.length).forEach(r -> r[col] = Math.abs(r[col]) / 2 * m);
|
||||
ints[row] = Arrays.stream(ints[row]).map(Math::abs).map(v -> v / 2 * m).toArray();
|
||||
return ints;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
manualTest(new int[][]{
|
||||
{1}
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1, 2},
|
||||
{3}
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1, 2, 3},
|
||||
{4, 5},
|
||||
{6}
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1, 2, 3},
|
||||
{},
|
||||
{4, 5},
|
||||
{6}
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1, 2, 3},
|
||||
{-4, -5},
|
||||
{6}
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1, -2, 3},
|
||||
{},
|
||||
{4, -5},
|
||||
{6}
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1, 2, 0},
|
||||
{1, 0},
|
||||
{0},
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{1},
|
||||
{1, 3},
|
||||
{1, 2, 3},
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{-1},
|
||||
{-1, -2},
|
||||
{-1, -2, -3},
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{},
|
||||
});
|
||||
manualTest(new int[][]{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
testRandom(tweakProfile(constProfile(10, 10), new int[][]{}));
|
||||
testRandom(tweakProfile(constProfile(100, 100), new int[][]{}));
|
||||
testRandom(randomProfile(100, maxSize));
|
||||
testRandom(randomProfile(maxSize / 10, maxSize));
|
||||
testRandom(randomProfile(maxSize, maxSize));
|
||||
for (final int d : DIVISORS) {
|
||||
final int size = maxSize / d;
|
||||
testRandom(tweakProfile(constProfile(size / 2, 0), new int[][]{{size / 2, 0}}));
|
||||
testRandom(tweakProfile(randomProfile(size, size / 2), new int[][]{{size / 2, 0}}));
|
||||
testRandom(tweakProfile(constProfile(size / 2, 0), new int[][]{{size / 2, size / 2 - 1}}));
|
||||
testRandom(tweakProfile(constProfile(size / 3, 1), new int[][]{{size / 3, size / 6, size / 3 - 1}}));
|
||||
}
|
||||
}
|
||||
|
||||
private int[] randomProfile(final int length, final int values) {
|
||||
final int[] profile = new int[length];
|
||||
for (int i = 0; i < values; i++) {
|
||||
profile[random().nextInt(0, length - 1)]++;
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
private void testRandom(final int[] profile) {
|
||||
test(random(profile));
|
||||
}
|
||||
|
||||
private int[] constProfile(final int length, final int value) {
|
||||
final int[] profile = new int[length];
|
||||
Arrays.fill(profile, value);
|
||||
return profile;
|
||||
}
|
||||
|
||||
private int[] tweakProfile(final int[] profile, final int[][] mods) {
|
||||
for (final int[] mod : mods) {
|
||||
Arrays.stream(mod).skip(1).forEach(i -> profile[i] = mod[0]);
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> List<List<T>> permutations(final List<T> elements) {
|
||||
final List<List<T>> result = new ArrayList<>();
|
||||
permutations(new ArrayList<>(elements), result, elements.size() - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static <T> void permutations(final List<T> elements, final List<List<T>> result, final int n) {
|
||||
if (n == 0) {
|
||||
result.add(List.copyOf(elements));
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
permutations(elements, result, n - 1);
|
||||
if (n % 2 == 1) {
|
||||
Collections.swap(elements, i, n);
|
||||
} else {
|
||||
Collections.swap(elements, 0, n);
|
||||
}
|
||||
}
|
||||
permutations(elements, result, n - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/reverse/package-info.java
Normal file
7
java/reverse/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#reverse">Reverse</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package reverse;
|
||||
28
java/sum/Sum.java
Normal file
28
java/sum/Sum.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package sum;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Sum {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int res = 0;
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
res += (!builder.toString().isEmpty())
|
||||
? Integer.parseInt(builder.toString())
|
||||
: 0;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res += (!builder.toString().isEmpty())
|
||||
? Integer.parseInt(builder.toString())
|
||||
: 0;
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
}
|
||||
63
java/sum/SumBigDecimalHex.java
Normal file
63
java/sum/SumBigDecimalHex.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package sum;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class SumBigDecimalHex {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BigDecimal res = new BigDecimal("0");
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int idxOfS = -1;
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
if (c == 's' || c == 'S') {
|
||||
idxOfS = builder.length();
|
||||
}
|
||||
builder.append(c);
|
||||
} else {
|
||||
res = res.add(compute(builder.toString(), idxOfS));
|
||||
idxOfS = -1;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res = res.add(compute(builder.toString(), idxOfS));
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
|
||||
static BigDecimal compute(String num, int idxOfS) {
|
||||
BigDecimal res = BigDecimal.ZERO;
|
||||
|
||||
if (num == null || num.isEmpty()) {
|
||||
return res;
|
||||
} else if (num.startsWith("0x") || num.startsWith("0X")) {
|
||||
num = num.toLowerCase();
|
||||
num = num.substring(2);
|
||||
if (idxOfS != -1) {
|
||||
idxOfS -= 2;
|
||||
String mantissaHex = num.substring(0, idxOfS);
|
||||
String exponentHex = num.substring(idxOfS + 1);
|
||||
|
||||
BigInteger mantissa = new BigInteger(mantissaHex, 16);
|
||||
BigInteger exponent = new BigInteger(exponentHex, 16);
|
||||
|
||||
res = res.add(
|
||||
new BigDecimal(mantissa).scaleByPowerOfTen(
|
||||
exponent.negate().intValueExact()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
res = res.add(new BigDecimal(new BigInteger(num, 16)));
|
||||
}
|
||||
} else {
|
||||
res = new BigDecimal(num);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
45
java/sum/SumBigIntegerOctal.java
Normal file
45
java/sum/SumBigIntegerOctal.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package sum;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class SumBigIntegerOctal {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BigInteger res = new BigInteger("0");
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
res = res.add(compute(builder.toString()));
|
||||
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res = res.add(compute(builder.toString()));
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
|
||||
static BigInteger compute(String num) {
|
||||
BigInteger res = new BigInteger("0");
|
||||
int numLength = num.length();
|
||||
if (num.isEmpty()) {
|
||||
res = res.add(BigInteger.ZERO);
|
||||
} else if (
|
||||
num.charAt(numLength - 1) == 'o' || num.charAt(numLength - 1) == 'O'
|
||||
) {
|
||||
res = res.add(
|
||||
new BigInteger(num.substring(0, num.length() - 1), 8)
|
||||
);
|
||||
} else {
|
||||
res = res.add(new BigInteger(num));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
28
java/sum/SumDouble.java
Normal file
28
java/sum/SumDouble.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package sum;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class SumDouble {
|
||||
|
||||
public static void main(String[] args) {
|
||||
double res = 0.0;
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
res += (!builder.toString().isEmpty())
|
||||
? Double.parseDouble(builder.toString())
|
||||
: 0;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res += (!builder.toString().isEmpty())
|
||||
? Double.parseDouble(builder.toString())
|
||||
: 0;
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
}
|
||||
45
java/sum/SumDoubleHex.java
Normal file
45
java/sum/SumDoubleHex.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package sum;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class SumDoubleHex {
|
||||
|
||||
public static void main(String[] args) {
|
||||
double res = 0;
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean containsDot = false;
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
builder.append(c);
|
||||
if (c == '.') {
|
||||
containsDot = true;
|
||||
}
|
||||
} else {
|
||||
res += compute(builder.toString(), containsDot);
|
||||
containsDot = false;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res += compute(builder.toString(), containsDot);
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
|
||||
static double compute(String num, boolean containsDot) {
|
||||
double res = 0;
|
||||
if (num.isEmpty()) {
|
||||
res += 0;
|
||||
} else if (
|
||||
num.charAt(0) == '0' &&
|
||||
(num.charAt(1) == 'x' || num.charAt(1) == 'X')
|
||||
) {
|
||||
res += (containsDot) ? Double.parseDouble(num) : Long.decode(num);
|
||||
} else {
|
||||
res += Double.parseDouble(num);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
42
java/sum/SumHex.java
Normal file
42
java/sum/SumHex.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package sum;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class SumHex {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int res = 0;
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
res += compute(builder.toString());
|
||||
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res += compute(builder.toString());
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
|
||||
static int compute(String num) {
|
||||
int res = 0;
|
||||
if (num.isEmpty()) {
|
||||
res += 0;
|
||||
} else if (
|
||||
num.length() >= 2 &&
|
||||
num.charAt(0) == '0' &&
|
||||
(num.charAt(1) == 'x' || num.charAt(1) == 'X')
|
||||
) {
|
||||
res += Long.decode(num);
|
||||
} else {
|
||||
res += Integer.parseInt(num);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
45
java/sum/SumLongOctal.java
Normal file
45
java/sum/SumLongOctal.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package sum;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class SumLongOctal {
|
||||
|
||||
public static void main(String[] args) {
|
||||
long res = 0;
|
||||
for (String arg : args) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : arg.toCharArray()) {
|
||||
if (!Character.isWhitespace(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
res += compute(builder.toString());
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
}
|
||||
res += compute(builder.toString());
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
|
||||
static long compute(String num) {
|
||||
if (num.isEmpty()) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
int numLength = num.length();
|
||||
|
||||
if (
|
||||
num.charAt(numLength - 1) == 'o' || num.charAt(numLength - 1) == 'O'
|
||||
) {
|
||||
return new BigInteger(
|
||||
num.substring(0, num.length() - 1),
|
||||
8
|
||||
).longValue();
|
||||
} else {
|
||||
return Long.parseLong(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
178
java/sum/SumTest.java
Normal file
178
java/sum/SumTest.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package sum;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Locale;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class SumTest {
|
||||
// === Base
|
||||
|
||||
@FunctionalInterface
|
||||
/* package-private */ interface Op<T extends Number> extends UnaryOperator<SumTester<T>> {}
|
||||
|
||||
private static final BiConsumer<Number, String> TO_STRING = (expected, out) -> Asserts.assertEquals("Sum", expected.toString(), out);
|
||||
|
||||
private static final Named<Supplier<SumTester<Integer>>> BASE = Named.of("", () -> new SumTester<>(
|
||||
Integer::sum, n -> (int) n, (r, max) -> r.nextInt() % max, TO_STRING,
|
||||
10, 100, Integer.MAX_VALUE
|
||||
));
|
||||
|
||||
/* package-private */ static <T extends Number> Named<Op<T>> plain() {
|
||||
return Named.of("", test -> test);
|
||||
}
|
||||
|
||||
|
||||
// === DoubleHex
|
||||
|
||||
private static BiConsumer<Number, String> approximate(final Function<String, Number> parser, final double precision) {
|
||||
return (expected, out) ->
|
||||
Asserts.assertEquals("Sum", expected.doubleValue(), parser.apply(out).doubleValue(), precision);
|
||||
}
|
||||
|
||||
private static final Named<Supplier<SumTester<Double>>> DOUBLE = Named.of("Double", () -> new SumTester<>(
|
||||
Double::sum, n -> (double) n, (r, max) -> (r.getRandom().nextDouble() - 0.5) * 2 * max,
|
||||
approximate(Double::parseDouble, 1e-10),
|
||||
10.0, 0.01, 1e20, 1e100, Double.MAX_VALUE / 10000)
|
||||
.test(5, "2.5 2.5")
|
||||
.test(0, "1e100 -1e100")
|
||||
.testT(2e100, "1.5e100 0.5e100"));
|
||||
|
||||
private static <T extends Number> Named<Op<T>> hexFull(final Function<T, String> toHex) {
|
||||
final Function<T, String> toHexSpoiled = toHex.andThen(s ->s.chars()
|
||||
.map(ch -> ((ch ^ s.hashCode()) & 1) == 0 ? Character.toLowerCase(ch) : Character.toUpperCase(ch))
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining()));
|
||||
return Named.of("Hex", test -> test
|
||||
.test(toHex, 1)
|
||||
.test(toHex, 0x1a)
|
||||
.test(toHexSpoiled, 0xA2)
|
||||
.test(toHexSpoiled, 0X0, 0X1, 0XF, 0XF, 0x0, 0x1, 0xF, 0xf)
|
||||
.test(toHexSpoiled, 0x12345678)
|
||||
.test(toHexSpoiled, 0x09abcdef)
|
||||
.test(toHexSpoiled, 0x3CafeBab)
|
||||
.test(toHexSpoiled, 0x3DeadBee)
|
||||
|
||||
.test(toHex, Integer.MAX_VALUE)
|
||||
.test(toHex, Integer.MIN_VALUE)
|
||||
.setToString(number -> {
|
||||
final int hashCode = number.hashCode();
|
||||
if ((hashCode & 1) == 0) {
|
||||
return number.toString();
|
||||
}
|
||||
|
||||
return toHexSpoiled.apply(number);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === Octal
|
||||
private static <T extends Number> Named<Op<T>> octal(final Function<T, String> toOctal) {
|
||||
//noinspection OctalInteger,StringConcatenationMissingWhitespace
|
||||
return Named.of("Octal", test -> test
|
||||
.test(1, "1o")
|
||||
.test(017, "17o")
|
||||
.testSpaces(6, " 1o 2o 3O ")
|
||||
.test(01234567, "1234567O")
|
||||
|
||||
.test(Integer.MIN_VALUE, "-0" + String.valueOf(Integer.MIN_VALUE).substring(1))
|
||||
.test(Integer.MAX_VALUE, "0" + Integer.MAX_VALUE)
|
||||
.test(Integer.MAX_VALUE, Integer.toOctalString(Integer.MAX_VALUE) + "o")
|
||||
.test(Integer.MAX_VALUE, "0" + Integer.toOctalString(Integer.MAX_VALUE) + "O")
|
||||
.setToString(number -> {
|
||||
final int hashCode = number.hashCode();
|
||||
if ((hashCode & 1) == 0) {
|
||||
return number.toString();
|
||||
}
|
||||
|
||||
final String lower = toOctal.apply(number).toLowerCase(Locale.ROOT) + "o";
|
||||
return (hashCode & 2) == 0 ? lower : lower.toUpperCase(Locale.ROOT);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === Long
|
||||
|
||||
private static final Named<Supplier<SumTester<Long>>> LONG = Named.of("Long", () -> new SumTester<>(
|
||||
Long::sum, n -> n, (r, max) -> r.getRandom().nextLong() % max, TO_STRING,
|
||||
10L, 100L, (long) Integer.MAX_VALUE, Long.MAX_VALUE)
|
||||
.test(12345678901234567L, " +12345678901234567 ")
|
||||
.test(0L, " +12345678901234567 -12345678901234567")
|
||||
.test(0L, " +12345678901234567 -12345678901234567"));
|
||||
|
||||
// === BigInteger
|
||||
|
||||
private static final Named<Supplier<SumTester<BigInteger>>> BIG_INTEGER = Named.of("BigInteger", () -> new SumTester<>(
|
||||
BigInteger::add, BigInteger::valueOf, (r, max) -> new BigInteger(max.bitLength(), r.getRandom()), TO_STRING,
|
||||
BigInteger.TEN, BigInteger.TEN.pow(10), BigInteger.TEN.pow(100), BigInteger.TWO.pow(1000))
|
||||
.test(0, "10000000000000000000000000000000000000000 -10000000000000000000000000000000000000000"));
|
||||
|
||||
|
||||
// === BigDecimalHex
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
private static final Named<Supplier<SumTester<BigDecimal>>> BIG_DECIMAL = Named.of("BigDecimal", () -> new SumTester<>(
|
||||
BigDecimal::add, BigDecimal::valueOf,
|
||||
(r, max) -> {
|
||||
final BigInteger unscaled = new BigInteger((max.precision() - max.scale() + 2) * 3, r.getRandom());
|
||||
return new BigDecimal(unscaled, 3);
|
||||
},
|
||||
TO_STRING,
|
||||
BigDecimal.TEN, BigDecimal.TEN.pow(10), BigDecimal.TEN.pow(100), BigDecimal.ONE.add(BigDecimal.ONE).pow(1000))
|
||||
.testT(BigDecimal.ZERO.setScale(3), "10000000000000000000000000000000000000000.123 -10000000000000000000000000000000000000000.123"));
|
||||
|
||||
private static String bigDecimalToString(final BigDecimal number) {
|
||||
final int scale = number.scale();
|
||||
return "0x" + number.unscaledValue().toString(16) + (scale == 0 ? "" : "s" + Integer.toString(scale, 16));
|
||||
}
|
||||
|
||||
|
||||
// === Hex
|
||||
|
||||
private static <T extends Number> Named<Op<T>> hex(final Function<T, String> toHex) {
|
||||
return hexFull(v -> "0x" + toHex.apply(v));
|
||||
}
|
||||
|
||||
|
||||
// === Common
|
||||
|
||||
/* package-private */ static <T extends Number> Consumer<TestCounter> variant(
|
||||
final Named<Function<String, Runner>> runner,
|
||||
final Named<Supplier<SumTester<T>>> test,
|
||||
final Named<? extends Function<? super SumTester<T>, ? extends SumTester<?>>> modifier
|
||||
) {
|
||||
return counter -> modifier.value().apply(test.value().get())
|
||||
.test("Sum" + test.name() + modifier.name() + runner.name(), counter, runner.value());
|
||||
}
|
||||
|
||||
/* package-private */ static final Named<Function<String, Runner>> RUNNER =
|
||||
Named.of("", Runner.packages("", "sum")::args);
|
||||
|
||||
public static final Selector SELECTOR = selector(SumTest.class, RUNNER);
|
||||
|
||||
private SumTest() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static Selector selector(final Class<?> owner, final Named<Function<String, Runner>> runner) {
|
||||
return new Selector(owner)
|
||||
.variant("Base", variant(runner, BASE, plain()))
|
||||
.variant("3637", variant(runner, DOUBLE, hexFull(value -> value == value.intValue() && value > 0 ? "0x" + Integer.toHexString(value.intValue()) : Double.toHexString(value))))
|
||||
.variant("3839", variant(runner, BIG_DECIMAL, hexFull(SumTest::bigDecimalToString)))
|
||||
.variant("3435", variant(runner, BASE, hex(Integer::toHexString)))
|
||||
.variant("3233", variant(runner, DOUBLE, plain()))
|
||||
.variant("4749", variant(runner, LONG, octal(Long::toOctalString)))
|
||||
.variant("4142", variant(runner, BIG_INTEGER, octal(number -> number.toString(8))))
|
||||
;
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
189
java/sum/SumTester.java
Normal file
189
java/sum/SumTester.java
Normal file
@@ -0,0 +1,189 @@
|
||||
package sum;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.LongStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class SumTester<T extends Number> {
|
||||
private static final List<String> SPACES = List.of(
|
||||
" \t\n\u000B\u2029\f",
|
||||
IntStream.rangeClosed(0, Character.MAX_VALUE)
|
||||
.filter(Character::isWhitespace)
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining())
|
||||
);
|
||||
|
||||
private final BinaryOperator<T> add;
|
||||
private final LongFunction<T> fromLong;
|
||||
private BiFunction<ExtendedRandom, T, String> toString;
|
||||
private final BiFunction<ExtendedRandom, T, T> randomValue;
|
||||
private final BiConsumer<Number, String> verifier;
|
||||
private List<String> spaces;
|
||||
private final List<T> limits;
|
||||
|
||||
private final List<Consumer<Checker>> tests = new ArrayList<>();
|
||||
|
||||
@SafeVarargs
|
||||
public SumTester(
|
||||
final BinaryOperator<T> add,
|
||||
final LongFunction<T> fromLong,
|
||||
final BiFunction<ExtendedRandom, T, T> randomValue,
|
||||
final BiConsumer<Number, String> verifier,
|
||||
final T... limits
|
||||
) {
|
||||
this.add = add;
|
||||
this.fromLong = fromLong;
|
||||
this.randomValue = randomValue;
|
||||
this.verifier = verifier;
|
||||
this.limits = List.of(limits);
|
||||
|
||||
setSpaces(SPACES);
|
||||
setToString(String::valueOf);
|
||||
|
||||
test(1, "1");
|
||||
test(6, "1", "2", "3");
|
||||
test(1, " 1");
|
||||
test(1, "1 ");
|
||||
test(1, " 1 ");
|
||||
test(12345, " 12345 ");
|
||||
test(60, "010", "020", "030");
|
||||
testSpaces(1368, " 123 456 789 ");
|
||||
test(-1, "-1");
|
||||
test(-6, "-1", "-2", "-3");
|
||||
test(-12345, " -12345 ");
|
||||
testSpaces(-1368, " -123 -456 -789 ");
|
||||
test(1, "+1");
|
||||
test(6, "+1", "+2", "+3");
|
||||
test(12345, " +12345 ");
|
||||
testSpaces(1368, " +123 +456 +789 ");
|
||||
test(0);
|
||||
testSpaces(0, " ", " ");
|
||||
}
|
||||
|
||||
protected SumTester<T> setSpaces(final List<String> spaces) {
|
||||
this.spaces = spaces;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected SumTester<T> addSpaces(final String... spaces) {
|
||||
this.spaces = Stream.concat(this.spaces.stream(), Arrays.stream(spaces)).toList();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SumTester<T> setToString(final Function<T, String> toString) {
|
||||
return setToString((r, n) -> toString.apply(n));
|
||||
}
|
||||
|
||||
public SumTester<T> setToString(final BiFunction<ExtendedRandom, T, String> toString) {
|
||||
this.toString = toString;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected SumTester<T> test(final Function<T, String> toString, final long... input) {
|
||||
return testT(
|
||||
fromLong.apply(LongStream.of(input).sum()),
|
||||
LongStream.of(input).mapToObj(fromLong).map(toString).collect(Collectors.joining(" "))
|
||||
);
|
||||
}
|
||||
|
||||
protected SumTester<T> test(final long result, final String... input) {
|
||||
return testT(fromLong.apply(result), input);
|
||||
}
|
||||
|
||||
protected SumTester<T> testT(final T result, final String... input) {
|
||||
return testT(result, Arrays.asList(input));
|
||||
}
|
||||
|
||||
private SumTester<T> testT(final T result, final List<String> input) {
|
||||
tests.add(checker -> checker.test(result, input));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SumTester<T> testSpaces(final long result, final String... input) {
|
||||
final T res = fromLong.apply(result);
|
||||
tests.add(checker -> spaces.stream()
|
||||
.flatMapToInt(String::chars)
|
||||
.forEach(space -> checker.test(
|
||||
res,
|
||||
Functional.map(Arrays.asList(input), s -> s.replace(' ', (char) space))
|
||||
))
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void test(final String name, final TestCounter counter, final Function<String, Runner> runner) {
|
||||
new Checker(counter, runner.apply(name)).test();
|
||||
}
|
||||
|
||||
private class Checker extends BaseChecker {
|
||||
private final Runner runner;
|
||||
|
||||
public Checker(final TestCounter counter, final Runner runner) {
|
||||
super(counter);
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
tests.forEach(test -> test.accept(this));
|
||||
|
||||
for (final T limit : limits) {
|
||||
for (int n = 10; n <= 10_000 / TestCounter.DENOMINATOR; n *= 10) {
|
||||
randomTest(n, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void test(final T result, final List<String> input) {
|
||||
counter.test(() -> {
|
||||
final List<String> out = runner.run(counter, input);
|
||||
Asserts.assertEquals("Single line expected", 1, out.size());
|
||||
verifier.accept(result, out.get(0));
|
||||
});
|
||||
}
|
||||
|
||||
private void randomTest(final int numbers, final T max) {
|
||||
for (final String spaces : spaces) {
|
||||
randomTest(numbers, max, spaces);
|
||||
}
|
||||
}
|
||||
|
||||
private void randomTest(final int numbers, final T max, final String spaces) {
|
||||
final List<T> values = new ArrayList<>();
|
||||
for (int i = 0; i < numbers; i++) {
|
||||
values.add(randomValue.apply(random(), max));
|
||||
}
|
||||
testRandom(values.stream().reduce(fromLong.apply(0), add), values, spaces);
|
||||
}
|
||||
|
||||
private void testRandom(final T result, final List<T> args, final String spaces) {
|
||||
final List<String> spaced = args.stream()
|
||||
.map(n -> toString.apply(random(), n))
|
||||
.map(value -> randomSpace(spaces) + value + randomSpace(spaces))
|
||||
.toList();
|
||||
final List<String> argsList = new ArrayList<>();
|
||||
for (final Iterator<String> i = spaced.listIterator(); i.hasNext(); ) {
|
||||
final StringBuilder next = new StringBuilder(i.next());
|
||||
while (i.hasNext() && random().nextBoolean()) {
|
||||
next.append(randomSpace(spaces)).append(i.next());
|
||||
}
|
||||
argsList.add(next.toString());
|
||||
}
|
||||
test(result, argsList);
|
||||
}
|
||||
|
||||
private String randomSpace(final String spaces) {
|
||||
return random().randomString(spaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/sum/package-info.java
Normal file
7
java/sum/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#sum">Sum</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package sum;
|
||||
15
java/wordStat/WordInfo.java
Normal file
15
java/wordStat/WordInfo.java
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordInfo {
|
||||
|
||||
String word;
|
||||
int count;
|
||||
int firstIndex;
|
||||
|
||||
WordInfo(String word, int count, int firstIndex) {
|
||||
this.word = word;
|
||||
this.count = count;
|
||||
this.firstIndex = firstIndex;
|
||||
}
|
||||
}
|
||||
68
java/wordStat/WordStat.java
Normal file
68
java/wordStat/WordStat.java
Normal file
@@ -0,0 +1,68 @@
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordStat {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("incorrect input!");
|
||||
System.err.println("usage: java WordStat <inputFile> <outputFile>");
|
||||
}
|
||||
|
||||
String inputFileName = args[0];
|
||||
String outputFileName = args[1];
|
||||
try {
|
||||
BufferedReader r = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
|
||||
LinkedHashMap<String, Integer> wordCount = new LinkedHashMap<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int data = r.read();
|
||||
while (data != -1) {
|
||||
char c = (char) data;
|
||||
|
||||
if (
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION ||
|
||||
Character.isLetter(c) ||
|
||||
c == '\''
|
||||
) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
if (!sb.isEmpty()) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
wordCount.put(
|
||||
word,
|
||||
wordCount.getOrDefault(word, 0) + 1
|
||||
);
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
data = r.read();
|
||||
}
|
||||
|
||||
r.close();
|
||||
|
||||
PrintWriter writer = new PrintWriter(
|
||||
outputFileName,
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
|
||||
for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
int value = entry.getValue();
|
||||
writer.println(key + " " + value);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("An error occured: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
127
java/wordStat/WordStatChecker.java
Normal file
127
java/wordStat/WordStatChecker.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package wordStat;
|
||||
|
||||
import base.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WordStatChecker extends BaseChecker {
|
||||
public static final String DASH = "-֊־‒–—―⸗⸚⸺〰゠︱︲﹘﹣-'";
|
||||
public static final String SIMPLE_DELIMITERS = " \t";
|
||||
public static final String ADVANCED_DELIMITERS = " \t!\"#%&()*+,./:;<=>?@[\\]^`{|}~ ¡¦§¨©«¬\u00AD®¯°±²³´¶·¸¹»¼½¾¿×÷˂˃˄˅˒˓˔˕˖˗˘˙˚˛˜˝";
|
||||
public static final String ALL = ExtendedRandom.RUSSIAN + ExtendedRandom.ENGLISH + ExtendedRandom.GREEK + DASH;
|
||||
private static final Pattern PATTERN = Pattern.compile("[^\\p{IsLetter}'\\p{Pd}]+");
|
||||
public static final Runner.Packages RUNNER = Runner.packages("", "wordstat", "wspp");
|
||||
|
||||
private final Function<String[][], ? extends List<? extends Pair<?, ?>>> processor;
|
||||
|
||||
private final MainChecker main;
|
||||
|
||||
private WordStatChecker(
|
||||
final String className,
|
||||
final Function<String[][], ? extends List<? extends Pair<?, ?>>> processor,
|
||||
final TestCounter counter
|
||||
) {
|
||||
super(counter);
|
||||
main = new MainChecker(RUNNER.files(className));
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
public static void test(
|
||||
final TestCounter counter,
|
||||
final String className,
|
||||
final Function<String[][], ? extends List<? extends Pair<?, ?>>> processor,
|
||||
final Consumer<WordStatChecker> tests
|
||||
) {
|
||||
tests.accept(new WordStatChecker(className, processor, counter));
|
||||
}
|
||||
|
||||
public void test(final String... lines) {
|
||||
test(PATTERN, lines);
|
||||
}
|
||||
|
||||
public void test(final Pattern pattern, final String... lines) {
|
||||
final String[][] data = Arrays.stream(lines)
|
||||
.map(line -> Arrays.stream(pattern.split(line)).filter(Predicate.not(String::isEmpty)).toArray(String[]::new))
|
||||
.toArray(String[][]::new);
|
||||
test(lines, processor.apply(data));
|
||||
}
|
||||
|
||||
private void randomTest(
|
||||
final int wordLength,
|
||||
final int totalWords,
|
||||
final int wordsPerLine,
|
||||
final int lines,
|
||||
final String chars,
|
||||
final String delimiters,
|
||||
final Function<String[][], List<? extends Pair<?, ?>>> processor
|
||||
) {
|
||||
final String[] words = generateWords(wordLength, totalWords, chars);
|
||||
final String[][] text = generateTest(lines, words, wordsPerLine);
|
||||
test(input(text, delimiters), processor.apply(text));
|
||||
}
|
||||
|
||||
public void randomTest(
|
||||
final int wordLength,
|
||||
final int totalWords,
|
||||
final int wordsPerLine,
|
||||
final int lines,
|
||||
final String chars,
|
||||
final String delimiters
|
||||
) {
|
||||
randomTest(wordLength, totalWords, wordsPerLine, lines, chars, delimiters, processor::apply);
|
||||
}
|
||||
|
||||
private void test(final String[] text, final List<? extends Pair<?, ?>> expected) {
|
||||
final List<String> expectedList = expected.stream()
|
||||
.map(p -> p.first() + " " + p.second())
|
||||
.collect(Collectors.toList());
|
||||
main.testEquals(counter, Arrays.asList(text), expectedList);
|
||||
}
|
||||
|
||||
public void test(final String[][] text, final String delimiters, final List<Pair<String, Integer>> answer) {
|
||||
test(input(text, delimiters), answer);
|
||||
}
|
||||
|
||||
private String[] generateWords(final int wordLength, final int totalWords, final String chars) {
|
||||
final String allChars = chars.chars().anyMatch(Character::isUpperCase)
|
||||
? chars : chars + chars.toUpperCase(Locale.ROOT);
|
||||
return IntStream.range(0, totalWords)
|
||||
.mapToObj(i -> random().randomString(allChars, wordLength / 2, wordLength))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private String[][] generateTest(final int lines, final String[] words, final int wordsPerLine) {
|
||||
final String[][] text = new String[lines][];
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
text[i] = new String[random().nextInt(wordsPerLine / 2, wordsPerLine)];
|
||||
for (int j = 0; j < text[i].length; j++) {
|
||||
text[i][j] = random().randomItem(words);
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private String[] input(final String[][] text, final String delimiters) {
|
||||
final String[] input = new String[text.length];
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
final String[] line = text[i];
|
||||
final StringBuilder sb = new StringBuilder(random().randomString(delimiters));
|
||||
for (final String word : line) {
|
||||
sb.append(word).append(random().randomString(delimiters));
|
||||
}
|
||||
input[i] = sb.toString();
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
83
java/wordStat/WordStatLength.java
Normal file
83
java/wordStat/WordStatLength.java
Normal file
@@ -0,0 +1,83 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordStatLength {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("incorrect input!");
|
||||
System.err.println(
|
||||
"usage: java WordStatLength <inputFile> <outputFile>"
|
||||
);
|
||||
}
|
||||
|
||||
String inputFileName = args[0];
|
||||
String outputFileName = args[1];
|
||||
try {
|
||||
BufferedReader r = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
|
||||
Map<String, WordInfo> wordMap = new HashMap<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int wordIndex = 0;
|
||||
|
||||
int data = r.read();
|
||||
while (data != -1) {
|
||||
char c = (char) data;
|
||||
|
||||
if (
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION ||
|
||||
Character.isLetter(c) ||
|
||||
c == '\''
|
||||
) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (wordMap.containsKey(word)) {
|
||||
wordMap.get(word).count++;
|
||||
} else {
|
||||
wordMap.put(word, new WordInfo(word, 1, wordIndex));
|
||||
wordIndex++;
|
||||
}
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
data = r.read();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (wordMap.containsKey(word)) {
|
||||
wordMap.get(word).count++;
|
||||
} else {
|
||||
wordMap.put(word, new WordInfo(word, 1, wordIndex));
|
||||
}
|
||||
}
|
||||
|
||||
r.close();
|
||||
|
||||
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
|
||||
sortedWords.sort(
|
||||
Comparator.comparingInt((WordInfo w) ->
|
||||
w.word.length()
|
||||
).thenComparingInt(w -> w.firstIndex)
|
||||
);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
|
||||
|
||||
for (WordInfo info : sortedWords) {
|
||||
writer.println(info.word + " " + info.count);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("An error occured: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
117
java/wordStat/WordStatLengthAffix.java
Normal file
117
java/wordStat/WordStatLengthAffix.java
Normal file
@@ -0,0 +1,117 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordStatLengthAffix {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("incorrect input!");
|
||||
System.err.println(
|
||||
"usage: java WordStatLengthAffix <inputFile> <outputFile>"
|
||||
);
|
||||
}
|
||||
|
||||
String inputFileName = args[0];
|
||||
String outputFileName = args[1];
|
||||
try {
|
||||
BufferedReader r = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
|
||||
Map<String, WordInfo> wordMap = new HashMap<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int wordIndex = 0;
|
||||
|
||||
int data = r.read();
|
||||
while (data != -1) {
|
||||
char c = (char) data;
|
||||
|
||||
if (
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION ||
|
||||
Character.isLetter(c) ||
|
||||
c == '\''
|
||||
) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() != 1) {
|
||||
String prefix = word.substring(
|
||||
0,
|
||||
word.length() / 2
|
||||
);
|
||||
String suffix = word.substring(
|
||||
word.length() - word.length() / 2
|
||||
);
|
||||
if (wordMap.containsKey(prefix)) {
|
||||
wordMap.get(prefix).count++;
|
||||
} else {
|
||||
wordMap.put(
|
||||
prefix,
|
||||
new WordInfo(prefix, 1, wordIndex)
|
||||
);
|
||||
wordIndex++;
|
||||
}
|
||||
if (wordMap.containsKey(suffix)) {
|
||||
wordMap.get(suffix).count++;
|
||||
} else {
|
||||
wordMap.put(
|
||||
suffix,
|
||||
new WordInfo(suffix, 1, wordIndex)
|
||||
);
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
data = r.read();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() != 1) {
|
||||
String prefix = word.substring(0, word.length() / 2);
|
||||
String suffix = word.substring(
|
||||
word.length() - word.length() / 2
|
||||
);
|
||||
if (wordMap.containsKey(prefix)) {
|
||||
wordMap.get(prefix).count++;
|
||||
} else {
|
||||
wordMap.put(prefix, new WordInfo(prefix, 1, wordIndex));
|
||||
wordIndex++;
|
||||
}
|
||||
if (wordMap.containsKey(suffix)) {
|
||||
wordMap.get(suffix).count++;
|
||||
} else {
|
||||
wordMap.put(suffix, new WordInfo(suffix, 1, wordIndex));
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.close();
|
||||
|
||||
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
|
||||
sortedWords.sort(
|
||||
Comparator.comparingInt((WordInfo w) ->
|
||||
w.word.length()
|
||||
).thenComparingInt(w -> w.firstIndex)
|
||||
);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
|
||||
|
||||
for (WordInfo info : sortedWords) {
|
||||
writer.println(info.word + " " + info.count);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("An error occured: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
93
java/wordStat/WordStatLengthMiddle.java
Normal file
93
java/wordStat/WordStatLengthMiddle.java
Normal file
@@ -0,0 +1,93 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordStatLengthMiddle {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("incorrect input!");
|
||||
System.err.println(
|
||||
"usage: java WordStatLengthMiddle <inputFile> <outputFile>"
|
||||
);
|
||||
}
|
||||
|
||||
String inputFileName = args[0];
|
||||
String outputFileName = args[1];
|
||||
try {
|
||||
BufferedReader r = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
|
||||
Map<String, WordInfo> wordMap = new HashMap<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int wordIndex = 0;
|
||||
|
||||
int data = r.read();
|
||||
while (data != -1) {
|
||||
char c = (char) data;
|
||||
|
||||
if (
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION ||
|
||||
Character.isLetter(c) ||
|
||||
c == '\''
|
||||
) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() >= 7) {
|
||||
word = word.substring(3, word.length() - 3);
|
||||
if (wordMap.containsKey(word)) {
|
||||
wordMap.get(word).count++;
|
||||
} else {
|
||||
wordMap.put(
|
||||
word,
|
||||
new WordInfo(word, 1, wordIndex)
|
||||
);
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
data = r.read();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() >= 7) {
|
||||
word = word.substring(3, word.length() - 3);
|
||||
if (wordMap.containsKey(word)) {
|
||||
wordMap.get(word).count++;
|
||||
} else {
|
||||
wordMap.put(word, new WordInfo(word, 1, wordIndex));
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.close();
|
||||
|
||||
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
|
||||
sortedWords.sort(
|
||||
Comparator.comparingInt((WordInfo w) ->
|
||||
w.word.length()
|
||||
).thenComparingInt(w -> w.firstIndex)
|
||||
);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
|
||||
|
||||
for (WordInfo info : sortedWords) {
|
||||
writer.println(info.word + " " + info.count);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("An error occured: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
96
java/wordStat/WordStatLengthPrefix.java
Normal file
96
java/wordStat/WordStatLengthPrefix.java
Normal file
@@ -0,0 +1,96 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordStatLengthPrefix {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("incorrect input!");
|
||||
System.err.println(
|
||||
"usage: java WordStatLengthPrefix <inputFile> <outputFile>"
|
||||
);
|
||||
}
|
||||
|
||||
String inputFileName = args[0];
|
||||
String outputFileName = args[1];
|
||||
try {
|
||||
BufferedReader r = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
|
||||
Map<String, WordInfo> wordMap = new HashMap<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int wordIndex = 0;
|
||||
|
||||
int data = r.read();
|
||||
while (data != -1) {
|
||||
char c = (char) data;
|
||||
|
||||
if (
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION ||
|
||||
Character.isLetter(c) ||
|
||||
c == '\''
|
||||
) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() != 1) {
|
||||
String prefix = word.substring(
|
||||
0,
|
||||
word.length() / 2
|
||||
);
|
||||
if (wordMap.containsKey(prefix)) {
|
||||
wordMap.get(prefix).count++;
|
||||
} else {
|
||||
wordMap.put(
|
||||
prefix,
|
||||
new WordInfo(prefix, 1, wordIndex)
|
||||
);
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
data = r.read();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() != 1) {
|
||||
String prefix = word.substring(0, word.length() / 2);
|
||||
if (wordMap.containsKey(prefix)) {
|
||||
wordMap.get(prefix).count++;
|
||||
} else {
|
||||
wordMap.put(prefix, new WordInfo(prefix, 1, wordIndex));
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.close();
|
||||
|
||||
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
|
||||
sortedWords.sort(
|
||||
Comparator.comparingInt((WordInfo w) ->
|
||||
w.word.length()
|
||||
).thenComparingInt(w -> w.firstIndex)
|
||||
);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
|
||||
|
||||
for (WordInfo info : sortedWords) {
|
||||
writer.println(info.word + " " + info.count);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("An error occured: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
95
java/wordStat/WordStatLengthSuffix.java
Normal file
95
java/wordStat/WordStatLengthSuffix.java
Normal file
@@ -0,0 +1,95 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordStatLengthSuffix {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println("incorrect input!");
|
||||
System.err.println(
|
||||
"usage: java WordStatLengthSuffix <inputFile> <outputFile>"
|
||||
);
|
||||
}
|
||||
|
||||
String inputFileName = args[0];
|
||||
String outputFileName = args[1];
|
||||
try {
|
||||
BufferedReader r = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
|
||||
Map<String, WordInfo> wordMap = new HashMap<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int wordIndex = 0;
|
||||
|
||||
int data = r.read();
|
||||
while (data != -1) {
|
||||
char c = (char) data;
|
||||
|
||||
if (
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION ||
|
||||
Character.isLetter(c) ||
|
||||
c == '\''
|
||||
) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() != 1) {
|
||||
word = word.substring(
|
||||
word.length() - word.length() / 2
|
||||
);
|
||||
if (wordMap.containsKey(word)) {
|
||||
wordMap.get(word).count++;
|
||||
} else {
|
||||
wordMap.put(
|
||||
word,
|
||||
new WordInfo(word, 1, wordIndex)
|
||||
);
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
data = r.read();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
String word = sb.toString().toLowerCase();
|
||||
if (word.length() != 1) {
|
||||
word = word.substring(word.length() - word.length() / 2);
|
||||
if (wordMap.containsKey(word)) {
|
||||
wordMap.get(word).count++;
|
||||
} else {
|
||||
wordMap.put(word, new WordInfo(word, 1, wordIndex));
|
||||
wordIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.close();
|
||||
|
||||
List<WordInfo> sortedWords = new ArrayList<>(wordMap.values());
|
||||
sortedWords.sort(
|
||||
Comparator.comparingInt((WordInfo w) ->
|
||||
w.word.length()
|
||||
).thenComparingInt(w -> w.firstIndex)
|
||||
);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputFileName, "UTF-8");
|
||||
|
||||
for (WordInfo info : sortedWords) {
|
||||
writer.println(info.word + " " + info.count);
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("An error occured: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
70
java/wordStat/WordStatTest.java
Normal file
70
java/wordStat/WordStatTest.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package wordStat;
|
||||
|
||||
import base.Named;
|
||||
import base.Selector;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#wordstat">Word Statistics</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WordStatTest {
|
||||
// === Base
|
||||
private static final Named<Function<String, Stream<String>>> ID = Named.of("", Stream::of);
|
||||
private static final WordStatTester.Variant BASE = new WordStatTester.Variant("", false, Comparator.comparingInt(p -> 0));
|
||||
|
||||
|
||||
// === 3637
|
||||
public static final int SIZE = 3;
|
||||
private static final WordStatTester.Variant LENGTH = new WordStatTester.Variant("Length", false, Comparator.comparingInt(p -> p.first().length()));
|
||||
private static final Named<Function<String, Stream<String>>> MIDDLE =
|
||||
size("Middle", SIZE * 2 + 1, s -> Stream.of(s.substring(SIZE, s.length() - SIZE)));
|
||||
|
||||
static Named<Function<String, Stream<String>>> size(
|
||||
final String name,
|
||||
final int length,
|
||||
final Function<String, Stream<String>> f
|
||||
) {
|
||||
return Named.of(name, s -> s.length() >= length ? f.apply(s) : Stream.empty());
|
||||
}
|
||||
|
||||
// === 3839
|
||||
private static final Named<Function<String, Stream<String>>> AFFIX = size(
|
||||
"Affix",
|
||||
2,
|
||||
s -> Stream.of(s.substring(0, s.length() / 2), s.substring(s.length() - s.length() / 2))
|
||||
);
|
||||
|
||||
// === 3536
|
||||
private static final Named<Function<String, Stream<String>>> SUFFIX =
|
||||
size("Suffix", 2, s -> Stream.of(s.substring(s.length() - s.length() / 2)));
|
||||
|
||||
// === 4749
|
||||
private static final Named<Function<String, Stream<String>>> PREFIX =
|
||||
size("Prefix", 2, s -> Stream.of(s.substring(0, s.length() / 2)));
|
||||
|
||||
// === Common
|
||||
public static final Selector SELECTOR = new Selector(WordStatTester.class)
|
||||
.variant("Base", BASE.with(ID))
|
||||
.variant("3637", LENGTH.with(MIDDLE))
|
||||
.variant("3839", LENGTH.with(AFFIX))
|
||||
.variant("3435", LENGTH.with(SUFFIX))
|
||||
.variant("3233", LENGTH.with(ID))
|
||||
.variant("4142", LENGTH.with(MIDDLE))
|
||||
.variant("4749", LENGTH.with(PREFIX))
|
||||
|
||||
;
|
||||
|
||||
private WordStatTest() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
public static void main(final String... args) {
|
||||
SELECTOR.main(args);
|
||||
}
|
||||
}
|
||||
100
java/wordStat/WordStatTester.java
Normal file
100
java/wordStat/WordStatTester.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package wordStat;
|
||||
|
||||
import base.ExtendedRandom;
|
||||
import base.Named;
|
||||
import base.Pair;
|
||||
import base.TestCounter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public final class WordStatTester {
|
||||
public static final String PRE_LOWER = chars()
|
||||
.filter(s -> s.toLowerCase(Locale.ROOT).length() == 1)
|
||||
.collect(Collectors.joining());
|
||||
public static final String POST_LOWER = chars()
|
||||
.collect(Collectors.joining())
|
||||
.toLowerCase();
|
||||
|
||||
private WordStatTester() {
|
||||
}
|
||||
|
||||
private static Stream<String> chars() {
|
||||
return IntStream.range(' ', Character.MAX_VALUE)
|
||||
.filter(ch -> !Character.isSurrogate((char) ch))
|
||||
.filter(ch -> Character.getType(ch) != Character.NON_SPACING_MARK)
|
||||
.filter(ch -> Character.getType(ch) != Character.DIRECTIONALITY_NONSPACING_MARK)
|
||||
.mapToObj(Character::toString);
|
||||
}
|
||||
|
||||
/* package-private */ record Variant(String name, boolean reverse, Comparator<Pair<String, Integer>> c) {
|
||||
public Consumer<TestCounter> with(final Named<Function<String, Stream<String>>> split) {
|
||||
return counter -> WordStatChecker.test(
|
||||
counter,
|
||||
"WordStat" + name + split.name(),
|
||||
text -> answer(split.value(), text),
|
||||
checker -> {
|
||||
checker.test("To be, or not to be, that is the question:");
|
||||
checker.test("Monday's child is fair of face.", "Tuesday's child is full of grace.");
|
||||
checker.test("Шалтай-Болтай", "Сидел на стене.", "Шалтай-Болтай", "Свалился во сне.");
|
||||
checker.test(
|
||||
"27 октября — 300-й день григорианскому календарю. До конца года остаётся 65 дней.",
|
||||
"До 15 октября 1582 года — 27 октября по юлианскому календарю, с 15 октября 1582 года — 27 октября по григорианскому календарю.",
|
||||
"В XX и XXI веках соответствует 14 октября по юлианскому календарю[1].",
|
||||
"(c) Wikipedia"
|
||||
);
|
||||
checker.test("23 октября — Всемирный день психического здоровья", "Тема 2025 года: Психическое здоровье на рабочем месте");
|
||||
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.ENGLISH, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(10, 3, 5, 5, ExtendedRandom.RUSSIAN, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(4, 10, 10, 3, ExtendedRandom.GREEK, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(4, 10, 10, 3, WordStatChecker.DASH, WordStatChecker.SIMPLE_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.ENGLISH, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 3, 5, 5, ExtendedRandom.RUSSIAN, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, ExtendedRandom.GREEK, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 3, WordStatChecker.DASH, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(3, 10, 10, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
final int d = TestCounter.DENOMINATOR;
|
||||
final int d2 = TestCounter.DENOMINATOR;
|
||||
checker.randomTest(10, 10000 / d, 10, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 1, 10, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10, 1000 / d, 100 / d2, 100 / d2, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(4, 1000 / d, 10, 3000 / d, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(4, 1000 / d, 3000 / d, 10, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(10000 / d, 20, 10, 5, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
checker.randomTest(1000000 / d, 2, 2, 1, WordStatChecker.ALL, WordStatChecker.ADVANCED_DELIMITERS);
|
||||
|
||||
checker.test(PRE_LOWER);
|
||||
checker.test(POST_LOWER);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private List<Pair<String, Integer>> answer(final Function<String, Stream<String>> split, final String[][] text) {
|
||||
final List<String> parts = Arrays.stream(text)
|
||||
.flatMap(Arrays::stream)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(split)
|
||||
.peek(s -> {assert !s.isBlank();})
|
||||
.collect(Collectors.toList());
|
||||
if (reverse()) {
|
||||
Collections.reverse(parts);
|
||||
}
|
||||
return parts.stream()
|
||||
.collect(Collectors.toMap(String::toLowerCase, v -> 1, Integer::sum, LinkedHashMap::new))
|
||||
.entrySet().stream()
|
||||
.map(Pair::of)
|
||||
.sorted(c)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
7
java/wordStat/package-info.java
Normal file
7
java/wordStat/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#wordstat">Word Statistics</a> homework
|
||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
||||
*
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
package wordStat;
|
||||
44
java/wspp/IntList.java
Normal file
44
java/wspp/IntList.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package wspp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class IntList {
|
||||
|
||||
protected int[] list = new int[8];
|
||||
protected int idx = 0;
|
||||
|
||||
public IntList() {}
|
||||
|
||||
public void put(int val) {
|
||||
if (idx >= list.length) {
|
||||
list = Arrays.copyOf(list, list.length * 2);
|
||||
}
|
||||
|
||||
list[idx++] = val;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return idx;
|
||||
}
|
||||
|
||||
public int get(int index) {
|
||||
return list[index];
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < idx; i++) {
|
||||
if (i == idx - 1) {
|
||||
sb.append(String.valueOf(list[i]) + "\n");
|
||||
} else {
|
||||
sb.append(String.valueOf(list[i]) + " ");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
25
java/wspp/WordInfo.java
Normal file
25
java/wspp/WordInfo.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package wspp;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class WordInfo {
|
||||
|
||||
final String word;
|
||||
int firstOccurrence;
|
||||
|
||||
IntList occurrences = new IntList();
|
||||
|
||||
Map<Integer, IntList> lineOccurrences = new HashMap<>();
|
||||
|
||||
public WordInfo(String word) {
|
||||
this.word = word;
|
||||
}
|
||||
|
||||
public WordInfo(String word, int firstOccurrence) {
|
||||
this.word = word;
|
||||
this.firstOccurrence = firstOccurrence;
|
||||
}
|
||||
}
|
||||
80
java/wspp/Wspp.java
Normal file
80
java/wspp/Wspp.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package wspp;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nikita Doschennikov (me@fymio.us)
|
||||
*/
|
||||
public class Wspp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.err.println(
|
||||
"usage: java Wspp <inputFilePath> <outputFilePath>"
|
||||
);
|
||||
}
|
||||
|
||||
final String inputFileName = args[0];
|
||||
final String outputFileName = args[1];
|
||||
|
||||
Map<String, WordInfo> words = new LinkedHashMap<>();
|
||||
|
||||
try (
|
||||
BufferedReader br = new BufferedReader(
|
||||
new FileReader(inputFileName)
|
||||
);
|
||||
FileWriter fw = new FileWriter(outputFileName)
|
||||
) {
|
||||
String line;
|
||||
int wordPos = 1;
|
||||
while ((line = br.readLine()) != null) {
|
||||
line = line.toLowerCase();
|
||||
StringBuilder word = new StringBuilder();
|
||||
|
||||
for (char c : line.toCharArray()) {
|
||||
if (
|
||||
Character.isLetter(c) ||
|
||||
c == '\'' ||
|
||||
Character.getType(c) == Character.DASH_PUNCTUATION
|
||||
) {
|
||||
word.append(c);
|
||||
} else {
|
||||
if (!word.isEmpty()) {
|
||||
if (words.containsKey(word.toString())) {
|
||||
words
|
||||
.get(word.toString())
|
||||
.occurrences.put(wordPos++);
|
||||
} else {
|
||||
WordInfo info = new WordInfo(word.toString());
|
||||
info.occurrences.put(wordPos++);
|
||||
words.put(word.toString(), info);
|
||||
}
|
||||
}
|
||||
|
||||
word = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
if (!word.isEmpty()) {
|
||||
if (words.containsKey(word.toString())) {
|
||||
words.get(word.toString()).occurrences.put(wordPos++);
|
||||
} else {
|
||||
WordInfo info = new WordInfo(word.toString());
|
||||
info.occurrences.put(wordPos++);
|
||||
words.put(word.toString(), info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String word : words.keySet()) {
|
||||
WordInfo info = words.get(word);
|
||||
int count = info.occurrences.getLength();
|
||||
String occurencies = info.occurrences.toString();
|
||||
fw.write(word + " " + count + " " + occurencies);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("Error reading file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user