update
All checks were successful
Markup Tests / test (push) Successful in 8s
Markdown to Html Tests / test (push) Successful in 17s

This commit is contained in:
2026-02-17 09:32:08 +03:00
commit 2f05f238e9
109 changed files with 9369 additions and 0 deletions

View 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

View 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

View 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

View 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

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

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

@@ -0,0 +1,5 @@
*.xml
java/out/*
*.iml
*.idea*
*.class

554
README.md Normal file
View 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 [![Markdown to HTML Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/md2html.yml/badge.svg)](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. Разметка [![Markup Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/markup.yml/badge.svg)](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. Подсчет слов++ [![Word Stat++ Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/wspp.yml/badge.svg)](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. Подсчет слов [![Word Stat](https://git.fymio.us/me/prog-intro-2025/actions/workflows/word-stat.yml/badge.svg)](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. Реверс [![Reverse Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/reverse.yml/badge.svg)](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. Сумма чисел [![Sum Tests](https://git.fymio.us/me/prog-intro-2025/actions/workflows/sum.yml/badge.svg)](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. Для ускорения отладки рекомендуется сделать скрипт, выполняющий шаги 24.
## Домашнее задание 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`
-->

Binary file not shown.

BIN
artifacts/Md2HtmlTest.jar Normal file

Binary file not shown.

BIN
artifacts/ReverseTest.jar Normal file

Binary file not shown.

BIN
artifacts/SumTest.jar Normal file

Binary file not shown.

BIN
artifacts/WordStatTest.jar Normal file

Binary file not shown.

BIN
artifacts/WsppTest.jar Normal file

Binary file not shown.

550
java/RunMe.java Normal file
View 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
View 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);
}
}

View File

@@ -0,0 +1,20 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public abstract class BaseChecker {
protected final TestCounter counter;
protected BaseChecker(final TestCounter counter) {
this.counter = counter;
}
public ExtendedRandom random() {
return counter.random();
}
public int mode() {
return counter.mode();
}
}

95
java/base/Either.java Normal file
View File

@@ -0,0 +1,95 @@
package base;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface Either<L, R> {
<NR> Either<L, NR> mapRight(final Function<? super R, NR> f);
<NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f);
<T> T either(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);
boolean isRight();
L getLeft();
R getRight();
static <L, R> Either<L, R> right(final R value) {
return new Either<>() {
@Override
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
return right(f.apply(value));
}
@Override
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
return f.apply(value);
}
@Override
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
return rf.apply(value);
}
@Override
public boolean isRight() {
return true;
}
@Override
public L getLeft() {
return null;
}
@Override
public R getRight() {
return value;
}
@Override
public String toString() {
return String.format("Right(%s)", value);
}
};
}
static <L, R> Either<L, R> left(final L value) {
return new Either<>() {
@Override
public <NR> Either<L, NR> mapRight(final Function<? super R, NR> f) {
return left(value);
}
@Override
public <NR> Either<L, NR> flatMapRight(final Function<? super R, ? extends Either<L, NR>> f) {
return left(value);
}
@Override
public <T> T either(final Function<? super L, ? extends T> lf, final Function<? super R, ? extends T> rf) {
return lf.apply(value);
}
@Override
public boolean isRight() {
return false;
}
@Override
public L getLeft() {
return value;
}
@Override
public R getRight() {
return null;
}
@Override
public String toString() {
return String.format("Left(%s)", value);
}
};
}
}

View File

@@ -0,0 +1,89 @@
package base;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExtendedRandom {
public static final String ENGLISH = "abcdefghijklmnopqrstuvwxyz";
public static final String RUSSIAN = "абвгдеежзийклмнопрстуфхцчшщъыьэюя";
public static final String GREEK = "αβγŋδεζηθικλμνξοπρτυφχψω";
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public static final String SPACES = " \t\n\u000B\u2029\f";
private final Random random;
public ExtendedRandom(final Random random) {
this.random = random;
}
public ExtendedRandom(final Class<?> owner) {
this(new Random(7912736473497634913L + owner.getName().hashCode()));
}
public String randomString(final String chars) {
return randomChar(chars) + (random.nextBoolean() ? "" : randomString(chars));
}
public char randomChar(final String chars) {
return chars.charAt(nextInt(chars.length()));
}
public String randomString(final String chars, final int length) {
final StringBuilder string = new StringBuilder();
for (int i = 0; i < length; i++) {
string.append(randomChar(chars));
}
return string.toString();
}
public String randomString(final String chars, final int minLength, final int maxLength) {
return randomString(chars, nextInt(minLength, maxLength));
}
public boolean nextBoolean() {
return random.nextBoolean();
}
public int nextInt() {
return random.nextInt();
}
public int nextInt(final int min, final int max) {
return nextInt(max - min + 1) + min;
}
public int nextInt(final int n) {
return random.nextInt(n);
}
@SafeVarargs
public final <T> T randomItem(final T... items) {
return items[nextInt(items.length)];
}
public <T> T randomItem(final List<T> items) {
return items.get(nextInt(items.size()));
}
public Random getRandom() {
return random;
}
public <T> List<T> random(final int list, final Function<ExtendedRandom, T> generator) {
return Stream.generate(() -> generator.apply(this)).limit(list).toList();
}
public double nextDouble() {
return random.nextDouble();
}
public <E> void shuffle(final List<E> all) {
Collections.shuffle(all, random);
}
}

92
java/base/Functional.java Normal file
View File

@@ -0,0 +1,92 @@
package base;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Functional {
private Functional() {}
public static <T, R> List<R> map(final Collection<T> items, final Function<? super T, ? extends R> f) {
return items.stream().map(f).collect(Collectors.toUnmodifiableList());
}
public static <T, R> List<R> map(final List<T> items, final BiFunction<? super Integer, ? super T, ? extends R> f) {
return IntStream.range(0, items.size())
.mapToObj(i -> f.apply(i, items.get(i)))
.collect(Collectors.toUnmodifiableList());
}
public static <K, T, R> Map<K, R> mapValues(final Map<K, T> map, final Function<T, R> f) {
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> f.apply(e.getValue())));
}
@SafeVarargs
public static <K, T> Map<K, T> mergeMaps(final Map<K, T>... maps) {
return Stream.of(maps).flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a));
}
@SafeVarargs
public static <T> List<T> concat(final Collection<? extends T>... items) {
final List<T> result = new ArrayList<>();
for (final Collection<? extends T> item : items) {
result.addAll(item);
}
return result;
}
public static <T> List<T> append(final Collection<T> collection, final T item) {
final List<T> list = new ArrayList<>(collection);
list.add(item);
return list;
}
public static <T> List<List<T>> allValues(final List<T> vals, final int length) {
return Stream.generate(() -> vals)
.limit(length)
.reduce(
List.of(List.of()),
(prev, next) -> next.stream()
.flatMap(value -> prev.stream().map(list -> append(list, value)))
.toList(),
(prev, next) -> next.stream()
.flatMap(suffix -> prev.stream().map(prefix -> concat(prefix, suffix)))
.toList()
);
}
public static <K, V> V get(final Map<K, V> map, final K key) {
final V result = map.get(key);
if (result == null) {
throw new NullPointerException(key.toString() + " in " + map(map.keySet(), Objects::toString));
}
return result;
}
public static void addRange(final List<Integer> values, final int d, final int c) {
for (int i = -d; i <= d; i++) {
values.add(c + i);
}
}
public static <T> void forEachPair(final T[] items, final BiConsumer<? super T, ? super T> consumer) {
assert items.length % 2 == 0;
IntStream.range(0, items.length / 2).forEach(i -> consumer.accept(items[i * 2], items[i * 2 + 1]));
}
public static <T> List<Pair<T, T>> toPairs(final T[] items) {
assert items.length % 2 == 0;
return IntStream.range(0, items.length / 2)
.mapToObj(i -> Pair.of(items[i * 2], items[i * 2 + 1]))
.toList();
}
}

56
java/base/Log.java Normal file
View File

@@ -0,0 +1,56 @@
package base;
import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class Log {
private final Pattern LINES = Pattern.compile("\n");
private int indent;
public static Supplier<Void> action(final Runnable action) {
return () -> {
action.run();
return null;
};
}
public void scope(final String name, final Runnable action) {
scope(name, action(action));
}
public <T> T scope(final String name, final Supplier<T> action) {
println(name);
indent++;
try {
return silentScope(name, action);
} finally {
indent--;
}
}
public <T> T silentScope(final String ignoredName, final Supplier<T> action) {
return action.get();
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public void println(final Object value) {
for (final String line : LINES.split(String.valueOf(value))) {
System.out.println(indent() + line);
}
}
public void format(final String format, final Object... args) {
println(String.format(format,args));
}
private String indent() {
return " ".repeat(indent);
}
protected int getIndent() {
return indent;
}
}

View File

@@ -0,0 +1,28 @@
package base;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public final class MainChecker {
private final Runner runner;
public MainChecker(final Runner runner) {
this.runner = runner;
}
public List<String> run(final TestCounter counter, final String... input) {
return runner.run(counter, input);
}
public List<String> run(final TestCounter counter, final List<String> input) {
return runner.run(counter, input);
}
public void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
runner.testEquals(counter, input, expected);
}
}

15
java/base/Named.java Normal file
View File

@@ -0,0 +1,15 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record Named<T>(String name, T value) {
public static <T> Named<T> of(final String name, final T f) {
return new Named<>(name, f);
}
@Override
public String toString() {
return name;
}
}

44
java/base/Pair.java Normal file
View File

@@ -0,0 +1,44 @@
package base;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "unused"})
public record Pair<F, S>(F first, S second) {
public static <F, S> Pair<F, S> of(final F first, final S second) {
return new Pair<>(first, second);
}
public static <F, S> Pair<F, S> of(final Map.Entry<F, S> e) {
return of(e.getKey(), e.getValue());
}
public static <F, S> UnaryOperator<Pair<F, S>> lift(final UnaryOperator<F> f, final UnaryOperator<S> s) {
return p -> of(f.apply(p.first), s.apply(p.second));
}
public static <F, S> BinaryOperator<Pair<F, S>> lift(final BinaryOperator<F> f, final BinaryOperator<S> s) {
return (p1, p2) -> of(f.apply(p1.first, p2.first), s.apply(p1.second, p2.second));
}
public static <T, F, S> Function<T, Pair<F, S>> tee(
final Function<? super T, ? extends F> f,
final Function<? super T, ? extends S> s
) {
return t -> of(f.apply(t), s.apply(t));
}
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
public <R> Pair<F, R> second(final R second) {
return new Pair<>(first, second);
}
}

185
java/base/Runner.java Normal file
View File

@@ -0,0 +1,185 @@
package base;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("unused")
@FunctionalInterface
public interface Runner {
List<String> run(final TestCounter counter, final List<String> input);
default List<String> run(final TestCounter counter, final String... input) {
return run(counter, List.of(input));
}
default void testEquals(final TestCounter counter, final List<String> input, final List<String> expected) {
counter.test(() -> {
final List<String> actual = run(counter, input);
for (int i = 0; i < Math.min(expected.size(), actual.size()); i++) {
final String exp = expected.get(i);
final String act = actual.get(i);
if (!exp.equalsIgnoreCase(act)) {
Asserts.assertEquals("Line " + (i + 1), exp, act);
return;
}
}
Asserts.assertEquals("Number of lines", expected.size(), actual.size());
});
}
static Packages packages(final String... packages) {
return new Packages(List.of(packages));
}
@FunctionalInterface
interface CommentRunner {
List<String> run(String comment, TestCounter counter, List<String> input);
}
final class Packages {
private final List<String> packages;
private Packages(final List<String> packages) {
this.packages = packages;
}
public Runner std(final String className) {
final CommentRunner main = main(className);
return (counter, input) -> counter.call("io", () -> {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (final PrintWriter writer = new PrintWriter(baos, 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
View File

@@ -0,0 +1,143 @@
package base;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Selector {
private final Class<?> owner;
private final List<String> modes;
private final Set<String> variantNames = new LinkedHashSet<>();
private final Map<String, Consumer<TestCounter>> variants = new LinkedHashMap<>();
public Selector(final Class<?> owner, final String... modes) {
this.owner = owner;
this.modes = List.of(modes);
}
public Selector variant(final String name, final Consumer<TestCounter> operations) {
Asserts.assertTrue("Duplicate variant " + name, variants.put(name.toLowerCase(), operations) == null);
variantNames.add(name);
return this;
}
private static void check(final boolean condition, final String format, final Object... args) {
if (!condition) {
throw new IllegalArgumentException(String.format(format, args));
}
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public void main(final String... args) {
try {
final String mode;
if (modes.isEmpty()) {
check(args.length >= 1, "At least one argument expected, found %s", args.length);
mode = "";
} else {
check(args.length >= 2, "At least two arguments expected, found %s", args.length);
mode = args[0];
}
final List<String> vars = Arrays.stream(args).skip(modes.isEmpty() ? 0 : 1)
.flatMap(arg -> Arrays.stream(arg.split("[ +]+")))
.toList();
test(mode, vars);
} catch (final IllegalArgumentException e) {
System.err.println("ERROR: " + e.getMessage());
if (modes.isEmpty()) {
System.err.println("Usage: " + owner.getName() + " VARIANT...");
} else {
System.err.println("Usage: " + owner.getName() + " MODE VARIANT...");
System.err.println("Modes: " + String.join(", ", modes));
}
System.err.println("Variants: " + String.join(", ", variantNames));
System.exit(1);
}
}
public void test(final String mode, List<String> vars) {
final int modeNo = modes.isEmpty() ? -1 : modes.indexOf(mode) ;
check(modes.isEmpty() || modeNo >= 0, "Unknown mode '%s'", mode);
if (variantNames.contains("Base") && !vars.contains("Base")) {
vars = new ArrayList<>(vars);
vars.add(0, "Base");
}
vars.forEach(variant -> check(variants.containsKey(variant.toLowerCase()), "Unknown variant '%s'", variant));
final Map<String, String> properties = modes.isEmpty()
? Map.of("variant", String.join("+", vars))
: Map.of("variant", String.join("+", vars), "mode", mode);
final TestCounter counter = new TestCounter(owner, modeNo, properties);
counter.printHead();
vars.forEach(variant -> counter.scope("Testing " + variant, () -> variants.get(variant.toLowerCase()).accept(counter)));
counter.printStatus();
}
public static <V extends Tester> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final String... modes) {
return new Composite<>(owner, factory, (tester, counter) -> tester.test(), modes);
}
public static <V> Composite<V> composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
return new Composite<>(owner, factory, tester, modes);
}
public List<String> getModes() {
return modes.isEmpty() ? List.of("~") : modes;
}
public List<String> getVariants() {
return List.copyOf(variants.keySet());
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public static final class Composite<V> {
private final Selector selector;
private final Function<TestCounter, V> factory;
private final BiConsumer<V, TestCounter> tester;
private List<Consumer<? super V>> base;
private Composite(final Class<?> owner, final Function<TestCounter, V> factory, final BiConsumer<V, TestCounter> tester, final String... modes) {
selector = new Selector(owner, modes);
this.factory = factory;
this.tester = tester;
}
@SafeVarargs
public final Composite<V> variant(final String name, final Consumer<? super V>... parts) {
if ("Base".equalsIgnoreCase(name)) {
base = List.of(parts);
return v(name.toLowerCase());
} else {
return v(name, parts);
}
}
@SafeVarargs
private Composite<V> v(final String name, final Consumer<? super V>... parts) {
selector.variant(name, counter -> {
final V variant = factory.apply(counter);
for (final Consumer<? super V> part : base) {
part.accept(variant);
}
for (final Consumer<? super V> part : parts) {
part.accept(variant);
}
tester.accept(variant, counter);
});
return this;
}
public Selector selector() {
return selector;
}
}
}

184
java/base/TestCounter.java Normal file
View File

@@ -0,0 +1,184 @@
package base;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class TestCounter extends Log {
public static final int DENOMINATOR = Integer.getInteger("base.denominator", 1);
public static final int DENOMINATOR2 = (int) Math.round(Math.sqrt(DENOMINATOR));
private static final String JAR_EXT = ".jar";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
private final Class<?> owner;
private final int mode;
private final Map<String, ?> properties;
private final ExtendedRandom random;
private final long start = System.currentTimeMillis();
private int passed;
public TestCounter(final Class<?> owner, final int mode, final Map<String, ?> properties) {
Locale.setDefault(Locale.US);
Asserts.checkAssert(getClass());
this.owner = owner;
this.mode = mode;
this.properties = properties;
random = new ExtendedRandom(owner);
}
public int mode() {
return mode;
}
public int getTestNo() {
return passed + 1;
}
public void test(final Runnable action) {
testV(() -> {
action.run();
return null;
});
}
public <T> void testForEach(final Iterable<? extends T> items, final Consumer<? super T> action) {
for (final T item : items) {
test(() -> action.accept(item));
}
}
public <T> T testV(final Supplier<T> action) {
return silentScope("Test " + getTestNo(), () -> {
final T result = action.get();
passed++;
return result;
});
}
private String getLine() {
return getIndent() == 0 ? "=" : "-";
}
public void printHead() {
println("=== " + getTitle());
}
public void printStatus() {
format("%s%n%s%n", getLine().repeat(30), getTitle());
format("%d tests passed in %dms%n", passed, System.currentTimeMillis() - start);
println("Version: " + getVersion(owner));
println("");
}
private String getTitle() {
return String.format("%s %s", owner.getSimpleName(), properties.isEmpty() ? "" : properties);
}
private static String getVersion(final Class<?> clazz) {
try {
final ClassLoader cl = clazz.getClassLoader();
final URL url = cl.getResource(clazz.getName().replace('.', '/') + ".class");
if (url == null) {
return "(no manifest)";
}
final String path = url.getPath();
final int index = path.indexOf(JAR_EXT);
if (index == -1) {
return DATE_FORMAT.format(new Date(new File(path).lastModified()));
}
final String jarPath = path.substring(0, index + JAR_EXT.length());
try (final JarFile jarFile = new JarFile(new File(new URI(jarPath)))) {
final JarEntry entry = jarFile.getJarEntry("META-INF/MANIFEST.MF");
return DATE_FORMAT.format(new Date(entry.getTime()));
}
} catch (final IOException | URISyntaxException e) {
return "error: " + e;
}
}
public <T> T call(final String message, final SupplierE<T> supplier) {
return get(supplier).either(e -> fail(e, "%s", message), Function.identity());
}
public void shouldFail(final String message, @SuppressWarnings("TypeMayBeWeakened") final RunnableE action) {
test(() -> get(action).either(e -> null, v -> fail("%s", message)));
}
public <T> T fail(final String format, final Object... args) {
return fail(Asserts.error(format, args));
}
public <T> T fail(final Throwable throwable) {
return fail(throwable, "%s: %s", throwable.getClass().getSimpleName(), throwable.getMessage());
}
public <T> T fail(final Throwable throwable, final String format, final Object... args) {
final String message = String.format(format, args);
println("ERROR: " + message);
throw throwable instanceof Error ? (Error) throwable : new AssertionError(throwable);
}
public void checkTrue(final boolean condition, final String message, final Object... args) {
if (!condition) {
fail(message, args);
}
}
public static <T> Either<Exception, T> get(final SupplierE<T> supplier) {
return supplier.get();
}
public Path getFile(final String suffix) {
return Paths.get(String.format("test%d.%s", getTestNo(), suffix));
}
public ExtendedRandom random() {
return random;
}
@FunctionalInterface
public interface SupplierE<T> extends Supplier<Either<Exception, T>> {
T getE() throws Exception;
@Override
default Either<Exception, T> get() {
try {
return Either.right(getE());
} catch (final Exception e) {
return Either.left(e);
}
}
}
@FunctionalInterface
public interface RunnableE extends SupplierE<Void> {
void run() throws Exception;
@Override
default Void getE() throws Exception {
run();
return null;
}
}
}

18
java/base/Tester.java Normal file
View File

@@ -0,0 +1,18 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public abstract class Tester extends BaseChecker {
protected Tester(final TestCounter counter) {
super(counter);
}
public abstract void test();
public void run(final Class<?> test, final String... args) {
System.out.println("=== Testing " + test.getSimpleName() + " " + String.join(" ", args));
test();
counter.printStatus();
}
}

15
java/base/Unit.java Normal file
View File

@@ -0,0 +1,15 @@
package base;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Unit {
public static final Unit INSTANCE = new Unit();
private Unit() { }
@Override
public String toString() {
return "unit";
}
}

View File

@@ -0,0 +1,7 @@
/**
* Common homeworks test classes
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package base;

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

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

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

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

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface Markup extends Markdown, Html, Tex {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

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

View File

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface PartOfParagraph extends Markup {}

View File

@@ -0,0 +1,6 @@
package markup;
/**
* @author Nikita Doschennikov (me@fymio.us)
*/
public interface PrimePart extends Markup {}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 88 KiB

