diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml
new file mode 100644
index 0000000..f8d7e33
--- /dev/null
+++ b/.github/workflows/github-actions.yml
@@ -0,0 +1,35 @@
+name: Kotlin CI with Gradle
+
+on:
+ push:
+ branches:
+ [main, CI]
+ pull_request:
+ branches:
+ [main]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: "oracle"
+ java-version: 21
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Testing
+ run: ./gradlew test
+
+ - name: Run Test Coverage
+ run: ./gradlew jacocoTestReport
+
+ - name: Generate JaCoCo Badge
+ uses: cicirello/jacoco-badge-generator@v2
+ with:
+ generate-branches-badge: true
+ jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b63da45
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,42 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..951ff97
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 spbu-coding-2023
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8865121
--- /dev/null
+++ b/README.md
@@ -0,0 +1,390 @@
+[![MIT License][license-shield]][license-url]
+[](https://github.com/marketplace/actions/jacoco-badge-generator)
+
+
Борцы Даня, Саша, Мухаммет
+
+## О проекте
+
+Добро пожаловать в библиотеку Kotlin Binary Tree! Эта библиотека разработана для предоставления разработчикам
+эффективных и гибких реализаций трех различных типов бинарных деревьев: Binary Tree, AVL Tree и Red-Black Tree
+
+## Создано с использованием
+
+- Kotlin 1.9.22: Библиотека написана на Kotlin, используя его лаконичный синтаксис и мощные возможности.
+- JDK 21: Построено с использованием Java Development Kit версии 21, обеспечивая совместимость с современными
+ средами Java и используя последние усовершенствования Java.
+
+## Главные особенности
+
+- #### Три Варианта Бинарных Деревьев: Выберите из различных типов бинарных деревьев, чтобы удовлетворить свои конкретные требования:
+
+- Binary Tree: Фундаментальная структура бинарного дерева, предоставляющая простые возможности вставки, удаления и поиска.
+- AVL Tree: Самобалансирующееся бинарное дерево поиска, обеспечивающее оптимальную производительность для операций вставки, удаления и поиска.
+- Red-Black Tree: Еще одно самобалансирующееся бинарное дерево поиска с логарифмической высотой, обеспечивающее эффективные операции для больших наборов данных.
+
+- #### Универсальные Операции: Каждая реализация дерева поддерживает основные операции для управления структурами деревьев:
+ - Поиск: Быстро находите узлы в дереве на основе ключевых значений.
+ - Вставка: Добавляйте новые узлы, сохраняя целостность и баланс дерева.
+ - Удаление: Удаляйте узлы из дерева, не нарушая его структурных свойств.
+ - Вывод на консоль: Визуализируйте структуру дерева через печать в консоли, помогая в отладке и визуализации задач.
+
+[//]: # (## Usage)
+
+## Как пользоватся
+
+### Запуск программы
+```shell
+./gradlew build
+```
+### Инициализация
+```kotlin
+val binaryTree = BinaryTree()
+val redBlackTree = RBTree()
+val avlTree = AVLTree()
+```
+### Базовые операции
+
+#### Добавление
+```kotlin
+val binaryTree = BinaryTree()
+
+binaryTree.add(1, "A")
+binaryTree.add(2, "B")
+binaryTree.add(3, "C")
+
+println(binaryTree)
+
+// output: [(1: "A"), (2: "B"), (3, "C")]
+```
+
+#### Поиск
+```kotlin
+val binaryTree = BinaryTree()
+
+/*
+ добавляем ноды
+*/
+
+// поиск существующего элемента
+println(binaryTree.get(1))
+println(binaryTree[3])
+println(binaryTree.getOrDefault(2, "No such element"))
+
+// поиск не существующего элемента
+println(binaryTree.get(4))
+println(binaryTree[5])
+println(binaryTree.getOrDefault(8, "No such element"))
+```
+#### Вывод
+```kotlin
+A
+C
+B
+null
+null
+No such element
+```
+
+#### Удаление
+```kotlin
+val binaryTree = BinaryTree()
+
+/*
+ добавляем ноды
+*/
+
+// удаление существующего элемента
+binaryTree.delete(1)
+
+// удаление не существующего элемента ничего не делает
+binaryTree.delete(4)
+
+println(binaryTree.toString())
+```
+
+#### Вывод
+```text
+[(2: B), (3: C)]
+```
+
+#### Присваивание
+```kotlin
+val binaryTree = BinaryTree()
+
+/*
+ добавляем ноды
+*/
+
+println(binaryTree.toString())
+
+// присвоить новое значение существующему элементу
+binaryTree.set(2, "D")
+binaryTree[3] = "E"
+
+println(binaryTree.toString())
+
+// присвоить значение не существующему элементу
+binaryTree.set(4, "Y")
+binaryTree[5] = "X"
+
+println(binaryTree.toString())
+```
+
+#### Вывод
+```text
+// изначальный вид
+[(1: A), (2: B), (3: C)]
+
+// после присваивания
+[(1: A), (2: D), (3: E)]
+
+// после присваивания значения не существующему элементу
+[(1: A), (2: D), (3: E), (4: Y), (5: X)]
+```
+
+#### Минимум / Максимум
+```kotlin
+val binaryTree = BinaryTree()
+
+binaryTree.add(1, "A")
+binaryTree.add(2, "B")
+binaryTree.add(3, "C")
+
+println(binaryTree.min())
+println(binaryTree.max())
+```
+
+#### Вывод
+```text
+(1: A)
+(3: C)
+```
+
+#### Итерирование по дереву
+
+
+ Итерирование по ключу
+
+ ```kotlin
+ val binaryTree = BinaryTree()
+
+binaryTree.add(2, "B")
+binaryTree.add(1, "A")
+binaryTree.add(3, "C")
+
+binaryTree.iterator().forEach {
+ println(it.key)
+}
+
+ println(binaryTree.toString())
+ ```
+#### Вывод
+ ```text
+1
+2
+3
+ ```
+
+
+
+ BST итерирование
+
+ ```kotlin
+ val binaryTree = BinaryTree()
+
+binaryTree.add(2, "B")
+binaryTree.add(1, "A")
+binaryTree.add(3, "C")
+
+binaryTree.iterateBFS().forEach {
+ println(it.key)
+}
+
+ println(binaryTree.toString())
+ ```
+#### Вывод
+ ```text
+2
+1
+3
+ ```
+
+
+
+ DFS итерирование
+
+ ```kotlin
+ val binaryTree = BinaryTree()
+val keys = arrayOf(3, 2, 1, 0, 4, 5)
+
+keys.forEach { binaryTree.add(it, it.toString()) }
+
+// Стандартно iterateDFS работает с mode=Tree.ModeDFS.PREORDER
+println("PREORDER: ")
+binaryTree.iterateDFS().forEach { print(it.key.toString() + " ") }
+println("INORDER: ")
+binaryTree.iterateDFS(mode=Tree.ModeDFS.INORDER).forEach { print(it.key.toString() + " ") }
+println("POSTORDER: ")
+binaryTree.iterateDFS(mode=Tree.ModeDFS.POSTORDER).forEach { print(it.key.toString() + " ") }
+ ```
+#### Вывод
+ ```text
+PREORDER: 3 2 1 0 4 5
+INORDER: 0 1 2 3 4 5
+POSTORDER: 0 1 2 5 4 3
+ ```
+
+
+О методах обхода в глубину — [Tree traversal](https://en.wikipedia.org/wiki/Tree_traversal)
+
+#### Соединение двух деревьев
+
+Операция соединения двух деверьев доступна, только при условии, что их ключи сравнимы и все ключи основного дерева
+меньше всех ключей присоединяемого дерева.
+
+```kotlin
+val binaryTree = BinaryTree()
+val secondBinaryTree = BinaryTree()
+
+binaryTree.add(2, "B")
+binaryTree.add(1, "A")
+binaryTree.add(3, "C")
+
+secondBinaryTree.add(4, "D")
+secondBinaryTree.add(5, "E")
+secondBinaryTree.add(6, "F")
+
+
+binaryTree.merge(secondBinaryTree)
+
+println(binaryTree.toString())
+```
+
+#### Вывод
+ ```text
+[(1: A), (2: B), (3: C), (4: D), (5: E), (6: F)]
+ ```
+
+#### Клонирование дерева
+```kotlin
+val binaryTree = BinaryTree()
+
+binaryTree.add(2, "B")
+binaryTree.add(1, "A")
+binaryTree.add(3, "C")
+
+val cloneTree = binaryTree.clone()
+
+println(cloneTree.toString())
+
+```
+
+#### Вывод
+ ```text
+[(1: A), (2: B), (3: C)]
+ ```
+
+### Виды выводов дерева
+
+ Итерационный вывод
+
+ ```kotlin
+ val binaryTree = BinaryTree()
+
+ binaryTree.add(1, "A")
+ binaryTree.add(2, "B")
+ binaryTree.add(3, "C")
+
+ println(binaryTree.toString())
+ ```
+#### Вывод
+ ```text
+ [(1: A), (2: B), (3: C)]
+ ```
+
+
+
+ Вертикальный вывод рисунком
+
+ ```kotlin
+ val binaryTree = BinaryTree()
+
+ binaryTree.add(1, "A")
+ binaryTree.add(2, "B")
+ binaryTree.add(3, "C")
+
+ println(binaryTree.toString(mode = Tree.TreeStringMode.WIDTH))
+ ```
+#### Вывод
+ ```text
+ | ┌── (3: C)
+ | ┌── (2: B)
+ └── (1: A)
+ ```
+
+
+
+ Горизонтальный вывод рисунком
+
+ ```kotlin
+ val binaryTree = BinaryTree()
+
+ binaryTree.add(1, "A")
+ binaryTree.add(2, "B")
+ binaryTree.add(3, "C")
+
+ println(binaryTree.toString(mode = Tree.TreeStringMode.WIDTH))
+ ```
+#### Вывод
+ ```text
+────────┐
+ (1: A)
+ └─────┐
+ (2: B)
+ └────┐
+ (3: C)
+ ```
+
+
+ Цветной вывод Red-Black Tree
+- Добавляйте параметр к дереву
+
+```kotlin
+redBlackTree.setColored(true)
+```
+
+```kotlin
+ val redBlackTree = RBTree()
+ redBlackTree.setColored(true)
+
+ redBlackTree.add(1, "A")
+ redBlackTree.add(2, "B")
+ redBlackTree.add(3, "C")
+ redBlackTree.add(4, "D")
+ redBlackTree.add(5, "E")
+
+ println(redBlackTree.toString())
+ println(redBlackTree.toString(mode = Tree.TreeStringMode.WIDTH))
+ println(redBlackTree.toString(mode = Tree.TreeStringMode.HEIGHT))
+```
+
+#### Вывод:
+
+
+
+
+
+
+
+## Лицензия
+
+Распространяется по лицензии MIT. См. файл `LICENSE.txt` для получения дополнительной информации.
+
+## Авторы
+
+- [AlexandrKudrya](https://github.com/AlexandrKudrya)
+- [7gambit7](https://github.com/7gambit7)
+- [VersusXX](https://github.com/VersusXX)
+
+[license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge:
+[license-url]: https://github.com/spbu-coding-2023/trees-11/blob/main/LICENSE.txt
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..ce5f22f
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,57 @@
+plugins {
+ kotlin("jvm") version "1.9.22"
+ `java-library`
+ jacoco
+ application
+}
+
+group = "org.example"
+version = "1.0-SNAPSHOT"
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+}
+
+tasks.test {
+ useJUnitPlatform()
+
+ testLogging {
+ events("skipped", "failed")
+ afterSuite(
+ // spell
+ KotlinClosure2({ desc: TestDescriptor, result: TestResult ->
+ // Only execute on the outermost suite
+ if (desc.parent == null) {
+ println(" **** Result: ${result.resultType} ****")
+ println(" > Tests: ${result.testCount}")
+ println(" > Passed: ${result.successfulTestCount}")
+ println(" > Failed: ${result.failedTestCount}")
+ println(" > Skipped: ${result.skippedTestCount}")
+ }
+ }),
+ )
+ }
+
+ reports {
+ junitXml.required = true
+ }
+
+ finalizedBy(tasks.jacocoTestReport)
+}
+
+tasks.named("jacocoTestReport") {
+ dependsOn(tasks.test)
+ reports {
+ csv.required = true
+ xml.required = false
+ html.required = false
+ }
+}
+
+kotlin {
+ jvmToolchain(21)
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..7fc6f1f
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7ad9c0a
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Mar 15 18:28:58 MSK 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..d861336
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
+}
+rootProject.name = "temp"
diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt
new file mode 100644
index 0000000..242fbfc
--- /dev/null
+++ b/src/main/kotlin/AVLTree.kt
@@ -0,0 +1,188 @@
+class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tree>(root) {
+ override fun add(
+ key: K,
+ value: V,
+ ) {
+ this.root = insert(this.root as Node.AVLNode?, key, value)
+ }
+
+ private fun height(node: Node.AVLNode?): Int {
+ if (node == null) return 0
+
+ return node.height
+ }
+
+ private fun max(
+ a: Int,
+ b: Int,
+ ): Int {
+ return if ((a > b)) a else b
+ }
+
+ private fun getBalance(node: Node.AVLNode?): Int {
+ if (node == null) return 0
+
+ return height(node.left as Node.AVLNode?) - height(node.right as Node.AVLNode?)
+ }
+
+ private fun insert(
+ node: Node.AVLNode?,
+ key: K,
+ value: V,
+ ): Node.AVLNode? {
+ if (node == null) return Node.AVLNode(key, value)
+
+ if (key < node.key) {
+ node.left = insert(node.left as Node.AVLNode?, key, value)
+ } else if (key > node.key) {
+ node.right = insert(node.right as Node.AVLNode?, key, value)
+ } else {
+ return node
+ }
+
+ node.height = 1 + max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?))
+
+ val balance = getBalance(node)
+
+ if (balance > 1 && key < node.left!!.key) return rightRotate(node)
+
+ if (balance < -1 && key > node.right!!.key) return leftRotate(node)
+
+ if (balance > 1 && key > node.left!!.key) {
+ node.left = leftRotate(node.left as Node.AVLNode?)
+ return rightRotate(node)
+ }
+
+ if (balance < -1 && key < node.right!!.key) {
+ node.right = rightRotate(node.right as Node.AVLNode?)
+ return leftRotate(node)
+ }
+
+ return node
+ }
+
+ private fun deleteNode(
+ node: Node.AVLNode?,
+ key: K,
+ ): Node.AVLNode? {
+ var root = node
+ if (root == null) return null
+
+ if (key < root.key) {
+ root.left = deleteNode(root.left as Node.AVLNode?, key)
+ } else if (key > root.key) {
+ root.right = deleteNode(root.right as Node.AVLNode?, key)
+ } else {
+ if ((root.left == null) || (root.right == null)) {
+ var temp: Node.AVLNode? = null
+ temp =
+ if (temp === root.left) {
+ root.right as Node.AVLNode?
+ } else {
+ root.left as Node.AVLNode?
+ }
+
+ if (temp == null) {
+ temp = root
+ root = null
+ } else {
+ root = temp
+ }
+ } else {
+ val temp = minValueNode(root.right as Node.AVLNode?)
+
+ root.key = temp!!.key
+
+ root.right = deleteNode(root.right as Node.AVLNode?, temp.key)
+ }
+ }
+
+ if (root == null) return root
+
+ (root as Node.AVLNode?)?.height =
+ max(height(root.left as Node.AVLNode?), height(root.right as Node.AVLNode?)) + 1
+
+ val balance = getBalance(root)
+
+ if (balance > 1 && getBalance(root.left as Node.AVLNode?) >= 0) return rightRotate(root)
+
+ if (balance > 1 && getBalance(root.left as Node.AVLNode?) < 0) {
+ root.left = leftRotate(root.left as Node.AVLNode?)
+ return rightRotate(root)
+ }
+
+ if (balance < -1 && getBalance(root.right as Node.AVLNode?) <= 0) return leftRotate(root)
+
+ if (balance < -1 && getBalance(root.right as Node.AVLNode?) > 0) {
+ root.right = rightRotate(root.right as Node.AVLNode?)
+ return leftRotate(root)
+ }
+
+ return root
+ }
+
+ private fun minValueNode(node: Node.AVLNode?): Node.AVLNode? {
+ var current = node
+
+ // loop down to find the leftmost leaf
+ while (current!!.left != null) current = current.left as Node.AVLNode?
+
+ return current
+ }
+
+ private fun rightRotate(node: Node.AVLNode?): Node.AVLNode {
+ val x = node!!.left as Node.AVLNode?
+ val T2 = x!!.right as Node.AVLNode?
+
+ // Perform rotation
+ x.right = node
+ node.left = T2
+
+ // Update heights
+ node.height = max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?)) + 1
+ x.height = max(height(x.left as Node.AVLNode?), height(x.right as Node.AVLNode?)) + 1
+
+ // Return new root
+ return x
+ }
+
+ private fun leftRotate(node: Node.AVLNode?): Node.AVLNode? {
+ val y = node!!.right as Node.AVLNode?
+ val T2 = y!!.left as Node.AVLNode?
+
+ y.left = node
+ node.right = T2
+
+ node.height = max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?)) + 1
+ y.height = max(height(y.left as Node.AVLNode?), height(y.right as Node.AVLNode?)) + 1
+
+ return y
+ }
+
+ override fun merge(tree: Tree>) {
+ if (this.root != null && tree.root != null) {
+ require(this.max()!!.key < tree.min()?.key!!) {
+ "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys"
+ }
+ }
+ if (this.root == null) {
+ this.root = tree.root
+ } else {
+ tree.forEach {
+ this.add(it.key, it.value)
+ }
+ }
+ }
+
+ override fun max(): Node.AVLNode? {
+ return if (root == null) null else (root as Node.AVLNode).max()
+ }
+
+ override fun min(): Node.AVLNode? {
+ return if (root == null) null else (root as Node.AVLNode).min()
+ }
+
+ override fun delete(key: K) {
+ root = deleteNode(root as Node.AVLNode?, key)
+ }
+}
diff --git a/src/main/kotlin/BinaryTree.kt b/src/main/kotlin/BinaryTree.kt
new file mode 100644
index 0000000..56be611
--- /dev/null
+++ b/src/main/kotlin/BinaryTree.kt
@@ -0,0 +1,4 @@
+class BinaryTree, V : Any>(root: Node.BinaryNode? = null) :
+ Tree>(root)
+
+// Not sure, that this file is needed, because BinaryTree is just Tree.
diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt
new file mode 100644
index 0000000..fa9b2a2
--- /dev/null
+++ b/src/main/kotlin/Main.kt
@@ -0,0 +1,13 @@
+fun main() {
+ val binaryTree = BinaryTree()
+ val keys = arrayOf(3, 2, 1, 0, 4, 5)
+
+ keys.forEach { binaryTree.add(it, it.toString()) }
+
+ // Стандартно iterateDFS работает с mode=Tree.ModeDFS.PREORDER
+ binaryTree.iterateDFS().forEach { print(it.key.toString() + " ") }
+ println()
+ binaryTree.iterateDFS(mode = Tree.ModeDFS.INORDER).forEach { print(it.key.toString() + " ") }
+ println()
+ binaryTree.iterateDFS(mode = Tree.ModeDFS.POSTORDER).forEach { print(it.key.toString() + " ") }
+}
diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt
new file mode 100644
index 0000000..c7b82cd
--- /dev/null
+++ b/src/main/kotlin/Nodes.kt
@@ -0,0 +1,99 @@
+open class Node, V, T : Node> internal constructor(
+ var key: K,
+ var value: V,
+ internal var left: Node? = null,
+ internal var right: Node? = null,
+ internal var parent: Node? = null,
+) {
+ class BinaryNode, V>(
+ key: K,
+ value: V,
+ left: BinaryNode? = null,
+ right: BinaryNode? = null,
+ parent: BinaryNode? = null,
+ ) : Node>(key, value, left, right, parent)
+
+ class RBNode, V>(
+ key: K,
+ value: V,
+ left: RBNode? = null,
+ right: RBNode? = null,
+ parent: RBNode? = null,
+ var color: Color = Color.RED,
+ ) : Node>(key, value, left, right, parent) {
+ enum class Color { RED, BLACK }
+
+ companion object {
+ private const val RED_COLOR: String = "\u001B[31m"
+ private const val RESET_COLOR: String = "\u001B[0m"
+ var colored: Boolean = false
+ }
+
+ override fun toString(): String {
+ return if (colored && color == Color.RED) {
+ "${RED_COLOR}($key: $value)${RESET_COLOR}"
+ } else {
+ "($key: $value)"
+ }
+ }
+
+ val isOnLeft: Boolean
+ get() = this == parent?.left
+
+ fun sibling(): RBNode? {
+ if (parent == null) return null
+
+ if (isOnLeft) return parent!!.right as RBNode?
+
+ return parent!!.left as RBNode?
+ }
+
+ fun hasRedChild(): Boolean {
+ return (left != null && (left as RBNode).color == Color.RED) ||
+ (right != null && (right as RBNode).color == Color.RED)
+ }
+ }
+
+ class AVLNode, V>(
+ key: K,
+ value: V,
+ left: AVLNode? = null,
+ right: AVLNode? = null,
+ parent: AVLNode? = null,
+ var height: Int = 1,
+ ) : Node>(key, value, left, right, parent) {
+ /*override fun toString(): String {
+ val rightHeight = if (right != null) (right as AVLNode).height else 0
+ val leftHeight = if (left != null) (left as AVLNode).height else 0
+ val balance = leftHeight - rightHeight
+ return "($key: $balance)"
+ }*/
+ fun max(): AVLNode {
+ var current = this
+ while (current.right != null) {
+ current = current.right as AVLNode
+ }
+ return current
+ }
+
+ fun min(): AVLNode {
+ var current = this
+ while (current.left != null) {
+ current = current.left as AVLNode
+ }
+ return current
+ }
+ }
+
+ override fun toString(): String {
+ return "($key: $value)"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return if (other !is Node<*, *, *>) false else (this.key == other.key && this.value == other.value)
+ }
+
+ override fun hashCode(): Int {
+ return super.hashCode()
+ }
+}
diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt
new file mode 100644
index 0000000..ea5b69d
--- /dev/null
+++ b/src/main/kotlin/RBTree.kt
@@ -0,0 +1,416 @@
+import kotlin.math.ceil
+import kotlin.math.floor
+
+class RBTree, V : Any>(root: Node.RBNode? = null) : Tree>(root) {
+ override fun add(
+ key: K,
+ value: V,
+ ) {
+ val node: Node.RBNode = Node.RBNode(key, value)
+ this.addNode(node)
+ }
+
+ override fun addNode(node: Node>) {
+ super.addNode(node)
+ balanceRBTree(node as Node.RBNode)
+ }
+
+ override fun merge(tree: Tree>) {
+ if (this.root != null && tree.root != null) {
+ require(this.max()!!.key < tree.min()?.key!!) {
+ "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys"
+ }
+ }
+ if (this.root == null) {
+ this.root = tree.root
+ } else {
+ tree.forEach {
+ this.add(it.key, it.value)
+ }
+ }
+ }
+
+ override fun delete(key: K) {
+ val node = this.getNode(key) as Node.RBNode? ?: return
+
+ deleteNode(node)
+ }
+
+ private fun deleteNode(nodeToDelete: Node.RBNode) {
+ val replacementNode = BSTreplace(nodeToDelete)
+
+ // Determine if both 'replacementNode' and 'nodeToDelete' are black
+ val areBothBlack =
+ (replacementNode == null || replacementNode.color == Node.RBNode.Color.BLACK) && (nodeToDelete.color == Node.RBNode.Color.BLACK)
+
+ val parentNode = nodeToDelete.parent
+
+ // Case 1: 'nodeToDelete' has no replacement node
+ if (replacementNode == null) {
+ if (nodeToDelete == root) {
+ root = null
+ } else {
+ if (areBothBlack) {
+ // Both 'replacementNode' and 'nodeToDelete' are black, so fix double black at 'nodeToDelete'
+ fixDoubleBlack(nodeToDelete)
+ } else if (nodeToDelete.sibling() != null) {
+ nodeToDelete.sibling()!!.color = Node.RBNode.Color.RED
+ }
+ // Delete 'nodeToDelete' from the tree
+ if (nodeToDelete.isOnLeft) {
+ parentNode!!.left = null
+ } else {
+ parentNode!!.right = null
+ }
+ }
+ return
+ }
+
+ // Case 2: 'nodeToDelete' has only one child
+ if (nodeToDelete.left == null || nodeToDelete.right == null) {
+ if (nodeToDelete == root) {
+ // 'nodeToDelete' is the root, so assign the value of 'replacementNode' to 'nodeToDelete' and delete 'replacementNode'
+ nodeToDelete.key = replacementNode.key
+ nodeToDelete.right = null
+ nodeToDelete.left = null
+ // Delete 'replacementNode'
+ } else {
+ // Detach 'nodeToDelete' from the tree and move 'replacementNode' up
+ if (nodeToDelete.isOnLeft) {
+ parentNode!!.left = replacementNode
+ } else {
+ parentNode!!.right = replacementNode
+ }
+
+ replacementNode.parent = parentNode
+
+ if (areBothBlack) {
+ // Both 'replacementNode' and 'nodeToDelete' are black, so fix double black at 'replacementNode'
+ fixDoubleBlack(replacementNode)
+ } else {
+ // Either 'replacementNode' or 'nodeToDelete' is red, so color 'replacementNode' black
+ replacementNode.color = Node.RBNode.Color.BLACK
+ }
+ }
+ return
+ }
+
+ // Case 3: 'nodeToDelete' has two children, swap values with successor and recurse
+ swapValues(replacementNode, nodeToDelete)
+ deleteNode(replacementNode)
+ }
+
+ private fun swapValues(
+ nodeA: Node.RBNode,
+ nodeB: Node.RBNode,
+ ) {
+ val tempKey = nodeA.key
+ val tempValue = nodeA.value
+
+ nodeA.key = nodeB.key
+ nodeA.value = nodeB.value
+
+ nodeB.key = tempKey
+ nodeB.value = tempValue
+ }
+
+ private fun fixDoubleBlack(doubleBlackNode: Node.RBNode?) {
+ if (doubleBlackNode == null || doubleBlackNode == root) return
+
+ // Retrieve the sibling and parent of the double black node
+ val sibling = doubleBlackNode.sibling()
+ val parent = doubleBlackNode.parent as Node.RBNode?
+
+ // If there is no sibling, the double black is pushed up to its parent
+ if (sibling == null) {
+ fixDoubleBlack(parent)
+ } else {
+ if (parent == null) return
+ if (sibling.color == Node.RBNode.Color.RED) {
+ // If the sibling is red, perform rotation and color changes
+ parent.color = Node.RBNode.Color.RED
+ sibling.color = Node.RBNode.Color.BLACK
+
+ if (sibling.isOnLeft) {
+ parent.rightRotate() // Right case
+ } else {
+ parent.leftRotate() // Left case
+ }
+
+ fixDoubleBlack(doubleBlackNode) // Recursively fix double black
+ } else {
+ // If the sibling is black
+ if (sibling.hasRedChild()) {
+ // If at least one child of the sibling is red
+ val siblingLeft = sibling.left as Node.RBNode?
+ val siblingRight = sibling.right as Node.RBNode?
+ if (sibling.isOnLeft) {
+ // Left subtree of the sibling
+ if (siblingLeft?.color == Node.RBNode.Color.RED) {
+ // Left-left case
+ siblingLeft.color = sibling.color
+ sibling.color = parent.color
+ parent.rightRotate()
+ } else {
+ // Left-right case
+ siblingRight?.color = parent.color
+ sibling.leftRotate()
+ parent.rightRotate()
+ }
+ } else {
+ // Right subtree of the sibling
+ if (siblingRight?.color == Node.RBNode.Color.RED) {
+ // Right-right case
+ siblingRight.color = sibling.color
+ sibling.color = parent.color
+ parent.leftRotate()
+ } else {
+ // Right-left case
+ siblingLeft?.color = parent.color
+ sibling.rightRotate()
+ parent.leftRotate()
+ }
+ }
+ parent.color = Node.RBNode.Color.BLACK
+ } else {
+ // If both children of the sibling are black
+ sibling.color = Node.RBNode.Color.RED
+ if (parent.color == Node.RBNode.Color.BLACK) {
+ fixDoubleBlack(parent)
+ } else {
+ parent.color = Node.RBNode.Color.BLACK
+ }
+ }
+ }
+ }
+ }
+
+ private fun successor(x: Node.RBNode): Node.RBNode {
+ var temp = x
+ while (temp.left != null) temp = temp.left as Node.RBNode
+ return temp
+ }
+
+ // find node that replaces a deleted node in BST
+ private fun BSTreplace(node: Node.RBNode): Node.RBNode? {
+ // If the node has two children
+ if (node.left != null && node.right != null) {
+ return successor(node.right as Node.RBNode)
+ }
+
+ if (node.left == null && node.right == null) {
+ return null // There is no replacement
+ }
+
+ return if (node.left != null) {
+ node.left as Node.RBNode?
+ } else {
+ node.right as Node.RBNode?
+ }
+ }
+
+ fun setColored(value: Boolean) {
+ Node.RBNode.colored = value
+ }
+
+ private fun balanceRBTree(node: Node.RBNode) {
+ if (node.parent == null) {
+ node.color = Node.RBNode.Color.BLACK
+ return
+ }
+
+ val parentNode = node.parent as? Node.RBNode ?: return
+
+ if (parentNode.color == Node.RBNode.Color.BLACK) {
+ return
+ }
+ val grandparentNode = parentNode.parent as? Node.RBNode ?: return
+
+ if (parentNode == grandparentNode.left) {
+ val uncle = grandparentNode.right as? Node.RBNode
+ if (uncle?.color == Node.RBNode.Color.RED) {
+ parentNode.color = Node.RBNode.Color.BLACK
+ uncle.color = Node.RBNode.Color.BLACK
+ grandparentNode.color = Node.RBNode.Color.RED
+ balanceRBTree(grandparentNode)
+ } else {
+ if (node == parentNode.right) {
+ parentNode.leftRotate()
+ balanceRBTree(node.left as Node.RBNode)
+ } else {
+ grandparentNode.rightRotate()
+ parentNode.color = Node.RBNode.Color.BLACK
+ (parentNode.right as Node.RBNode?)?.color = Node.RBNode.Color.RED
+ }
+ }
+ } else {
+ val uncle = grandparentNode.left as? Node.RBNode
+ if (uncle?.color == Node.RBNode.Color.RED) {
+ parentNode.color = Node.RBNode.Color.BLACK
+ uncle.color = Node.RBNode.Color.BLACK
+ grandparentNode.color = Node.RBNode.Color.RED
+ balanceRBTree(grandparentNode)
+ } else {
+ if (node == parentNode.left) {
+ parentNode.rightRotate()
+ balanceRBTree((node.right as Node.RBNode))
+ } else {
+ grandparentNode.leftRotate()
+ parentNode.color = Node.RBNode.Color.BLACK
+ (parentNode.left as Node.RBNode?)?.color = Node.RBNode.Color.RED
+ }
+ }
+ }
+ }
+
+ override fun toStringBeautifulHeight(ofSide: Int): String {
+ if (this.root == null) {
+ return ""
+ } else {
+ val buffer: StringBuilder = StringBuilder()
+
+ val lines: MutableList> = mutableListOf()
+
+ var level: MutableList?> = mutableListOf()
+ var next: MutableList?> = mutableListOf()
+
+ level.add(this.root as Node.RBNode?)
+
+ var nodeNumber = 1
+ var widtest = 0
+
+ while (nodeNumber != 0) {
+ val line: MutableList = mutableListOf()
+
+ nodeNumber = 0
+
+ for (node in level) {
+ if (node == null) {
+ line.add(null)
+
+ next.add(null)
+ next.add(null)
+ } else {
+ val strNode: String = node.toString()
+ line.addLast(strNode)
+
+ val extra = if (node.color == Node.RBNode.Color.RED) 18 else 0
+
+ if (strNode.length > widtest + extra) widtest = strNode.length
+
+ next.add(node.left as Node.RBNode?)
+ next.add(node.right as Node.RBNode?)
+
+ if (node.left != null) nodeNumber++
+ if (node.right != null) nodeNumber++
+ }
+ }
+
+ widtest += widtest % 2
+
+ lines.add(line)
+ val swap = level
+ level = next
+ next = swap
+ next.clear()
+ }
+
+ var perpiece: Int = lines[lines.size - 1].size * (widtest + ofSide)
+
+ for (i in 1..perpiece / 2) buffer.append("─")
+ buffer.append("┐\n")
+
+ for (i in 0.. = lines[i]
+
+ val hpw: Int = floor(perpiece / 2f - 1).toInt()
+
+ if (i > 0) {
+ for (j in 0...leftRotate() {
+ val newRoot = this.right
+ this.right = newRoot?.left
+ if (newRoot?.left != null) {
+ newRoot.left!!.parent = this
+ }
+ newRoot?.parent = this.parent
+ if (this.parent == null) {
+ root = newRoot
+ } else if (this == this.parent?.left) {
+ this.parent?.left = newRoot
+ } else {
+ this.parent?.right = newRoot
+ }
+ newRoot?.left = this
+ this.parent = newRoot
+ }
+
+ private fun Node.RBNode.rightRotate() {
+ val newRoot = this.left
+ this.left = newRoot?.right
+ if (newRoot?.right != null) {
+ newRoot.right!!.parent = this
+ }
+ newRoot?.parent = this.parent
+ if (this.parent == null) {
+ root = newRoot
+ } else if (this == this.parent?.right) {
+ this.parent?.right = newRoot
+ } else {
+ this.parent?.left = newRoot
+ }
+ newRoot?.right = this
+ this.parent = newRoot
+ }
+}
diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt
new file mode 100644
index 0000000..67a986a
--- /dev/null
+++ b/src/main/kotlin/Tree.kt
@@ -0,0 +1,563 @@
+import java.util.*
+import kotlin.math.ceil
+import kotlin.math.floor
+
+open class Tree, V : Any, T : Node>(
+ var root: Node? = null,
+) : Iterable> {
+ open fun add(
+ key: K,
+ value: V,
+ ) {
+ val node: Node = Node(key, value)
+ this.addNode(node)
+ }
+
+ open fun addNode(node: Node) {
+ require(this.root?.key != node.key) {
+ throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function")
+ }
+ if (this.root == null) {
+ this.root = node
+ } else {
+ val root: Node = this.root!!
+ // not-null assertion operator because null check
+ val isLeft: Boolean = root.key > node.key
+ addNode(
+ node,
+ (if (isLeft) root.left else root.right),
+ root,
+ isLeft,
+ )
+ }
+ }
+
+ private tailrec fun addNode(
+ node: Node,
+ current: Node?,
+ parent: Node,
+ isLeft: Boolean,
+ ) {
+ if (current == null) {
+ if (isLeft) {
+ parent.left = node
+ } else {
+ parent.right = node
+ }
+
+ node.parent = parent
+ } else {
+ require(current.key != node.key) {
+ throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function")
+ }
+ val isAddedLeft: Boolean = current.key > node.key
+ addNode(
+ node,
+ (if (isAddedLeft) current.left else current.right),
+ current,
+ isAddedLeft,
+ )
+ }
+ }
+
+ operator fun set(
+ key: K,
+ value: V,
+ ) {
+ if (!this.isKey(key)) {
+ this.add(key, value)
+ } else {
+ this.getNode(key)!!.value = value
+ // not-null assertion operator because key is exist in tree
+ }
+ }
+
+ operator fun get(key: K): V? = getNode(key)?.value
+
+ fun getNode(key: K): Node? {
+ return getNode(key, this.root)
+ }
+
+ private tailrec fun getNode(
+ key: K,
+ current: Node?,
+ ): Node? {
+ return if (current == null) {
+ null
+ } else if (current.key == key) {
+ current
+ } else if (current.key > key) {
+ getNode(key, current.left)
+ } else {
+ getNode(key, current.right)
+ }
+ }
+
+ fun getOrDefault(
+ key: K,
+ default: V,
+ ): V {
+ val node: Node? = getNode(key)
+ return node?.value ?: default
+ }
+
+ fun getOrDefaultNode(
+ key: K,
+ default: Node,
+ ): Node {
+ return getNode(key, root) ?: default
+ }
+
+ open fun delete(key: K) {
+ if (this.getNode(key) != null) this.delete(this.getNode(key)!!)
+ }
+
+ private fun delete(current: Node) {
+ val parent = current.parent
+ if (current.left == null && current.right == null) {
+ if (parent == null) {
+ this.root = null
+ } else if (parent.left === current) {
+ parent.left = null
+ } else {
+ parent.right = null
+ }
+ } else if (current.left == null || current.right == null) {
+ if (current.left == null) {
+ if (parent == null) {
+ this.root = current.right as Node
+ } else {
+ if (parent.left === current) {
+ parent.left = current.right
+ } else {
+ parent.right = current.right
+ }
+ }
+ (current.right as Node).parent = parent
+ } else {
+ if (parent == null) {
+ this.root = current.left as Node
+ } else {
+ if (parent.left === current) {
+ parent.left = current.left
+ } else {
+ parent.right = current.left
+ }
+ (current.left as Node).parent = parent
+ }
+ }
+ } else {
+ val next = getNext(current)!!
+ current.key = next.key
+ current.value = next.value
+ if (next.parent!!.left === next) {
+ next.parent!!.left = next.right
+ if (next.right != null) {
+ next.right!!.parent = next.parent
+ }
+ } else {
+ next.parent!!.right = next.right
+ if (next.right != null) {
+ next.right!!.parent = next.parent
+ }
+ }
+
+ next.left = current.left
+ if (next.left != null) next.left!!.parent = next
+ next.right = current.right
+ if (next.right != null) next.right!!.parent = next
+ next.parent = current.parent
+ if (next.parent != null) {
+ if (next.parent!!.left === current) {
+ next.parent!!.left = next
+ } else {
+ next.parent!!.right = next
+ }
+ } else {
+ this.root = next
+ next.parent = null
+ }
+ }
+ }
+
+ fun isKey(key: K): Boolean {
+ return isKey(key, this.root)
+ }
+
+ private tailrec fun isKey(
+ key: K,
+ current: Node?,
+ ): Boolean {
+ return if (current == null) {
+ false
+ } else if (current.key == key) {
+ true
+ } else if (current.key > key) {
+ isKey(key, current.left)
+ } else {
+ isKey(key, current.right)
+ }
+ }
+
+ open fun min(): Node? {
+ val rootNow = this.root
+ return if (rootNow == null) null else min(rootNow)
+ }
+
+ internal fun min(node: Node): Node? {
+ val left = node.left
+ return if (left == null) node else min(left)
+ }
+
+ open fun max(): Node? {
+ val rootNow = this.root
+ return if (rootNow == null) null else max(rootNow)
+ }
+
+ internal fun max(node: Node): Node? {
+ val right = node.right
+ return if (right == null) node else max(right)
+ }
+
+ fun getNext(node: Node): Node? {
+ val right = node.right
+ if (right != null) {
+ // If the right subtree is not null, the successor is the leftmost node in the right subtree
+ return min(right)
+ }
+
+ // If the right subtree is null, the successor is the first ancestor whose left child is also an ancestor
+ var current = node
+ var parent = node.parent
+ while (parent != null && current == parent.right) {
+ current = parent
+ parent = parent.parent
+ }
+ return parent
+ }
+
+ fun getPrev(node: Node): Node? {
+ val left = node.left
+ if (left != null) {
+ // If the left subtree is not null, the predecessor is the rightmost node in the left subtree
+ return max(left)
+ }
+
+ // If the left subtree is null, the predecessor is the first ancestor whose right child is also an ancestor
+ var current = node
+ var parent = node.parent
+ while (parent != null && current == parent.left) {
+ current = parent
+ parent = parent.parent
+ }
+ return parent
+ }
+
+ inner class ByKeyIterator(
+ private val tree: Tree,
+ ) : Iterator> {
+ private var current: Node? = tree.min()
+
+ override fun hasNext(): Boolean {
+ return current != null
+ }
+
+ override fun next(): Node {
+ val result = current ?: throw NoSuchElementException("No more elements")
+ current = tree.getNext(result)
+ return result
+ }
+ }
+
+ inner class BFSIterator(tree: Tree) : Iterator> {
+ private var queue: Queue> = LinkedList()
+
+ init {
+ val BSIqueue: Queue> = LinkedList()
+ if (tree.root != null) {
+ BSIqueue.add(tree.root)
+ queue.add(tree.root)
+ while (!BSIqueue.isEmpty()) {
+ val current: Node = BSIqueue.poll()
+ if (current.left != null) {
+ BSIqueue.add(current.left)
+ queue.add(current.left)
+ }
+ if (current.right != null) {
+ BSIqueue.add(current.right)
+ queue.add(current.right)
+ }
+ }
+ }
+ }
+
+ override fun hasNext(): Boolean {
+ return !queue.isEmpty()
+ }
+
+ override fun next(): Node {
+ return queue.poll()
+ }
+ }
+
+ enum class ModeDFS { PREORDER, POSTORDER, INORDER }
+
+ inner class DFSIterator(tree: Tree, mode: ModeDFS = ModeDFS.PREORDER) : Iterator> {
+ private var stack: Queue> = LinkedList()
+
+ init {
+ when (mode) {
+ ModeDFS.PREORDER -> addToStackPreOrder(tree.root)
+ ModeDFS.INORDER -> addToStackInOrder(tree.root)
+ ModeDFS.POSTORDER -> addToStackPostOrder(tree.root)
+ }
+ }
+
+ private fun addToStackPreOrder(current: Node?) {
+ if (current != null) {
+ stack.add(current)
+ this.addToStackPreOrder(current.left)
+ this.addToStackPreOrder(current.right)
+ }
+ }
+
+ private fun addToStackPostOrder(current: Node?) {
+ if (current != null) {
+ this.addToStackPostOrder(current.left)
+ this.addToStackPostOrder(current.right)
+ stack.add(current)
+ }
+ }
+
+ private fun addToStackInOrder(current: Node?) {
+ if (current != null) {
+ this.addToStackInOrder(current.left)
+ stack.add(current)
+ this.addToStackInOrder(current.right)
+ }
+ }
+
+ override fun hasNext(): Boolean {
+ return !stack.isEmpty()
+ }
+
+ override fun next(): Node {
+ return stack.poll()
+ }
+ }
+
+ override fun iterator(): Iterator> {
+ return ByKeyIterator(this)
+ }
+
+ fun iterateBFS(): BFSIterator {
+ return BFSIterator(this)
+ }
+
+ fun iterateDFS(mode: ModeDFS = ModeDFS.PREORDER): DFSIterator {
+ return DFSIterator(this, mode = mode)
+ }
+
+ open fun merge(tree: Tree) {
+ if (this.root != null && tree.root != null) {
+ require(this.max()!!.key < tree.min()!!.key) {
+ "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys"
+ }
+ }
+ if (this.root == null) {
+ this.root = tree.root
+ } else {
+ this.max()!!.right = tree.root
+ }
+ }
+
+ fun split(x: K): Pair, Tree> {
+ val lowerTree: Tree = Tree()
+ val biggerTree: Tree = Tree()
+
+ for (i in this.iterateBFS()) {
+ if (i.key < x) {
+ lowerTree.addNode(i)
+ } else {
+ biggerTree.addNode(i)
+ }
+ }
+ return Pair(lowerTree, biggerTree)
+ }
+
+ fun clone(): Tree {
+ val clonedTree: Tree = Tree()
+
+ for (i in this.iterateBFS()) clonedTree.add(i.key, i.value)
+ return clonedTree
+ }
+
+ enum class TreeStringMode { NORMAL, WIDTH, HEIGHT }
+
+ fun toString(mode: TreeStringMode = TreeStringMode.NORMAL): String {
+ return when (mode) {
+ TreeStringMode.NORMAL -> this.toString()
+ TreeStringMode.WIDTH -> this.toStringBeautifulWidth()
+ TreeStringMode.HEIGHT -> this.toStringBeautifulHeight()
+ }
+ }
+
+ override fun toString(): String {
+ val buffer = StringBuilder()
+
+ buffer.append("[")
+ this.forEach { buffer.append("$it, ") }
+ val preResult = buffer.removeSuffix(", ").toString()
+
+ val result = StringBuilder()
+ result.append(preResult)
+ result.append("]")
+
+ return result.toString()
+ }
+
+ open fun toStringBeautifulWidth(): String {
+ return if (this.root == null) {
+ ""
+ } else {
+ this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString()
+ }
+ // not-null assertion operator because null check
+ }
+
+ private fun toStringBeautifulWidth(
+ prefix: StringBuilder,
+ isTail: Boolean,
+ buffer: StringBuilder,
+ current: Node,
+ ): StringBuilder {
+ if (current.right != null) {
+ val newPrefix = StringBuilder()
+ newPrefix.append(prefix)
+ newPrefix.append(if (isTail) "| " else " ")
+ this.toStringBeautifulWidth(newPrefix, false, buffer, current.right as Node)
+ }
+ buffer.append(prefix)
+ buffer.append(if (isTail) "└── " else "┌── ")
+ buffer.append(current.toString())
+ buffer.append("\n")
+ if (current.left != null) {
+ val newPrefix = StringBuilder()
+ newPrefix.append(prefix)
+ newPrefix.append(if (!isTail) "| " else " ")
+ this.toStringBeautifulWidth(newPrefix, true, buffer, current.left as Node)
+ }
+
+ return buffer
+ }
+
+ open fun toStringBeautifulHeight(ofSide: Int = 4): String {
+ if (this.root == null) {
+ return ""
+ } else {
+ val buffer: StringBuilder = StringBuilder()
+
+ val lines: MutableList> = mutableListOf()
+
+ var level: MutableList?> = mutableListOf()
+ var next: MutableList?> = mutableListOf()
+
+ level.add(this.root)
+
+ var nodeNumber = 1
+ var widtest = 0
+
+ while (nodeNumber != 0) {
+ val line: MutableList = mutableListOf()
+
+ nodeNumber = 0
+
+ for (node in level) {
+ if (node == null) {
+ line.add(null)
+
+ next.add(null)
+ next.add(null)
+ } else {
+ val strNode: String = node.toString()
+ line.addLast(strNode)
+
+ if (strNode.length > widtest) widtest = strNode.length
+
+ next.add(node.left)
+ next.add(node.right)
+
+ if (node.left != null) nodeNumber++
+ if (node.right != null) nodeNumber++
+ }
+ }
+
+ widtest += widtest % 2
+
+ lines.add(line)
+ val swap = level
+ level = next
+ next = swap
+ next.clear()
+ }
+
+ var perpiece: Int = lines[lines.size - 1].size * (widtest + ofSide)
+
+ for (i in 1..perpiece / 2) buffer.append("─")
+ buffer.append("┐\n")
+
+ for (i in 0.. = lines[i]
+
+ val hpw: Int = floor(perpiece / 2f - 1).toInt()
+
+ if (i > 0) {
+ for (j in 0..
+
+ @BeforeEach
+ fun setup() {
+ tree = AVLTree()
+ }
+
+ // max and min tests
+ @Test
+ fun `max should return if empty`() {
+ assert(tree.max()?.key == null)
+ }
+
+ @Test
+ fun `min should return if empty`() {
+ assert(tree.min()?.key == null)
+ }
+
+ @Test
+ fun `max should return max key`() {
+ tree.add(1, "A")
+ tree.add(2, "B")
+ tree.add(3, "C")
+ tree.add(4, "D")
+ tree.add(5, "E")
+ tree.add(6, "F")
+ tree.add(7, "G")
+ tree.add(8, "H")
+ tree.add(9, "I")
+ tree.add(10, "J")
+
+ assert(tree.max()?.key == 10)
+ }
+
+ @Test
+ fun `min should return min key`() {
+ tree.add(1, "A")
+ tree.add(2, "B")
+ tree.add(3, "C")
+ tree.add(4, "D")
+ tree.add(5, "E")
+ tree.add(6, "F")
+ tree.add(7, "G")
+ tree.add(8, "H")
+ tree.add(9, "I")
+ tree.add(10, "J")
+
+ assert(tree.min()?.key == 1)
+ }
+}
diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt
new file mode 100644
index 0000000..1a514ae
--- /dev/null
+++ b/src/test/kotlin/AVLTreeTest.kt
@@ -0,0 +1,359 @@
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class AVLTreeTest {
+ private lateinit var tree: AVLTree
+
+ @BeforeEach
+ fun setup() {
+ tree = AVLTree()
+ }
+
+ @Nested
+ inner class AddTests {
+ @Test
+ fun `Inserting a node into an empty tree`() {
+ val key = 1
+ val value = "0"
+
+ tree.add(key, value)
+ assertEquals(tree[key], value)
+ }
+
+ @Test
+ fun `Inserting a duplicate node`() {
+ tree.add(1, "0")
+ tree.add(1, "0")
+
+ val root = tree.root
+ assert(root == tree.getNode(1))
+ assert(root?.right == null)
+ assert(root?.left == null)
+ }
+
+ @Test
+ fun `Inserting with left rotate`() {
+ val key1 = 0
+ val key2 = 1
+ val key3 = 2
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+
+ val root = tree.root
+ assert(root?.key == key2)
+ assert(root?.left!!.key == key1)
+ assert(root.right!!.key == key3)
+ }
+
+ @Test
+ fun `Inserting with right rotate`() {
+ val key1 = 2
+ val key2 = 1
+ val key3 = 0
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+
+ val root = tree.root
+ assert(root?.key == key2)
+ assert(root?.left!!.key == key3)
+ assert(root.right!!.key == key1)
+ }
+
+ @Test
+ fun `Inserting with big left rotate (last node)`() {
+ val key1 = 2
+ val key2 = 1
+ val key3 = 5
+ val key4 = 3
+ val key5 = 6
+ val key6 = 4
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+ tree.add(key4, key4.toString())
+ tree.add(key5, key5.toString())
+ tree.add(key6, key6.toString())
+
+ val root = tree.root
+ assert(root == tree.getNode(3))
+ }
+
+ @Test
+ fun `Inserting with big right rotate (last node)`() {
+ val key1 = 5
+ val key2 = 6
+ val key3 = 4
+ val key4 = 2
+ val key5 = 3
+ val key6 = 1
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+ tree.add(key4, key4.toString())
+ tree.add(key5, key5.toString())
+ tree.add(key6, key6.toString())
+
+ val root = tree.root
+ assert(root == tree.getNode(3))
+ }
+ }
+
+ @Nested
+ inner class MinMaxTests() {
+ @Test
+ fun `Max should return max node`() {
+ for (i in 1..10) {
+ tree.add(i, "")
+ }
+ assert(tree.max()?.key == 10)
+ }
+
+ @Test
+ fun `Max from null tree returns null`() {
+ assert(tree.max() == null)
+ }
+
+ @Test
+ fun `Min should return min node`() {
+ for (i in 1..10) {
+ tree.add(i, "")
+ }
+ assert(tree.min()?.key == 1)
+ }
+
+ @Test
+ fun `Min from null tree returns null`() {
+ assert(tree.min() == null)
+ }
+ }
+
+ @Nested
+ inner class DeleteTests {
+ @Test
+ fun `Deletion of the non-existent node should do nothing`() {
+ tree.add(1, "0")
+ tree.add(2, "890")
+
+ tree.delete(4)
+
+ assert(tree.getNode(1) != null)
+ assert(tree.getNode(2) != null)
+ }
+
+ @Test
+ fun `Deletion of tree's root`() {
+ val keyLeft = 0
+ val keyRoot = 1
+ val keyRight = 2
+
+ tree.add(keyLeft, keyLeft.toString())
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(keyRight, keyRight.toString())
+
+ tree.delete(1)
+
+ val root = tree.root
+ assert(tree.getNode(1) == null)
+ assert(tree.getNode(keyRight) == root)
+ assert(root?.right == null)
+ }
+
+ @Test
+ fun `Deletion of leaf(node with no children)`() {
+ val keyRoot = 0
+ val key = 1
+ val childKey = 2
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.add(childKey, childKey.toString())
+
+ tree.delete(2)
+
+ val root = tree.root
+ assert(tree.getNode(childKey) == null)
+ assert(root?.right == null)
+ assert(root?.left == tree.getNode(0))
+ }
+
+ @Test
+ fun `Deletion of node with one child`() {
+ val key1 = 3
+ val key2 = 2
+ val key3 = 1
+ val key4 = 0
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+ tree.add(key4, key4.toString())
+
+ tree.delete(1)
+
+ val root = tree.root
+ assert(tree.getNode(1) == null)
+ assert(tree.getNode(0) == root?.left)
+ }
+
+ @Test
+ fun `Deletion of node with two children`() {
+ val key1 = 2
+ val key2 = 1
+ val key3 = 5
+ val key4 = 3
+ val key5 = 6
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+ tree.add(key4, key4.toString())
+ tree.add(key5, key5.toString())
+
+ tree.delete(5)
+
+ val root = tree.root
+ assert(tree.getNode(5) == null)
+ assert(root?.right == tree.getNode(key5))
+
+ val newNode = tree.getNode(key5)
+ val childNewNode = newNode?.left
+ assert(newNode?.key!! > childNewNode?.key!!)
+ }
+
+ @Test
+ fun `Deletion of node with two children, which also have child`() {
+ val key1 = 2
+ val key2 = 1
+ val key3 = 5
+ val key4 = 3
+ val key5 = 6
+ val key6 = 0
+ val key7 = 4
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+ tree.add(key4, key4.toString())
+ tree.add(key5, key5.toString())
+ tree.add(key6, key6.toString())
+ tree.add(key7, key7.toString())
+
+ tree.delete(5)
+
+ val newNode = tree.getNode(key7)
+ val root = tree.root
+
+ assert(tree.getNode(5) == null)
+ assert(newNode == root?.right)
+ assert(newNode?.right != null && newNode.left != null)
+ }
+
+ @Test
+ fun `Deletion of node with two children, which also have child (version 2)`() {
+ val key1 = 5
+ val key2 = 2
+ val key3 = 6
+ val key4 = 0
+ val key5 = 3
+ val key6 = 7
+ val key7 = 4
+
+ tree.add(key1, key1.toString())
+ tree.add(key2, key2.toString())
+ tree.add(key3, key3.toString())
+ tree.add(key4, key4.toString())
+ tree.add(key5, key5.toString())
+ tree.add(key6, key6.toString())
+ tree.add(key7, key7.toString())
+
+ tree.delete(6)
+
+ val newNode = tree.getNode(key1)
+ val root = tree.root
+
+ assert(tree.getNode(6) == null)
+ assert(newNode == root?.right)
+ assert(newNode?.right != null && newNode.left != null)
+ }
+ }
+
+ @Nested
+ inner class MergeTests {
+ @Test
+ fun `Merge should work correctly on 2 empty trees`() {
+ val secondTree = AVLTree()
+
+ tree.merge(secondTree)
+
+ assert(tree.root == null)
+ }
+
+ @Test
+ fun `should merge second tree with empty tree`() {
+ val secondTree = AVLTree()
+ secondTree.add(1, "value1")
+ secondTree.add(2, "value2")
+
+ tree.merge(secondTree)
+
+ assert(secondTree.root == tree.root)
+ }
+
+ @Test
+ fun `should merge empty second tree with initial tree`() {
+ val secondTree = AVLTree()
+ tree.add(1, "value1")
+ tree.add(2, "value2")
+
+ tree.merge(secondTree)
+
+ assert(tree.root == tree.root)
+ }
+
+ @Test
+ fun `should merge non empty trees`() {
+ val secondTree = AVLTree()
+ tree.add(1, "value1")
+ tree.add(2, "value2")
+ secondTree.add(3, "value3")
+ secondTree.add(4, "value4")
+
+ tree.merge(secondTree)
+
+ // Check if the resulting tree contains all elements from both trees
+ var treeNodeCount = 0
+ tree.iterator().forEach { _ -> treeNodeCount++ }
+ assert("value1" == tree.getNode(1)?.value)
+ assert("value2" == tree.getNode(2)?.value)
+ assert("value3" == tree.getNode(3)?.value)
+ assert("value4" == tree.getNode(4)?.value)
+ }
+
+ @Test
+ fun `Merge should throw IllegalArgumentException on attempt to merge tree that isn't bigger than this`() {
+ val secondTree = AVLTree()
+ val keys = arrayOf(0, 1, -1)
+ val secondKeys = arrayOf(3, 4, 0)
+
+ keys.forEach { tree.add(it, it.toString()) }
+ secondKeys.forEach { secondTree.add(it, it.toString()) }
+
+ val message =
+ "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys"
+
+ val exception =
+ assertFailsWith {
+ tree.merge(secondTree)
+ }
+ assert(exception.message == message)
+ }
+ }
+}
diff --git a/src/test/kotlin/BFSIteratorTest.kt b/src/test/kotlin/BFSIteratorTest.kt
new file mode 100644
index 0000000..7feb5ec
--- /dev/null
+++ b/src/test/kotlin/BFSIteratorTest.kt
@@ -0,0 +1,29 @@
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class BFSIteratorTest {
+ private lateinit var tree: BinaryTree
+
+ @BeforeEach
+ fun setup() {
+ tree = BinaryTree()
+ }
+
+ @Test
+ fun `BFS should work correctly empty tree`() {
+ var counter = 0
+ for (i in tree.iterateBFS()) counter++
+ assert(counter == 0)
+ }
+
+ @Test
+ fun `DFS should work correctly simple tree sample`() {
+ val keys = arrayOf(2, 1, 3, 0, 4)
+ val result = mutableListOf()
+ val expectedResult = arrayOf(2, 1, 3, 0, 4)
+ keys.forEach { tree.add(it, it.toString()) }
+ for (i in tree.iterateBFS()) result.add(i.key)
+
+ for (i in keys.indices) assert(result[i] == expectedResult[i])
+ }
+}
diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt
new file mode 100644
index 0000000..541eb4b
--- /dev/null
+++ b/src/test/kotlin/BinaryTreeTest.kt
@@ -0,0 +1,770 @@
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import kotlin.test.assertFailsWith
+
+class BinaryTreeTest {
+ private lateinit var tree: BinaryTree
+
+ @BeforeEach
+ fun setup() {
+ tree = BinaryTree()
+ }
+
+ // Positive
+
+ @Test
+ fun `value should be added to empty tree`() {
+ val expectedResult = "A"
+ val key = 1
+
+ tree.add(key, expectedResult)
+
+ assert(tree[key] contentEquals expectedResult)
+ }
+
+ @Test
+ fun `root shouldn't be null after adding element to empty tree`() {
+ val expectedResult = "A"
+ val key = 1
+
+ tree.add(key, expectedResult)
+
+ assert(tree.root != null)
+ }
+
+ @Test
+ fun `value should added correctly left-left`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(firstKey, firstKey.toString())
+
+ assert(
+ tree.root?.key == thirdKey &&
+ tree.root?.left?.key == secondKey &&
+ tree.root?.left?.left?.key == firstKey,
+ )
+ }
+
+ @Test
+ fun `value should added correctly left-right`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(firstKey, firstKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ assert(
+ tree.root?.key == thirdKey &&
+ tree.root?.left?.key == firstKey &&
+ tree.root?.left?.right?.key == secondKey,
+ )
+ }
+
+ @Test
+ fun `value should added correctly right-left`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ assert(
+ tree.root?.key == firstKey &&
+ tree.root?.right?.key == thirdKey &&
+ tree.root?.right?.left?.key == secondKey,
+ )
+ }
+
+ @Test
+ fun `value should added correctly right-right`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+
+ assert(
+ tree.root?.key == firstKey &&
+ tree.root?.right?.key == secondKey &&
+ tree.root?.right?.right?.key == thirdKey,
+ )
+ }
+
+ @Test
+ fun `Value should be added after set if it not in tree`() {
+ val expectedResult = "A"
+ val key = 1
+
+ tree[key] = expectedResult
+
+ assert(tree[key] contentEquals expectedResult)
+ }
+
+ @Test
+ fun `Value should be changed after set if it in tree`() {
+ val firstValue = "B"
+ val expectedResult = "A"
+ val key = 1
+ tree[key] = firstValue
+ tree[key] = expectedResult
+
+ assert(tree[key] contentEquals expectedResult)
+ }
+
+ @Test
+ fun `Get should return value`() {
+ val key = 1
+ val expectedResult = "A"
+ tree.add(key, expectedResult)
+ assert(tree[key] contentEquals expectedResult)
+ }
+
+ @Test
+ fun `Get should return value left-left`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(firstKey, firstKey.toString())
+
+ assert(tree[firstKey] == firstKey.toString())
+ }
+
+ @Test
+ fun `Get should return value left-right`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(firstKey, firstKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ assert(tree[secondKey] == secondKey.toString())
+ }
+
+ @Test
+ fun `Get should return value right-left`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ assert(tree[secondKey] == secondKey.toString())
+ }
+
+ @Test
+ fun `Get should return value right-right`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+
+ assert(tree[thirdKey] == thirdKey.toString())
+ }
+
+ @Test
+ fun `GetOrDefault should return value if it exist`() {
+ val key = 1
+ val expectedResult = "A"
+
+ tree.add(key, expectedResult)
+ assert(tree.getOrDefault(key, "B") == "A")
+ }
+
+ @Test
+ fun `GetOrDefault should return default if it's not exist`() {
+ val key = 1
+ val fakeKey = 2
+ val expectedResult = "A"
+
+ tree.add(key, expectedResult)
+ assert(tree.getOrDefault(fakeKey, "B") == "B")
+ }
+
+ @Test
+ fun `IsKey should return true if key is exist (zig-zag)`() {
+ val keys = arrayOf(5, 1, 4, 2, 3)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ assert(tree.isKey(3))
+ }
+
+ @Test
+ fun `IsKey should return false if key isn't exist (zig-zag)`() {
+ val keys = arrayOf(6, 1, 5, 2, 4)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ assert(!tree.isKey(3))
+ }
+
+ @Test
+ fun `Min should return null from empty tree`() {
+ assert(tree.min() == null)
+ }
+
+ @Test
+ fun `Min should return min from nonempty tree`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(firstKey, firstKey.toString())
+
+ assert(tree.min()?.key == firstKey)
+ }
+
+ @Test
+ fun `Max should return null from empty tree`() {
+ assert(tree.max() == null)
+ }
+
+ @Test
+ fun `Max should return min from nonempty tree`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(firstKey, firstKey.toString())
+
+ assert(tree.max()?.key == thirdKey)
+ }
+
+ @Test
+ fun `getNext should return min of right child if it exist`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+
+ val root = tree.root
+ val right = root?.right
+ if (right != null) {
+ assert(tree.getNext(root) === tree.min(right))
+ } else {
+ assert(false)
+ }
+ }
+
+ @Test
+ fun `getNext should return parent if right child isn't exist`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(firstKey, firstKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ val secondNode = tree.getNode(secondKey)
+ val thirdNode = tree.getNode(thirdKey)
+
+ if (secondNode != null) {
+ assert(tree.getNext(secondNode) === thirdNode)
+ } else {
+ assert(false)
+ }
+ }
+
+ @Test
+ fun `getPrev should return max of left child if it exist`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+ tree.add(firstKey, firstKey.toString())
+
+ val root = tree.root
+ val left = root?.left
+ if (left != null) {
+ assert(tree.getPrev(root) === tree.max(left))
+ } else {
+ assert(false)
+ }
+ }
+
+ @Test
+ fun `getPrev should return parent if left child isn't exist`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ val secondNode = tree.getNode(secondKey)
+ val firstNode = tree.getNode(firstKey)
+
+ if (secondNode != null) {
+ assert(tree.getPrev(secondNode) === firstNode)
+ } else {
+ assert(false)
+ }
+ }
+
+ @Test
+ fun `Merge should make other root to this root if this root is null`() {
+ val secondTree = BinaryTree()
+
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ secondTree.merge(tree)
+
+ assert(secondTree.root === tree.root)
+ }
+
+ @Test
+ fun `Merge should make other root to this max if this root isn't null`() {
+ val secondTree = BinaryTree()
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ secondTree.add(firstKey - 3, firstKey.toString())
+ secondTree.add(secondKey - 3, secondKey.toString())
+ secondTree.add(thirdKey - 3, thirdKey.toString())
+
+ val max = secondTree.max()
+ secondTree.merge(tree)
+
+ assert(max?.right === tree.root)
+ }
+
+ @Test
+ fun `Merge should work correctly on 2 empty trees`() {
+ val secondTree = BinaryTree()
+
+ tree.merge(secondTree)
+
+ assert(tree.root == null)
+ }
+
+ @Test
+ fun `Split should work correctly`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+
+ val (small, big) = tree.split(secondKey)
+
+ assert(small.root === tree.getNode(firstKey) && big.root === tree.getNode(thirdKey))
+ }
+
+ @Test
+ fun `Clone should clone only content not nodes`() {
+ val firstKey = -1
+ val secondKey = 0
+ val thirdKey = 1
+
+ tree.add(firstKey, firstKey.toString())
+ tree.add(thirdKey, thirdKey.toString())
+ tree.add(secondKey, secondKey.toString())
+
+ val clonedTree = tree.clone()
+
+ assert(
+ tree.getNode(firstKey) == clonedTree.getNode(firstKey) &&
+ tree.getNode(firstKey) !== clonedTree.getNode(firstKey) &&
+ tree.getNode(secondKey) == clonedTree.getNode(secondKey) &&
+ tree.getNode(secondKey) !== clonedTree.getNode(secondKey) &&
+ tree.getNode(thirdKey) == clonedTree.getNode(thirdKey) &&
+ tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey),
+ )
+ }
+
+ @Test
+ fun `Delete from empty tree if key isn't in tree should work correctly(do nothing)`() {
+ tree.delete(15)
+ assert(true)
+ }
+
+ @Test
+ fun `Delete should work correctly no children root`() {
+ val key = 1
+
+ tree.add(key, key.toString())
+ tree.delete(key)
+
+ assert(tree.root == null)
+ }
+
+ @Test
+ fun `Delete should work correctly no children not-root left`() {
+ val keyRoot = 0
+ val key = -1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.delete(key)
+
+ val root = tree.root
+ assert(tree.getNode(keyRoot) === root && root != null && root.left == null)
+ }
+
+ @Test
+ fun `Delete should work correctly no children not-root right`() {
+ val keyRoot = 0
+ val key = 1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.delete(key)
+
+ val root = tree.root
+ assert(tree.getNode(keyRoot) === root && root != null && root.right == null)
+ }
+
+ @Test
+ fun `Delete should work correctly left child root`() {
+ val keyRoot = 0
+ val key = -1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+
+ tree.delete(keyRoot)
+
+ assert(tree.getNode(key) === tree.root)
+ }
+
+ @Test
+ fun `Delete should work correctly right child root`() {
+ val keyRoot = 0
+ val key = 1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.delete(keyRoot)
+
+ val root = tree.root
+ assert(tree.getNode(key) === root)
+ }
+
+ @Test
+ fun `Delete should work correctly left child not-root left`() {
+ val keyRoot = 0
+ val key = -1
+ val childKey = -2
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.add(childKey, childKey.toString())
+
+ val root = tree.root
+ tree.delete(key)
+
+ assert(tree.getNode(keyRoot) === root)
+ assert(root != null && root.left === tree.getNode(childKey))
+ assert(tree.getNode(childKey)?.parent === root)
+ }
+
+ @Test
+ fun `Delete should work correctly right child not-root left`() {
+ val keyRoot = 0
+ val key = -2
+ val childKey = -1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.add(childKey, childKey.toString())
+
+ val root = tree.root
+ tree.delete(key)
+
+ assert(tree.getNode(keyRoot) === root)
+ assert(root != null && root.left === tree.getNode(childKey))
+ assert(tree.getNode(childKey)?.parent === root)
+ }
+
+ @Test
+ fun `Delete should work correctly left child not-root right`() {
+ val keyRoot = 0
+ val key = 2
+ val childKey = 1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.add(childKey, childKey.toString())
+
+ val root = tree.root
+ tree.delete(key)
+
+ assert(tree.getNode(keyRoot) === root)
+ assert(root != null && root.right === tree.getNode(childKey))
+ assert(tree.getNode(childKey)?.parent === root)
+ }
+
+ @Test
+ fun `Delete should work correctly right child not-root right`() {
+ val keyRoot = 0
+ val key = 1
+ val childKey = 2
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+ tree.add(childKey, childKey.toString())
+
+ val root = tree.root
+ tree.delete(key)
+
+ assert(
+ tree.getNode(keyRoot) === root &&
+ root != null &&
+ root.right === tree.getNode(childKey) &&
+ tree.getNode(childKey)?.parent === root,
+ )
+ }
+
+ // Madness is following...
+
+ @Test
+ fun `Delete should work correctly 2 child root where next haven't children`() {
+ val keyRoot = 0
+ val keyRight = 1
+ val keyLeft = -1
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(keyRight, keyRight.toString())
+ tree.add(keyLeft, keyLeft.toString())
+
+ tree.delete(keyRoot)
+
+ val root = tree.root
+ assert(tree.getNode(keyRight) === root)
+ assert(root != null && root.left === tree.getNode(keyLeft))
+ assert(tree.getNode(keyLeft)?.parent === root)
+ }
+
+ @Test
+ fun `Delete should work correctly 2 child root where next has not left children`() {
+ // scenario explanation on picture
+ //
+ // ┌── 3 ┌── 3
+ // | | ┌── 2 | └── 2
+ // | └── 1 ──> 1
+ // 0 (0 delete) └── -1
+ // └── -1
+
+ val keys = arrayOf(0, -1, 3, 1, 2)
+
+ keys.forEach { tree.add(it, it.toString()) }
+
+ val next = tree.getNext(tree.root!!)
+
+ tree.delete(keys[0])
+
+ val root = tree.root
+ if (root != null) {
+ assert(root == next)
+ assert(root.left === tree.getNode(keys[1]))
+ assert(tree.getNode(keys[1])?.parent === root)
+ assert(root.right === tree.getNode(keys[2]))
+ assert(tree.getNode(keys[2])?.parent === root)
+ assert(root.right != null && root.right?.left === tree.getNode(keys[4]))
+ assert(tree.getNode(keys[4])?.parent === root.right)
+ } else {
+ assert(false)
+ }
+ }
+
+ @Test
+ fun `delete should work correctly 2 child not-root`() {
+ // scenario explanation on picture
+ // ┌── 3 ┌── 3
+ // ┌── 2 | └── 1
+ // | └── 1 ──> 0
+ // 0 (2 delete) └── -1
+ // └── -1
+
+ val keys = arrayOf(0, -1, 2, 1, 3)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ val node = tree.getNode(keys[4])
+ tree.delete(keys[2])
+
+ val root = tree.root
+ if (root != null) {
+ assert(root.left === tree.getNode(keys[1]))
+ assert(tree.getNode(keys[1])?.parent === root)
+ assert(node == root.right && root.right?.left === tree.getNode(keys[3]))
+ } else {
+ assert(false)
+ }
+ }
+
+ @Test
+ fun `delete should work correctly 2 child not-root but next is left child of its parent`() {
+ // scenario explanation on picture
+ // 0 0
+ // | ┌── -1 | ┌── -1
+ // | ┌── -2 ──> └── -2
+ // └── -3 (-3 delete) └── -4
+ // └── -4
+
+ val keys = arrayOf(0, -3, -2, -4, -1)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ tree.delete(keys[1])
+
+ val root = tree.root
+ val node = tree.getNode(keys[2])
+ if (root != null && node != null) {
+ assert(root.left === node)
+ assert(node.parent === root)
+ assert(node.left === tree.getNode(keys[3]))
+ assert(tree.getNode(keys[3])?.parent === node)
+ assert(node.right === tree.getNode(keys[4]))
+ assert(tree.getNode(keys[4])?.parent === node)
+ } else {
+ assert(false)
+ }
+ }
+
+ // Yes, writing test on this function is very easy
+
+ @Test
+ fun `toStringBeautifulWidth should work correctly on empty tree`() {
+ assert(tree.toStringBeautifulWidth() == "")
+ }
+
+ @Test
+ fun `toStringBeautifulWidth should work correctly on sample`() {
+ // no-child, left, right and 2 child node example
+ // because it works similar on all this scenarios
+ val keys = arrayOf(0, 1, -1, 2, -2)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ val strTree = tree.toStringBeautifulWidth()
+ val expectedResult = """| ┌── (2: 2)
+| ┌── (1: 1)
+└── (0: 0)
+ └── (-1: -1)
+ └── (-2: -2)
+"""
+ assert(strTree == expectedResult)
+ }
+
+ @Test
+ fun `toStringBeautifulHeight should work correctly on empty tree`() {
+ assert(tree.toStringBeautifulHeight() == "")
+ }
+
+ @Test
+ fun `toStringBeautifulHeight should work correctly on sample`() {
+ // no-child, left, right and 2 child node example
+ // because it works similar on all this scenarios
+ val keys = arrayOf(0, 1, -1, 2, -2)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ val strTree = tree.toStringBeautifulHeight()
+ val expectedResult = """────────────────────────┐
+ (0: 0)
+ ┌───────────┴───────────┐
+ (-1: -1) (1: 1)
+ ┌─────┘ └─────┐
+ (-2: -2) (2: 2)
+"""
+ assert(strTree == expectedResult)
+ }
+
+ @Test
+ fun `toString should work correctly on empty tree`() {
+ assert(tree.toString() == "[]")
+ }
+
+ @Test
+ fun `toString should work correctly on sample`() {
+ // no-child, left, right and 2 child node example
+ // because it works similar on all this scenarios
+ val keys = arrayOf(0, 1, -1, 2, -2)
+ keys.forEach { tree.add(it, it.toString()) }
+
+ val strTree = tree.toString()
+ val expectedResult = "[(-2: -2), (-1: -1), (0: 0), (1: 1), (2: 2)]"
+ assert(strTree == expectedResult)
+ }
+
+ // Negative
+
+ @Test
+ fun `Add should throw IllegalArgumentException on attempt to add key to root that already exist `() {
+ val key = 1
+ val message =
+ "Multiple uses of key: $key. To modify value use set function"
+
+ tree.add(key, key.toString())
+
+ val exception =
+ assertFailsWith {
+ tree.add(key, key.toString())
+ }
+ assert(exception.message == message)
+ }
+
+ @Test
+ fun `Add should throw IllegalArgumentException on attempt to add key to not-root that already exist`() {
+ val keyRoot = 0
+ val key = 1
+ val message =
+ "Multiple uses of key: $key. To modify value use set function"
+ tree.add(keyRoot, keyRoot.toString())
+ tree.add(key, key.toString())
+
+ val exception =
+ assertFailsWith {
+ tree.add(key, key.toString())
+ }
+ assert(exception.message == message)
+ }
+
+ @Test
+ fun `Merge should throw IllegalArgumentException on attempt to merge tree that isn't bigger than this`() {
+ val secondTree = BinaryTree()
+ val keys = arrayOf(0, 1, -1)
+ val secondKeys = arrayOf(3, 4, 0)
+
+ keys.forEach { tree.add(it, it.toString()) }
+ secondKeys.forEach { secondTree.add(it, it.toString()) }
+
+ val message =
+ "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys"
+
+ val exception =
+ assertFailsWith {
+ tree.merge(secondTree)
+ }
+ assert(exception.message == message)
+ }
+}
diff --git a/src/test/kotlin/ByKeyIteratorTest.kt b/src/test/kotlin/ByKeyIteratorTest.kt
new file mode 100644
index 0000000..65fc303
--- /dev/null
+++ b/src/test/kotlin/ByKeyIteratorTest.kt
@@ -0,0 +1,29 @@
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class ByKeyIteratorTest {
+ private lateinit var tree: BinaryTree
+
+ @BeforeEach
+ fun setup() {
+ tree = BinaryTree()
+ }
+
+ @Test
+ fun `BFS should work correctly empty tree`() {
+ var counter = 0
+ for (i in tree) counter++
+ assert(counter == 0)
+ }
+
+ @Test
+ fun `DFS should work correctly simple tree sample`() {
+ val keys = arrayOf(2, 1, 3, 0, 4)
+ val result = mutableListOf()
+ val expectedResult = arrayOf(0, 1, 2, 3, 4)
+ keys.forEach { tree.add(it, it.toString()) }
+ for (i in tree) result.add(i.key)
+
+ for (i in keys.indices) assert(result[i] == expectedResult[i])
+ }
+}
diff --git a/src/test/kotlin/DFSIteratorTest.kt b/src/test/kotlin/DFSIteratorTest.kt
new file mode 100644
index 0000000..0a0ee34
--- /dev/null
+++ b/src/test/kotlin/DFSIteratorTest.kt
@@ -0,0 +1,51 @@
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class DFSIteratorTest {
+ private lateinit var tree: BinaryTree
+
+ @BeforeEach
+ fun setup() {
+ tree = BinaryTree()
+ }
+
+ @Test
+ fun `DFS should work correctly empty tree`() {
+ var counter = 0
+ for (i in tree.iterateDFS()) counter++
+ assert(counter == 0)
+ }
+
+ @Test
+ fun `DFS should work correctly simple tree sample simple mode`() {
+ val keys = arrayOf(2, 1, 3, 0, 4)
+ val result = mutableListOf()
+ val expectedResult = arrayOf(2, 1, 0, 3, 4)
+ keys.forEach { tree.add(it, it.toString()) }
+ for (i in tree.iterateDFS()) result.add(i.key)
+
+ for (i in keys.indices) assert(result[i] == expectedResult[i])
+ }
+
+ @Test
+ fun `DFS should work correctly simple tree sample symmetric mode`() {
+ val keys = arrayOf(2, 1, 3, 0, 4)
+ val result = mutableListOf()
+ val expectedResult = arrayOf(0, 1, 2, 3, 4)
+ keys.forEach { tree.add(it, it.toString()) }
+ for (i in tree.iterateDFS(mode = Tree.ModeDFS.INORDER)) result.add(i.key)
+
+ for (i in keys.indices) assert(result[i] == expectedResult[i])
+ }
+
+ @Test
+ fun `DFS should work correctly simple tree sample reverse mode`() {
+ val keys = arrayOf(2, 1, 3, 0, 4)
+ val result = mutableListOf()
+ val expectedResult = arrayOf(0, 1, 4, 3, 2)
+ keys.forEach { tree.add(it, it.toString()) }
+ for (i in tree.iterateDFS(mode = Tree.ModeDFS.POSTORDER)) result.add(i.key)
+
+ for (i in keys.indices) assert(result[i] == expectedResult[i])
+ }
+}
diff --git a/src/test/kotlin/NodeTest.kt b/src/test/kotlin/NodeTest.kt
new file mode 100644
index 0000000..8051a60
--- /dev/null
+++ b/src/test/kotlin/NodeTest.kt
@@ -0,0 +1,63 @@
+import org.junit.jupiter.api.Test
+
+class NodeTest {
+ @Test
+ fun `Node should be equal if values and keys are equal`() {
+ val key = 15
+ val value = "Mitochondria"
+
+ val nodeOne = Node>(key, value)
+ val nodeSecond = Node>(key, value)
+
+ assert(nodeOne == nodeSecond)
+ }
+
+ @Test
+ fun `Node should be equal if values aren't equal`() {
+ val key = 15
+ val valueOne = "Mitochondria"
+ val valueTwo = "Mit0ch0ndr1a"
+
+ val nodeOne = Node>(key, valueOne)
+ val nodeSecond = Node>(key, valueTwo)
+
+ assert(nodeOne != nodeSecond)
+ }
+
+ @Test
+ fun `Node should be equal if keys aren't equal`() {
+ val keyOne = 15
+ val keyTwo = 150
+ val value = "Mitochondria"
+
+ val nodeOne = Node>(keyOne, value)
+ val nodeSecond = Node>(keyTwo, value)
+
+ assert(nodeOne != nodeSecond)
+ }
+
+ @Test
+ fun `Node should be equal if keys aren't equal and values aren't equal`() {
+ val keyOne = 15
+ val keyTwo = 150
+ val valueOne = "Mitochondria"
+ val valueTwo = "Mit0ch0ndr1a"
+
+ val nodeOne = Node>(keyOne, valueOne)
+ val nodeSecond = Node>(keyTwo, valueTwo)
+
+ assert(nodeOne != nodeSecond)
+ }
+
+ @Test
+ fun `Node shouldn't be equal to notNode`() {
+ val keyOne = 15
+ val valueOne = "Mitochondria"
+
+ val nodeOne = Node>(keyOne, valueOne)
+
+ val notNode = arrayOf(keyOne, valueOne)
+
+ assert(nodeOne != notNode)
+ }
+}
diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt
new file mode 100644
index 0000000..7b7b250
--- /dev/null
+++ b/src/test/kotlin/RBNodeTest.kt
@@ -0,0 +1,126 @@
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class RBNodeTest {
+ private lateinit var tree: RBTree
+
+ @BeforeEach
+ fun setup() {
+ tree = RBTree()
+ }
+
+ // toString tests
+ @Test
+ fun `should return null if tree is empty`() {
+ assert(tree.root?.toString() == null)
+ }
+
+ @Test
+ fun `should return node with black color`() {
+ tree.add(1, "A")
+ tree.add(2, "B")
+ tree.add(3, "C")
+
+ assert(tree.getNode(1)?.toString() == "(1: A)")
+ }
+
+ @Test
+ fun `should return node with red color`() {
+ tree.add(1, "A")
+ tree.add(2, "B")
+ tree.add(3, "C")
+ tree.setColored(true)
+
+ assert(tree.getNode(1)?.toString() == "\u001B[31m(1: A)\u001B[0m")
+ }
+
+ // isOnLeft tests
+ @Test
+ fun `isOnLeft should return false to root node`() {
+ tree.add(1, "A")
+ assert(!(tree.root as Node.RBNode).isOnLeft)
+ }
+
+ @Test
+ fun `isOnLeft should return true`() {
+ tree.add(1, "A")
+ tree.add(2, "D")
+ tree.add(3, "C")
+
+ assert((tree.getNode(1) as Node.RBNode