View 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

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

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

View 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>Это не &lt;&lt;вставка&gt;&gt;</p>", "<", ">")
.spoiled("Это не <<вставка>> 2", "<p>Это не &lt;&lt;вставка&gt;&gt; 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("<", "&lt;").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);
}
}

View 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("<", "&lt;", ">", "&gt;");
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Код представляется элементом `code`.\n\n",
"<p>Мы все любим <em>выделять</em> текст <em>разными</em> способами.\n<strong>Сильное выделение</strong>, используется гораздо реже,\о <strong>почему бы и нет</strong>?\nНемного <s>зачеркивания</s> еще никому не вредило.\nКод представляется элементом <code>code</code>.</p>"
);
test(
"Обратите внимание, как экранируются специальные\nHTML-символы, такие как `<`, `>` и `&`.\n\n",
"<p>Обратите внимание, как экранируются специальные\nHTML-символы, такие как <code>&lt;</code>, <code>&gt;</code> и <code>&amp;</code>.</p>"
);
test(
"Экранирование должно работать во всех местах: <>&.\n\n",
"<p>Экранирование должно работать во всех местах: &lt;&gt;&amp;.</p>"
);
test(
"Знаете ли вы, что в Markdown, одиночные * и _\е означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: \\*.",
"<p>Знаете ли вы, что в Markdown, одиночные * и _\е означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: *.</p>"
);
test(
"\n\n\nЛишние пустые строки должны игнорироваться.\n\n\n\n",
"<p>Лишние пустые строки должны игнорироваться.</p>"
);
test(
"Любите ли вы *вложенные __выделения__* так,\ак __--люблю--__ их я?",
"<p>Любите ли вы <em>вложенные <strong>выделения</strong></em> так,\ак <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>&lt;</code>, <code>&gt;</code> и <code>&amp;</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);
}
}

View 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("&lt;");
} else if (block.charAt(currentChar) == '>') {
sb.append("&gt;");
} else if (block.charAt(currentChar) == '&') {
sb.append("&amp;");
} 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);
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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