diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..097f9f9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,9 @@
+#
+# https://help.github.com/articles/dealing-with-line-endings/
+#
+# Linux start script should use lf
+/gradlew text eol=lf
+
+# These are Windows script files and should use crlf
+*.bat text eol=crlf
+
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 0000000..44b952f
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,35 @@
+name: Build and test
+
+on:
+ workflow_dispatch:
+
+ push:
+ branches: [ "main", "dev" ]
+ pull_request:
+ branches: [ "main" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Build source code
+ run: ./gradlew build -x test
+
+ - name: Run tests
+ run: ./gradlew test >./test-res-out.log 2>./test-res-err.log
+ continue-on-error: true
+
+ - name: Display test results
+ run: python3 ./scripts/test-result-printer.py --dir ./lib/build/test-results/test --all-failures
+
+ - name: Run jacoco coverage report
+ run: ./gradlew jacocoTestReport >./test-res-out.log 2>./test-res-err.log
+
+ - name: Display info about coverage
+ run: python3 ./scripts/csv-reports-printer.py --input ./lib/build/reports/jacoco/info.csv --lib lib
+
+ - name: Clear tmpfiles of runnig tests
+ run: rm ./test-res-out.log && rm ./test-res-err.log
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..05ede4d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+### Gradle console-app generated template
+# Ignore Gradle project-specific cache directory
+.gradle
+
+# Ignore Gradle build output directory
+*/build/*
+
+### VSCode Template
+.vscode/
+
+### IntelliJ IDEA Template
+.idea/
+*.iml
+
+### Apple MacOS folder attributes
+*.DS_Store
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..22f3e1d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Ilia Suponev, Maxim Rodionov, Vladimir Zaikin
+
+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..406ed99
--- /dev/null
+++ b/ReadMe.md
@@ -0,0 +1,270 @@
+[//]: # (Project readme template from https://github.com/othneildrew/Best-README-Template/)
+
+
+[](https://choosealicense.com/licenses/mit/)
+
+
+
TreeTripper
+
+## Description
+
+Library `TreeTripper`: provides implementations of the following binary search tree data structures:
+- [x] [`Binary Search Tree`](lib/src/main/kotlin/tree_tripper/binary_trees/BSTree.kt), see more [information](https://en.wikipedia.org/wiki/Binary_search_tree)
+- [x] [`AVL tree`](lib/src/main/kotlin/tree_tripper/binary_trees/AVLTree.kt), see more [information](https://en.wikipedia.org/wiki/AVL_tree)
+- [x] [`Red-black tree`](lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt),
+ see more [information](https://en.wikipedia.org/wiki/Left-leaning_red%E2%80%93black_tree)
+
+> [!IMPORTANT]
+> The red-black tree is implemented based on the algorithm
+> of left-linear red-black trees described by Robert Sedgewick
+> on his [website](https://sedgewick.io/) and [presentation](https://sedgewick.io/wp-content/uploads/2022/03/2008-09LLRB.pdf) about it
+
+The library supports the extension both internally (future library updates) and externally (implemented by the user).
+
+## Getting started
+To run building library execute command:
+```bash
+./gradlew build -x test
+```
+
+To run tests of library execute command:
+```bash
+./gradlew test
+```
+
+## Using library
+
+### Basic operations
++ `insert`, see [docs](lib/src/main/kotlin/tree_tripper/SearchTree.kt#L17)
++ `search`, see [docs](lib/src/main/kotlin/tree_tripper/SearchTree.kt#L50)
++ `remove`, see [docs](lib/src/main/kotlin/tree_tripper/SearchTree.kt#L36)
+
+### Examples
+
+##### Example 1 (importing)
+```kotlin
+import tree_tripper.binary_trees.BSTree
+import tree_tripper.binary_trees.AVLTree
+import tree_tripper.binary_trees.RBTree
+
+
+val simpleTree = BSTree() // initialization of empty simple binary search tree
+val avlTree = AVLTree() // initialization of empty AVL tree
+val rbTree = RBTree>() // initialization of empty Red-Black tree
+```
+
+##### Example 2 (inserting)
+Code:
+```kotlin
+import tree_tripper.binary_trees.BSTree
+
+fun main() {
+ val tree = BSTree()
+
+ tree.insert(key = 1, value = 1)
+ tree.insert(key = 2, value = 2)
+ tree.insert(key = 3, value = 3)
+
+ /* The words `key` and `value` are optional */
+ tree.insert(4, 4)
+
+ /* Alternative insert method */
+ tree[5] = 5
+
+ println(tree)
+}
+```
+Output:
+```text
+BSTree(1: 1, 2: 2, 3: 3, 4: 4, 5: 5)
+```
+
+##### Example 3 (searching)
+Code:
+```kotlin
+import tree_tripper.binary_trees.BSTree
+
+fun main() {
+ val tree = BSTree()
+ /*
+ ...
+ inserting from `example 2`
+ ...
+ */
+
+ /* Existing element in tree */
+ println(tree.search(key = 1))
+ println(tree.search(key = 3))
+ println(tree.search(key = 5))
+
+ /* Unexciting element in tree */
+ println(tree.search(key = -2))
+ println(tree.searchOrDefault(7, "Element not found"))
+
+ /* Alternative search method */
+ println(tree[2])
+ println(tree[0])
+}
+```
+Output:
+```text
+1
+3
+5
+null
+Element not found
+2
+null
+```
+
+##### Example 4 (removing)
+Code:
+```kotlin
+import tree_tripper.binary_trees.BSTree
+
+fun main() {
+ val tree = BSTree()
+ /*
+ ...
+ inserting from `example 2`
+ ...
+ */
+
+ /* Existing element in tree */
+ println(tree.remove(key = 1))
+ println(tree.remove(key = 3))
+ println(tree.remove(key = 5))
+
+ /* Unexciting element in tree */
+ println(tree.remove(key = -2))
+ println(tree.removeOrDefault(key = 7, "Element not found"))
+
+ println(tree)
+}
+```
+Output:
+```text
+1
+3
+5
+null
+Element not found
+BSTree(2: 2, 4: 4)
+```
+
+##### Example 5 (tree-like printing)
+Code:
+```kotlin
+import tree_tripper.binary_trees.BSTree
+import tree_tripper.binary_trees.AVLTree
+import tree_tripper.binary_trees.RBTree
+
+fun main() {
+ val simpleTree = BSTree()
+ val avlTree = AVLTree()
+ val rbTree = RBTree()
+ /*
+ ...
+ inserting similar to `example 2` for each tree
+ ...
+ */
+
+ println("${simpleTree.toStringWithTreeView()}\n")
+ println("${avlTree.toStringWithTreeView()}\n")
+ println("${rbTree.toStringWithTreeView()}\n")
+}
+```
+Output:
+```text
+BSTree(
+ (5: 5)
+ (4: 4)
+ (3: 3)
+ (2: 2)
+(1: 1)
+)
+
+AVLTree(
+ (5: 5)
+ (4: 4)
+ (3: 3)
+(2: 2)
+ (1: 1)
+)
+
+RBTree(
+ (5: 5) - BLACK
+(4: 4) - BLACK
+ (3: 3) - BLACK
+ (2: 2) - RED
+ (1: 1) - BLACK
+)
+```
+
+##### Example 6 (iterators)
+Code:
+```kotlin
+import tree_tripper.binary_trees.AVLTree
+
+fun main() {
+ val tree = AVLTree()
+ /*
+ ...
+ inserting from `example 2`
+ ...
+ */
+
+ println("WIDTH ORDER:")
+ tree.forEach(IterationOrders.WIDTH_ORDER) {
+ println(it)
+ }
+ println()
+ println("INCREASING ORDER:")
+ tree.forEach(IterationOrders.INCREASING_ORDER) {
+ println(it)
+ }
+ println()
+ println("DECREASING ORDER:")
+ tree.forEach(IterationOrders.DECREASING_ORDER) {
+ println(it)
+ }
+}
+```
+Output:
+```text
+WIDTH ORDER:
+(2: 2)
+(1: 1)
+(4: 4)
+(3: 3)
+(5: 5)
+
+INCREASING ORDER:
+(1: 1)
+(2: 2)
+(3: 3)
+(4: 4)
+(5: 5)
+
+DECREASING ORDER:
+(5: 5)
+(4: 4)
+(3: 3)
+(2: 2)
+(1: 1)
+```
+
+## Documentation
+See more [_**documentation**_](lib/src/main/kotlin/tree_tripper/SearchTree.kt) of library `TreeTripper` to learn more about it.
+
+## Authors
+
+- [@IliaSuponeff](https://github.com/IliaSuponeff)
+- [@RodionovMaxim05](https://github.com/RodionovMaxim05)
+- [@Friend-zva](https://github.com/Friend-zva), sometimes in commits his name is _**Vladimir Zaikin**_
+
+## License
+
+Distributed under the [MIT License](https://choosealicense.com/licenses/mit/). See [`LICENSE`](LICENSE) for more information.
+
+(back to top)
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..18f452c
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,6 @@
+# This file was generated by the Gradle 'init' task.
+# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
+
+org.gradle.parallel=true
+org.gradle.caching=true
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..4f41755
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,13 @@
+# This file was generated by the Gradle 'init' task.
+# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
+
+[versions]
+commons-math3 = "3.6.1"
+guava = "32.1.3-jre"
+
+[libraries]
+commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" }
+guava = { module = "com.google.guava:guava", version.ref = "guava" }
+
+[plugins]
+jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.9.20" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
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..a80b22c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1aa94a4
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/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/HEAD/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
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ 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
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# 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..25da30d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts
new file mode 100644
index 0000000..62c75da
--- /dev/null
+++ b/lib/build.gradle.kts
@@ -0,0 +1,51 @@
+plugins {
+ // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
+ alias(libs.plugins.jvm)
+
+ // Code coverage plugin
+ jacoco
+}
+
+repositories {
+ // Use Maven Central for resolving dependencies.
+ mavenCentral()
+}
+
+dependencies {
+ // This dependency is exported to consumers, that is to say found on their compile classpath.
+ api(libs.commons.math3)
+ // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
+ // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params
+ testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
+ // This dependency is used internally, and not exposed to consumers on their own compile classpath.
+ implementation(libs.guava)
+}
+
+// Apply a specific Java toolchain to ease working on different environments.
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(19)
+ }
+}
+
+tasks.named("test") {
+ useJUnitPlatform()
+ testLogging {
+ showCauses = false
+ showStackTraces = false
+ showExceptions = false
+ }
+}
+
+tasks.named("jacocoTestReport") {
+ val sep = File.separator
+ val jacocoReportsDirName = "reports${sep}jacoco"
+ reports {
+ csv.required = true
+ xml.required = false
+ html.required = true
+ csv.outputLocation = layout.buildDirectory.file("${jacocoReportsDirName}${sep}info.csv")
+ html.outputLocation = layout.buildDirectory.dir("${jacocoReportsDirName}${sep}html")
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/kotlin/tree_tripper/SearchTree.kt b/lib/src/main/kotlin/tree_tripper/SearchTree.kt
new file mode 100644
index 0000000..e7773ee
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/SearchTree.kt
@@ -0,0 +1,107 @@
+package tree_tripper
+
+import tree_tripper.iterators.IterationOrders
+
+
+/**
+ * The interface represents a search tree, that inherits from [Iterable] interface.
+ *
+ * @param K the key type in a tree, supporting the [Comparable] interface
+ * @param V the value type in a tree
+ */
+public interface SearchTree, V>: Iterable> {
+
+ /**
+ * Inserts or updates the specified [value] with the specified [key] in a tree.
+ */
+ public fun insert(key: K, value: V)
+
+ /**
+ * Inserts the specified [value] with the specified [key] in a tree.
+ * @return true if the specified [value] with the specified [key] was inserted in a tree,
+ * false if a tree was not modified.
+ */
+ public fun insertIfAbsent(key: K, value: V): Boolean
+
+ /**
+ * Associates the specified [value] with the specified [key] in a tree.
+ */
+ public operator fun set(key: K, value: V)
+
+ /**
+ * Removes a given [key] and its corresponding value from a tree.
+ * @return the removed value associated with a given [key], or
+ * null if such a [key] does not present in a tree.
+ */
+ public fun remove(key: K): V?
+
+ /**
+ * Removes a given [key] and its corresponding value from a tree.
+ * @return the removed value associated with a given [key], or
+ * the [defaultValue] if such a [key] does not present in a tree.
+ */
+ public fun removeOrDefault(key: K, defaultValue: V): V
+
+ /**
+ * Searches the value associated with a given [key].
+ * @return the value associated with a given [key], or
+ * null if such a [key] does not present in a tree.
+ */
+ public fun search(key: K): V?
+
+ /**
+ * Searches the value associated with a given [key].
+ * @return the value associated with a given [key], or
+ * the [defaultValue] if such a [key] does not present in a tree.
+ */
+ public fun searchOrDefault(key: K, defaultValue: V): V
+
+ /**
+ * Checks if a given [key] is contained in a tree.
+ */
+ public fun contains(key: K): Boolean
+
+ /**
+ * Returns the value associated with a given [key], or
+ * null if such a [key] does not present in a tree.
+ */
+ public operator fun get(key: K): V?
+
+ /**
+ * Returns the key/value pair with the max key, or null if a tree is empty.
+ */
+ public fun getMax(): Pair?
+
+ /**
+ * Returns the key/value pair with the max key in subtree with a given [key] in root, or
+ * null if such a [key] does not present in a tree.
+ */
+ public fun getMaxInSubtree(key: K): Pair?
+
+ /**
+ * Returns the key/value pair with the min key, or null if a tree is empty.
+ */
+ public fun getMin(): Pair?
+
+ /**
+ * Returns the key/value pair with the min key in subtree with a given [key] in root, or
+ * null if such a [key] does not present in a tree.
+ */
+ public fun getMinInSubtree(key: K): Pair?
+
+ // Iterator
+ public fun iterator(order: IterationOrders): Iterator>
+
+ public fun forEach(order: IterationOrders, action: (Pair) -> Unit)
+
+ /**
+ * Returns a string with a transformed tree according to the [order].
+ */
+ public fun toString(order: IterationOrders): String
+
+ /**
+ * Returns a string with a transformed tree according to the tree structure.
+ */
+ public fun toStringWithTreeView(): String
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/binary_trees/AVLTree.kt b/lib/src/main/kotlin/tree_tripper/binary_trees/AVLTree.kt
new file mode 100644
index 0000000..0ea16fe
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/binary_trees/AVLTree.kt
@@ -0,0 +1,89 @@
+package tree_tripper.binary_trees
+
+import tree_tripper.nodes.binary_nodes.AVLTreeNode
+
+
+/**
+ * AVLTree class represents a self-balancing binary search tree that maintains
+ * the property of AVL tree, ensuring that the heights
+ * of the two child subtrees of any node differ by at most one.
+ *
+ * @param K the type of the keys in the tree
+ * @param V the value type associated with the key
+ */
+public open class AVLTree, V>: AbstractBSTree>() {
+
+ override fun createNode(key: K, value: V): AVLTreeNode {
+ return AVLTreeNode(key, value)
+ }
+
+ override fun balanceTree(node: AVLTreeNode): AVLTreeNode {
+ node.updateHeight()
+ return balance(node)
+ }
+
+ /**
+ * @param node the AVLTreeNode for which to calculate the balance factor
+ * @return balance factor (height difference of his children) of the [node]
+ */
+ protected fun balanceFactor(node: AVLTreeNode?): Int {
+ return (node?.rightChild?.height ?: 0) - (node?.leftChild?.height ?: 0)
+ }
+
+ /**
+ * Defines and calls the method(s) for balancing the current [node].
+ *
+ * @param node the node to balance in the AVL tree
+ * @return the root of the rebalanced subtree or a [node] if balance is not required
+ */
+ protected fun balance(node: AVLTreeNode): AVLTreeNode {
+ when (balanceFactor(node)) {
+ -2 -> {
+ if (balanceFactor(node.leftChild) == 1)
+ node.leftChild = rotateLeft(node.leftChild as AVLTreeNode)
+ return rotateRight(node)
+ }
+ 2 -> {
+ if (balanceFactor(node.rightChild) == -1)
+ node.rightChild = rotateRight(node.rightChild as AVLTreeNode)
+ return rotateLeft(node)
+ }
+ else -> return node
+ }
+ }
+
+ /**
+ * Performs a left rotation (counterclockwise) of the tree with the [node] as the root.
+ * Updates the heights of the affected nodes.
+ *
+ * @param node the node to perform a left rotation on
+ * @returns the root of the rotated subtree
+ */
+ protected fun rotateLeft(node: AVLTreeNode): AVLTreeNode {
+ val nodeSwapped = node.rightChild ?: return node
+ node.rightChild = nodeSwapped.leftChild
+ nodeSwapped.leftChild = node
+
+ node.updateHeight()
+ nodeSwapped.updateHeight()
+ return nodeSwapped
+ }
+
+ /**
+ * Performs a right rotation (clockwise) of the tree with the [node] as the root.
+ * Updates the heights of the affected nodes.
+ *
+ * @param node the node to perform a right rotation on
+ * @returns the root of the rotated subtree
+ */
+ protected fun rotateRight(node: AVLTreeNode): AVLTreeNode {
+ val nodeSwapped = node.leftChild ?: return node
+ node.leftChild = nodeSwapped.rightChild
+ nodeSwapped.rightChild = node
+
+ node.updateHeight()
+ nodeSwapped.updateHeight()
+ return nodeSwapped
+ }
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/binary_trees/AbstractBSTree.kt b/lib/src/main/kotlin/tree_tripper/binary_trees/AbstractBSTree.kt
new file mode 100644
index 0000000..e507d30
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/binary_trees/AbstractBSTree.kt
@@ -0,0 +1,247 @@
+package tree_tripper.binary_trees
+
+import tree_tripper.SearchTree
+import tree_tripper.iterators.BinarySearchTreeIterator
+import tree_tripper.iterators.IterationOrders
+import tree_tripper.nodes.binary_nodes.AbstractBSTreeNode
+import tree_tripper.nodes.notNullNodeAction
+
+
+/**
+ * The class represents an abstract binary search tree,
+ * from which binary search trees are inherited.
+ *
+ * @param K the key type in a tree, supporting the [Comparable] interface
+ * @param V the value type in a tree
+ * @param N the node type in a tree
+ */
+public abstract class AbstractBSTree, V, N: AbstractBSTreeNode>: SearchTree {
+ protected var root: N? = null
+ private set
+ public var size: Int = 0
+ private set
+
+ override fun insert(key: K, value: V) {
+ insert(key, value, permissionUpdate = true)
+ }
+
+ override fun insertIfAbsent(key: K, value: V): Boolean {
+ return insert(key, value, permissionUpdate = false)
+ }
+
+ override fun set(key: K, value: V) {
+ insert(key, value)
+ }
+
+ override fun remove(key: K): V? {
+ val resultRemove = removeNode(root, key)
+ updateRoot(resultRemove.first)
+ if (resultRemove.second != null) size--
+ return resultRemove.second
+ }
+
+ override fun removeOrDefault(key: K, defaultValue: V): V {
+ return remove(key) ?: defaultValue
+ }
+
+ override fun search(key: K): V? {
+ return searchNode(key)?.value
+ }
+
+ override fun searchOrDefault(key: K, defaultValue: V): V {
+ return search(key) ?: defaultValue
+ }
+
+ override fun contains(key: K): Boolean {
+ return search(key) != null
+ }
+
+ override fun get(key: K): V? {
+ return search(key)
+ }
+
+ override fun getMaxInSubtree(key: K): Pair? {
+ val resultSearch = getMaxNodeInSubtree(searchNode(key)) ?: return null
+ return Pair(resultSearch.key, resultSearch.value)
+ }
+
+ override fun getMax(): Pair? {
+ return notNullNodeAction(root, null) { node -> getMaxInSubtree(node.key) }
+ }
+
+ override fun getMinInSubtree(key: K): Pair? {
+ val resultSearch = getMinNodeInSubtree(searchNode(key)) ?: return null
+ return Pair(resultSearch.key, resultSearch.value)
+ }
+
+ override fun getMin(): Pair? {
+ return notNullNodeAction(root, null) { node -> getMinInSubtree(node.key) }
+ }
+
+ override fun iterator(): BinarySearchTreeIterator {
+ return iterator(IterationOrders.WIDTH_ORDER)
+ }
+
+ override fun iterator(order: IterationOrders): BinarySearchTreeIterator {
+ return BinarySearchTreeIterator(root, order)
+ }
+
+ override fun forEach(order: IterationOrders, action: (Pair) -> Unit) {
+ val treeIterator: BinarySearchTreeIterator = iterator(order)
+ while (treeIterator.hasNext())
+ action(treeIterator.next())
+ }
+
+ override fun toString(): String {
+ return toString(IterationOrders.WIDTH_ORDER)
+ }
+
+ override fun toString(order: IterationOrders): String {
+ val builder = StringBuilder()
+ this.forEach(order) { pair: Pair -> builder.append("${pair.first}: ${pair.second}, ") }
+ if (builder.isNotEmpty())
+ repeat(2) { builder.deleteCharAt(builder.length - 1) } // remove ", " in the last pair
+ return "${this.javaClass.simpleName}($builder)"
+ }
+
+ override fun toStringWithTreeView(): String {
+ val builder = StringBuilder()
+ notNullNodeAction(root, Unit) { node -> node.toStringWithSubtreeView(0, builder) }
+ return "${this.javaClass.simpleName}(\n$builder)"
+ }
+
+ /**
+ * Returns a new [N] node with the specified [value] with the specified [key].
+ */
+ protected abstract fun createNode(key: K, value: V): N
+
+ /**
+ * Changes the root to a given [node].
+ */
+ protected open fun updateRoot(node: N?) {
+ root = node
+ }
+
+ /**
+ * Balances subtree with a given [node] at the top.
+ */
+ protected open fun balanceTree(node: N): N {
+ return node
+ }
+
+ /**
+ * Inserts the specified [value] with the specified [key] in a tree or
+ * updates [value] if [permissionUpdate] is true
+ * @return true if the specified [value] with the specified [key] was inserted in a tree,
+ * false if a tree was not modified.
+ */
+ private fun insert(key: K, value: V, permissionUpdate: Boolean): Boolean {
+ val insertResult: Pair = insertNode(root, key, value, permissionUpdate)
+ updateRoot(insertResult.first)
+ if (insertResult.second) size++
+ return insertResult.second
+ }
+
+ /**
+ * Add recursively the node with the specified [value] with the specified [key] in a tree or
+ * updates [value] if [permissionUpdate] is true and balances tree on every call.
+ * @return a pair of a new or balanced [N] node, and true if the specified [value] with
+ * the specified [key] was inserted in a tree, false if not.
+ */
+ private fun insertNode(node: N?, key: K, value: V, permissionUpdate: Boolean): Pair {
+ if (node == null) return Pair(createNode(key, value), true)
+
+ val resultInsert: Pair
+ val resultCompare: Int = key.compareTo(node.key)
+ if (resultCompare < 0) {
+ resultInsert = insertNode(node.leftChild, key, value, permissionUpdate)
+ node.leftChild = resultInsert.first
+ } else if (resultCompare > 0) {
+ resultInsert = insertNode(node.rightChild, key, value, permissionUpdate)
+ node.rightChild = resultInsert.first
+ } else {
+ if (permissionUpdate) node.value = value
+ return Pair(node, false)
+ }
+
+ return Pair(balanceTree(node), resultInsert.second)
+ }
+
+ /**
+ * Removes recursively the node with a given [key] and balances tree on every call.
+ * @return a pair of a balanced [N] node or null, and [V] value corresponding the given [key]
+ * if a node was removed, null if not.
+ */
+ protected open fun removeNode(node: N?, key: K): Pair {
+ if (node == null) return Pair(null, null)
+
+ val resultRemove: Pair
+ val resultCompare: Int = key.compareTo(node.key)
+ if (resultCompare < 0) {
+ resultRemove = removeNode(node.leftChild, key)
+ node.leftChild = resultRemove.first
+ } else if (resultCompare > 0) {
+ resultRemove = removeNode(node.rightChild, key)
+ node.rightChild = resultRemove.first
+ } else {
+ val nodeSubstitutive: N?
+ if (node.leftChild == null || node.rightChild == null) {
+ nodeSubstitutive = node.leftChild ?: node.rightChild
+ return Pair(nodeSubstitutive, node.value)
+ }
+ nodeSubstitutive = getMaxNodeInSubtree(node.leftChild) as N
+ node.leftChild = removeNode(node.leftChild, nodeSubstitutive.key).first
+ nodeSubstitutive.rightChild = node.rightChild
+ nodeSubstitutive.leftChild = node.leftChild
+ return Pair(balanceTree(nodeSubstitutive), node.value)
+ }
+
+ return Pair(balanceTree(node), resultRemove.second)
+ }
+
+ /**
+ * Searches the node with a given key.
+ * @return the found node or null if the node is not contained in a tree.
+ */
+ private fun searchNode(key: K): N? {
+ var nodeCurrent: N? = root ?: return null
+
+ while (nodeCurrent != null) {
+ val resultCompare: Int = key.compareTo(nodeCurrent.key)
+ if (resultCompare < 0)
+ nodeCurrent = nodeCurrent.leftChild
+ else if (resultCompare > 0)
+ nodeCurrent = nodeCurrent.rightChild
+ else
+ return nodeCurrent
+ }
+ return null
+ }
+
+ /**
+ * Returns the [N] node with the max key in subtree with a given [node] in root, or null
+ * if such a [node] is not contained in a tree.
+ */
+ protected fun getMaxNodeInSubtree(node: N?): N? {
+ if (node == null) return null
+
+ var nodeCurrent: N = node
+ while (true)
+ nodeCurrent = nodeCurrent.rightChild ?: break
+ return nodeCurrent
+ }
+
+ /**
+ * Returns the [N] node with the min key in subtree with a given [node] in root, or null
+ * if such a [node] is not contained in a tree.
+ */
+ protected fun getMinNodeInSubtree(node: N?): N? {
+ if (node == null) return null
+
+ var nodeCurrent: N = node
+ while (true)
+ nodeCurrent = nodeCurrent.leftChild ?: break
+ return nodeCurrent
+ }
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/binary_trees/BSTree.kt b/lib/src/main/kotlin/tree_tripper/binary_trees/BSTree.kt
new file mode 100644
index 0000000..5f790c6
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/binary_trees/BSTree.kt
@@ -0,0 +1,18 @@
+package tree_tripper.binary_trees
+
+import tree_tripper.nodes.binary_nodes.BSTreeNode
+
+
+/**
+ * The class represents the binary search tree.
+ *
+ * @param K the key type in the tree, supporting the [Comparable] interface
+ * @param V the value type in the tree
+ */
+public open class BSTree, V>: AbstractBSTree>() {
+
+ override fun createNode(key: K, value: V): BSTreeNode {
+ return BSTreeNode(key, value)
+ }
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt b/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt
new file mode 100644
index 0000000..12aa1ac
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/binary_trees/RBTree.kt
@@ -0,0 +1,205 @@
+package tree_tripper.binary_trees
+
+import tree_tripper.nodes.binary_nodes.RBTreeNode
+import tree_tripper.nodes.notNullNodeAction
+import tree_tripper.nodes.notNullNodeUpdate
+
+
+/**
+ * A class that represents a red-black tree data structure.
+ *
+ * @param K the type of the keys in the tree
+ * @param V the type of the values in the tree
+ */
+public open class RBTree, V>: AbstractBSTree>() {
+
+ override fun createNode(key: K, value: V): RBTreeNode {
+ return RBTreeNode(key, value, isRed = true, leftChild = null, rightChild = null)
+ }
+
+ override fun updateRoot(node: RBTreeNode?) {
+ notNullNodeUpdate(node) { it.isRed = false }
+ super.updateRoot(node)
+ }
+
+ override fun balanceTree(node: RBTreeNode): RBTreeNode {
+ var nodeCurrent = node
+ if (isRedColor(nodeCurrent.rightChild) && !isRedColor(nodeCurrent.leftChild)) {
+ nodeCurrent = rotateLeft(nodeCurrent)
+ }
+ if (isRedColor(nodeCurrent.leftChild) && isRedLeftChild(nodeCurrent.leftChild)) {
+ nodeCurrent = rotateRight(nodeCurrent)
+ }
+ if (isRedColor(nodeCurrent.leftChild) && isRedColor(nodeCurrent.rightChild)) {
+ flipColors(nodeCurrent)
+ }
+ return nodeCurrent
+ }
+
+ override fun removeNode(node: RBTreeNode?, key: K): Pair?, V?> {
+ if (node == null) return Pair(null, null)
+
+ val removeResult: Pair?, V?>
+ var resultCompare: Int = key.compareTo(node.key)
+ var nodeCurrent = node
+ if (resultCompare < 0) {
+ if (!isRedColor(nodeCurrent.leftChild) && !isRedLeftChild(nodeCurrent.leftChild))
+ nodeCurrent = moveRedLeft(nodeCurrent)
+
+ removeResult = removeNode(nodeCurrent.leftChild, key)
+ nodeCurrent.leftChild = removeResult.first
+ } else {
+ if (isRedColor(nodeCurrent.leftChild)) {
+ nodeCurrent = rotateRight(nodeCurrent)
+ resultCompare = key.compareTo(nodeCurrent.key)
+ }
+ if (resultCompare == 0 && nodeCurrent.rightChild == null)
+ return Pair(null, nodeCurrent.value)
+ if (!isRedColor(nodeCurrent.rightChild) && !isRedLeftChild(nodeCurrent.rightChild)) {
+ nodeCurrent = moveRedRight(nodeCurrent)
+ resultCompare = key.compareTo(nodeCurrent.key)
+ }
+ if (resultCompare == 0) {
+ val nodeWithMinimalKey = getMinNodeInSubtree(nodeCurrent.rightChild) as RBTreeNode
+ val nodeSubstitutive: RBTreeNode = createNode(nodeWithMinimalKey.key, nodeWithMinimalKey.value)
+ nodeSubstitutive.isRed = nodeCurrent.isRed
+ nodeSubstitutive.leftChild = nodeCurrent.leftChild
+ nodeSubstitutive.rightChild = removeMinNode(nodeCurrent.rightChild)
+ return Pair(balanceTree(nodeSubstitutive), nodeCurrent.value)
+ } else {
+ removeResult = removeNode(nodeCurrent.rightChild, key)
+ nodeCurrent.rightChild = removeResult.first
+ }
+ }
+
+ return Pair(balanceTree(nodeCurrent), removeResult.second)
+ }
+
+ /**
+ * Returns whether the specified node is red or not.
+ *
+ * @param node the node to check
+ * @return `true` if the node is red, `false` otherwise
+ */
+ protected fun isRedColor(node: RBTreeNode?): Boolean {
+ if (node == null) return false
+ return node.isRed
+ }
+
+ /**
+ * Returns whether the specified node is red or not.
+ *
+ * @param node the node to check color its left child
+ * @return `true` if left child of `node` is red, `false` otherwise
+ */
+ protected fun isRedLeftChild(node: RBTreeNode?): Boolean {
+ if (node == null) return false
+ return isRedColor(node.leftChild)
+ }
+
+ /**
+ * Rotates the binary tree node with the given root to the left.
+ *
+ * @param node the root of the binary tree node to rotate left
+ * @return if `node.rightChild` is null, returns `node`,
+ * otherwise `node` switches places with the right child
+ */
+ protected fun rotateLeft(node: RBTreeNode): RBTreeNode {
+ val nodeSwapped: RBTreeNode = node.rightChild ?: return node
+ node.rightChild = nodeSwapped.leftChild
+ nodeSwapped.leftChild = node
+
+ nodeSwapped.isRed = node.isRed
+ node.isRed = true
+ return nodeSwapped
+ }
+
+ /**
+ * Rotates the binary tree node with the given root to the right.
+ *
+ * @param node the binary tree node to rotate right
+ * @return if `node.leftChild` is null, returns `node`,
+ * otherwise `node` switches places with the left child
+ */
+ protected fun rotateRight(node: RBTreeNode): RBTreeNode {
+ val nodeSwapped: RBTreeNode = node.leftChild ?: return node
+ node.leftChild = nodeSwapped.rightChild
+ nodeSwapped.rightChild = node
+
+ nodeSwapped.isRed = node.isRed
+ node.isRed = true
+ return nodeSwapped
+ }
+
+ /**
+ * Flips the colors of the specified node and its children.
+ *
+ * @param node needed to flip the colors
+ */
+ protected fun flipColors(node: RBTreeNode): Unit {
+ node.isRed = !node.isRed
+ notNullNodeUpdate(node.leftChild) { child -> child.isRed = !child.isRed }
+ notNullNodeUpdate(node.rightChild) { child -> child.isRed = !child.isRed }
+ }
+
+ /**
+ * This function is used to move a red node to the right, if it has a red left child of its [node] left child.
+ * It first flips the colors of the node and its children, then rotates the tree if the left child is also red.
+ *
+ * @param node the node to move
+ * @return the new root of the tree, which is balanced node subtree
+ */
+ protected fun moveRedRight(node: RBTreeNode): RBTreeNode {
+ if (node.rightChild == null) return node
+ var nodeCurrent = node
+
+ flipColors(nodeCurrent)
+ if (isRedLeftChild(nodeCurrent.leftChild)) {
+ nodeCurrent = rotateRight(nodeCurrent)
+ flipColors(nodeCurrent)
+ }
+ return nodeCurrent
+ }
+
+ /**
+ * This function is used to move a red node to the left, if it has a red right child of its [node] left child.
+ * It first flips the colors of the node and its children, then rotates the tree if the left child is also red.
+ *
+ * @param node the node to move
+ * @return the new root of the tree, which is balanced node subtree
+ */
+ private fun moveRedLeft(node: RBTreeNode): RBTreeNode {
+ if (node.leftChild == null) return node
+ var nodeCurrent = node
+
+ flipColors(nodeCurrent)
+ if (isRedLeftChild(nodeCurrent.rightChild)) {
+ nodeCurrent.rightChild = notNullNodeAction(
+ node.rightChild, null
+ ) { rightChild -> rotateRight(rightChild) }
+ nodeCurrent = rotateLeft(nodeCurrent)
+ flipColors(nodeCurrent)
+ }
+ return nodeCurrent
+ }
+
+ /**
+ * Removes the node with the minimum key from the binary search tree.
+ *
+ * @param node the root of the binary search tree
+ * @return the root of the binary search tree with the node removed, or `null` if the tree is empty
+ */
+ protected fun removeMinNode(node: RBTreeNode?): RBTreeNode? {
+ if (node == null) return null
+ val leftChild = node.leftChild ?: return node.rightChild
+
+ var nodeCurrent = node
+ if (!isRedColor(leftChild) && !isRedLeftChild(leftChild))
+ nodeCurrent = moveRedLeft(nodeCurrent)
+
+ nodeCurrent.leftChild = removeMinNode(leftChild)
+
+ return balanceTree(nodeCurrent)
+ }
+
+}
\ No newline at end of file
diff --git a/lib/src/main/kotlin/tree_tripper/iterators/BinarySearchTreeIterator.kt b/lib/src/main/kotlin/tree_tripper/iterators/BinarySearchTreeIterator.kt
new file mode 100644
index 0000000..8fc15da
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/iterators/BinarySearchTreeIterator.kt
@@ -0,0 +1,182 @@
+package tree_tripper.iterators
+
+import tree_tripper.nodes.binary_nodes.AbstractBSTreeNode
+import java.util.LinkedList
+import java.util.Queue
+
+
+/**
+ * A generic binary search tree iterator that can iterate over a binary search tree in different orders.
+ *
+ * @param K the type of the keys in the binary search tree
+ * @param V the type of the values in the binary search tree
+ * @param N the type of the nodes in the binary search tree
+ */
+class BinarySearchTreeIterator, V, N : AbstractBSTreeNode>(
+ root: N?,
+ order: IterationOrders = IterationOrders.WIDTH_ORDER
+) : Iterator> {
+ private val iterationState: IterationState
+
+ init {
+ iterationState = when (order) {
+ IterationOrders.WIDTH_ORDER -> WidthIterationState(root)
+ IterationOrders.INCREASING_ORDER -> IncreasingIterationState(root)
+ IterationOrders.DECREASING_ORDER -> DecreasingIterationState(root)
+ }
+ }
+
+ override fun hasNext(): Boolean {
+ return iterationState.hasNext()
+ }
+
+ override fun next(): Pair {
+ return iterationState.next()
+ }
+
+ /**
+ * An interface for the different iteration states of a binary search tree iterator.
+ */
+ private interface IterationState, V, N : AbstractBSTreeNode> {
+
+ /**
+ * Returns `true` if the iteration has more elements.
+ */
+ abstract fun hasNext(): Boolean
+
+ /**
+ * Returns `true` if the iteration has more elements.
+ * @throws: NoSuchElementException - if the iteration state has no next element.
+ */
+ abstract fun next(): Pair
+
+ }
+
+ /**
+ * A concrete iteration state for a binary search tree iterator with the width iteration order.
+ */
+ private class WidthIterationState, V, N : AbstractBSTreeNode>(
+ root: N?
+ ) : IterationState {
+ private val queue: Queue = LinkedList()
+
+ init {
+ if (root != null) queue.add(root)
+ }
+
+ override fun hasNext(): Boolean {
+ return (queue.size > 0)
+ }
+
+ override fun next(): Pair {
+ if (!hasNext()) throw NoSuchElementException("Try get next element from the end of iterator state")
+ val nodeCurrent: N = queue.poll()
+ nodeCurrent.getChildren().forEach() { child ->
+ queue.add(child)
+ }
+ return Pair(nodeCurrent.key, nodeCurrent.value)
+ }
+
+ }
+
+ /**
+ * A concrete iteration state for a binary search tree iterator with the increasing iteration order.
+ */
+ private class IncreasingIterationState, V, N : AbstractBSTreeNode>(
+ root: N?
+ ) : IterationState {
+ private val unprocessedNodesStack = LinkedList()
+ private val semiProcessedNodesStack = LinkedList()
+
+ init {
+ if (root != null) unprocessedNodesStack.add(root)
+ }
+
+ override fun hasNext(): Boolean {
+ return (hasUnprocessedNodes() || hasSemiProcessedNodes())
+ }
+
+ private fun hasUnprocessedNodes(): Boolean {
+ return unprocessedNodesStack.isNotEmpty()
+ }
+
+ private fun hasSemiProcessedNodes(): Boolean {
+ return semiProcessedNodesStack.isNotEmpty()
+ }
+
+ override fun next(): Pair {
+ if (!hasNext()) throw NoSuchElementException("Try get next element from the end of iterator state")
+ var nodeCurrent: N
+
+ while (hasUnprocessedNodes()) {
+ nodeCurrent = unprocessedNodesStack.pollFirst()
+ semiProcessedNodesStack.addFirst(nodeCurrent)
+ if (nodeCurrent.leftChild != null)
+ unprocessedNodesStack.addFirst(nodeCurrent.leftChild)
+ else {
+ semiProcessedNodesStack.pollFirst()
+ if (nodeCurrent.rightChild != null)
+ unprocessedNodesStack.addFirst(nodeCurrent.rightChild)
+ return Pair(nodeCurrent.key, nodeCurrent.value)
+ }
+ }
+
+ nodeCurrent = semiProcessedNodesStack.pollFirst()
+ if (nodeCurrent.rightChild != null)
+ unprocessedNodesStack.addFirst(nodeCurrent.rightChild)
+ return Pair(nodeCurrent.key, nodeCurrent.value)
+ }
+
+ }
+
+ /**
+ * A concrete iteration state for a binary search tree iterator with the decreasing iteration order.
+ */
+ private class DecreasingIterationState, V, N : AbstractBSTreeNode>(
+ root: N?
+ ) : IterationState {
+ private val unprocessedNodesStack = LinkedList()
+ private val semiProcessedNodesStack = LinkedList()
+
+ init {
+ if (root != null) unprocessedNodesStack.add(root)
+ }
+
+ override fun hasNext(): Boolean {
+ return (hasUnprocessedNodes() || hasSemiProcessedNodes())
+ }
+
+ private fun hasSemiProcessedNodes(): Boolean {
+ return semiProcessedNodesStack.isNotEmpty()
+ }
+
+ private fun hasUnprocessedNodes(): Boolean {
+ return unprocessedNodesStack.isNotEmpty()
+ }
+
+ override fun next(): Pair {
+ if (!hasNext()) throw NoSuchElementException("Try get next element from the end of iterator state")
+ var nodeCurrent: N
+
+ while (hasUnprocessedNodes()) {
+ nodeCurrent = unprocessedNodesStack.pollFirst()
+ semiProcessedNodesStack.addFirst(nodeCurrent)
+ if (nodeCurrent.rightChild != null)
+ unprocessedNodesStack.addFirst(nodeCurrent.rightChild)
+ else {
+ semiProcessedNodesStack.pollFirst()
+ if (nodeCurrent.leftChild != null)
+ unprocessedNodesStack.addFirst(nodeCurrent.leftChild)
+ return Pair(nodeCurrent.key, nodeCurrent.value)
+ }
+ }
+
+ nodeCurrent = semiProcessedNodesStack.pollFirst()
+ if (nodeCurrent.leftChild != null)
+ unprocessedNodesStack.addFirst(nodeCurrent.leftChild)
+ return Pair(nodeCurrent.key, nodeCurrent.value)
+ }
+
+ }
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/iterators/IterationOrders.kt b/lib/src/main/kotlin/tree_tripper/iterators/IterationOrders.kt
new file mode 100644
index 0000000..7bcec24
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/iterators/IterationOrders.kt
@@ -0,0 +1,15 @@
+package tree_tripper.iterators
+
+
+/**
+ * An enumeration of the possible orders in which to iterate over the elements of a tree.
+ *
+ * @property WIDTH_ORDER iterate over the elements in order of their width in the tree
+ * @property INCREASING_ORDER iterate over the elements in order of their increasing depth in the tree
+ * @property DECREASING_ORDER iterate over the elements in order of their decreasing depth in the tree
+ */
+enum class IterationOrders {
+ WIDTH_ORDER,
+ INCREASING_ORDER,
+ DECREASING_ORDER,
+}
diff --git a/lib/src/main/kotlin/tree_tripper/nodes/SearchTreeNode.kt b/lib/src/main/kotlin/tree_tripper/nodes/SearchTreeNode.kt
new file mode 100644
index 0000000..78fb845
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/nodes/SearchTreeNode.kt
@@ -0,0 +1,28 @@
+package tree_tripper.nodes
+
+
+/**
+ * The interface represents a node of a search tree.
+ *
+ * @param K the key type of node, supporting the [Comparable] interface
+ * @param V the value type of node
+ * @param N the node type
+ */
+public interface SearchTreeNode, V, N: SearchTreeNode> {
+
+ /**
+ * Returns a [N] list of not null children of a node.
+ */
+ public fun getChildren(): List
+
+ /**
+ * Returns a string with a transformed to the simple view node.
+ */
+ public fun toStringSimpleView(): String
+
+ /**
+ * Transforms a node to the [builder] of the subtree structure with a some [indent].
+ */
+ public fun toStringWithSubtreeView(indent: Int, builder: StringBuilder): Unit
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/nodes/Utils.kt b/lib/src/main/kotlin/tree_tripper/nodes/Utils.kt
new file mode 100644
index 0000000..2fe421f
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/nodes/Utils.kt
@@ -0,0 +1,20 @@
+package tree_tripper.nodes
+
+
+/**
+ * Checks the search tree [node] for null and if it is not null then executes the [action]
+ *
+ * @return the result of applying the given [action] function to the given [node], or the [nullNodeResult] if the [node] is null.
+ */
+public fun , R> notNullNodeAction(node: N?, nullNodeResult: R, action: (N) -> (R)): R {
+ if (node == null) return nullNodeResult
+ return action(node)
+}
+
+/**
+ * Checks the search tree [node] for null and if it is not null then executes the [updateAction]
+ */
+public fun > notNullNodeUpdate(node: N?, updateAction: (N) -> (Unit)): Unit {
+ if (node == null) return
+ return updateAction(node)
+}
diff --git a/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNode.kt b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNode.kt
new file mode 100644
index 0000000..7e70f93
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNode.kt
@@ -0,0 +1,39 @@
+package tree_tripper.nodes.binary_nodes
+
+import kotlin.math.max
+
+/**
+ * AVLTreeNode class represents a node in an AVL tree, containing a key-value pair and additional
+ * information for maintaining the AVL property such as the height of the node.
+ *
+ * @param K the key type that implements the Comparable interface
+ * @param V the value type associated with the key
+ * @property height the height of the [AVLTreeNode] in the AVL tree
+ */
+public class AVLTreeNode, V>(
+ key: K,
+ value: V
+): AbstractBSTreeNode>(key, value) {
+
+ public constructor(
+ key: K, value: V, height: Int,
+ leftChild: AVLTreeNode?,
+ rightChild: AVLTreeNode?
+ ) : this(key, value) {
+ this.height = height
+ this.leftChild = leftChild
+ this.rightChild = rightChild
+ }
+
+ /** The height of the node in the AVL tree, initialized to 1. */
+ public var height: Int = 1
+ private set
+
+ /** Updates height of the node in AVL tree based on the heights of its left and right child subtrees. */
+ public fun updateHeight() {
+ val leftHeight = this.leftChild?.height ?: 0
+ val rightHeight = this.rightChild?.height ?: 0
+ height = (max(leftHeight, rightHeight) + 1)
+ }
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/AbstractBSTreeNode.kt b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/AbstractBSTreeNode.kt
new file mode 100644
index 0000000..8707bef
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/AbstractBSTreeNode.kt
@@ -0,0 +1,40 @@
+package tree_tripper.nodes.binary_nodes
+
+import tree_tripper.nodes.SearchTreeNode
+import tree_tripper.nodes.notNullNodeAction
+
+
+/**
+ * The class represents a node of the abstract binary search tree,
+ * from which nodes of binary search trees are inherited.
+ *
+ * @param K the [key] type of node, supporting the [Comparable] interface
+ * @param V the [value] type of node
+ * @param N the node type
+ */
+public abstract class AbstractBSTreeNode, V, N: AbstractBSTreeNode>(
+ public val key: K,
+ public var value: V
+): SearchTreeNode {
+ public var leftChild: N? = null
+ public var rightChild: N? = null
+
+ override fun getChildren(): List {
+ return listOfNotNull(leftChild, rightChild)
+ }
+
+ override fun toString(): String {
+ return "${this.javaClass.simpleName}(key=$key, value=$value)"
+ }
+
+ override fun toStringSimpleView(): String {
+ return "($key: $value)"
+ }
+
+ override fun toStringWithSubtreeView(indent: Int, builder: StringBuilder) {
+ notNullNodeAction(this.rightChild, Unit) {node -> node.toStringWithSubtreeView(indent + 1, builder)}
+ builder.append("\t".repeat(indent)).append(this.toStringSimpleView()).append("\n")
+ notNullNodeAction(this.leftChild, Unit) {node -> node.toStringWithSubtreeView(indent + 1, builder)}
+ }
+
+}
diff --git a/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/BSTreeNode.kt b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/BSTreeNode.kt
new file mode 100644
index 0000000..601102b
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/BSTreeNode.kt
@@ -0,0 +1,22 @@
+package tree_tripper.nodes.binary_nodes
+
+
+/**
+ * The class represents a node of the binary search tree.
+ *
+ * @param K the key type of the node, supporting the [Comparable] interface
+ * @param V the value type of the node
+ */
+public class BSTreeNode, V>(
+ key: K,
+ value: V
+) : AbstractBSTreeNode>(key, value) {
+
+ public constructor(key: K, value: V, leftChild: BSTreeNode?, rightChild: BSTreeNode?) : this(
+ key,
+ value
+ ) {
+ this.leftChild = leftChild
+ this.rightChild = rightChild
+ }
+}
diff --git a/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNode.kt b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNode.kt
new file mode 100644
index 0000000..f76dba0
--- /dev/null
+++ b/lib/src/main/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNode.kt
@@ -0,0 +1,40 @@
+package tree_tripper.nodes.binary_nodes
+
+
+/**
+ * A red-black tree node.
+ *
+ * @param K the key type
+ * @param V the value type
+ */
+public class RBTreeNode, V>(
+ key: K,
+ value: V
+) : AbstractBSTreeNode>(key, value) {
+ var isRed: Boolean = true
+
+ public constructor(key: K, value: V, isRed: Boolean) : this(key, value) {
+ this.isRed = isRed
+ }
+
+ public constructor(
+ key: K, value: V, isRed: Boolean,
+ leftChild: RBTreeNode?,
+ rightChild: RBTreeNode?
+ ) : this(key, value, isRed) {
+ this.leftChild = leftChild
+ this.rightChild = rightChild
+ }
+
+ override fun toStringSimpleView(): String {
+ return "${super.toStringSimpleView()} - ${colorName()}"
+ }
+
+ /**
+ * Returns the color name of this node.
+ */
+ private fun colorName(): String {
+ return if (isRed) "RED" else "BLACK"
+ }
+
+}
\ No newline at end of file
diff --git a/lib/src/test/kotlin/AssertionUtils.kt b/lib/src/test/kotlin/AssertionUtils.kt
new file mode 100644
index 0000000..2109199
--- /dev/null
+++ b/lib/src/test/kotlin/AssertionUtils.kt
@@ -0,0 +1,37 @@
+import org.junit.jupiter.api.Assertions
+import tree_tripper.nodes.binary_nodes.AbstractBSTreeNode
+
+
+public fun > assertBinaryNodeDataEquals(nodeFirst: N?, nodeSecond: N?): Unit {
+ assertBinaryNodeDataEquals(nodeFirst, nodeSecond) { _, _: N -> true }
+}
+
+public fun > assertBinaryNodeDataEquals(
+ nodeFirst: N?,
+ nodeSecond: N?,
+ assertAction: (N, N) -> (Boolean)
+): Unit {
+ if (nodeFirst == null) return Assertions.assertNull(nodeSecond) { "Second node is not null." }
+ if (nodeSecond == null) return Assertions.assertNull(nodeFirst) { "First node is not null." }
+
+ Assertions.assertEquals(nodeFirst.key, nodeSecond.key) { "Keys are not equal." }
+ Assertions.assertEquals(nodeFirst.value, nodeSecond.value) { "Values are not equal." }
+ Assertions.assertTrue(assertAction(nodeFirst, nodeSecond)) { "Action assertion is invalid equals" }
+}
+
+public fun > assertBinaryNodeDeepEquals(nodeFirst: N?, nodeSecond: N?): Unit {
+ assertBinaryNodeDeepEquals(nodeFirst, nodeSecond) { _, _: N? -> true }
+}
+
+public fun > assertBinaryNodeDeepEquals(
+ nodeFirst: N?,
+ nodeSecond: N?,
+ assertAction: (N, N) -> (Boolean)
+): Unit {
+ if (nodeFirst == null) return Assertions.assertNull(nodeSecond) { "Second node is not null." }
+ if (nodeSecond == null) return Assertions.assertNull(nodeFirst) { "First node is not null." }
+
+ assertBinaryNodeDataEquals(nodeFirst, nodeSecond, assertAction)
+ assertBinaryNodeDeepEquals(nodeFirst.leftChild, nodeSecond.leftChild, assertAction)
+ assertBinaryNodeDeepEquals(nodeFirst.rightChild, nodeSecond.rightChild, assertAction)
+}
diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt
new file mode 100644
index 0000000..5296d7c
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/binary_trees/AVLTreeTest.kt
@@ -0,0 +1,408 @@
+package tree_tripper.binary_trees
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import tree_tripper.binary_trees.assistants.AVLTreeTestAssistant
+import tree_tripper.nodes.binary_nodes.AVLTreeNode
+
+
+class AVLTreeTest {
+ private lateinit var tree: AVLTreeTestAssistant
+
+ @BeforeEach
+ public fun setup() {
+ tree = AVLTreeTestAssistant()
+ }
+
+ @Test
+ @DisplayName("tree initialization")
+ public fun testTreeInitialization() {
+ tree.assertRoot(null) { "Root of AVLTree is not null by standard initialize." }
+ Assertions.assertEquals(0, tree.size)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeCreationCases")
+ @DisplayName("node creation")
+ public fun testNodeCreation(key: Int, value: Int) {
+ tree.assertNodeCreation(key, value)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testBalanceTreeCases")
+ @DisplayName("check balance tree")
+ public fun testBalanceTree(expected: AVLTreeNode, node: AVLTreeNode) {
+ tree.assertBalanceTree(expected, node)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("checkBalanceFactor")
+ @DisplayName("balance factor")
+ public fun checkBalanceFactor(expected: Int, node: AVLTreeNode?) {
+ tree.assertBalanceFactor(expected, node)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testBalanceCases")
+ @DisplayName("balance case")
+ public fun testBalanceCase(expected: AVLTreeNode, node: AVLTreeNode) {
+ tree.assertBalance(expected, node)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeRotateLeftCases")
+ @DisplayName("node rotate left case")
+ public fun testNodeRotateLeftCases(expected: AVLTreeNode, node: AVLTreeNode) {
+ tree.assertNodeLeftRotation(expected, node)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeRotateRightCases")
+ @DisplayName("node rotate right case")
+ public fun testNodeRotateRightCase(expected: AVLTreeNode, node: AVLTreeNode) {
+ tree.assertNodeRightRotation(expected, node)
+ }
+
+ companion object {
+
+ @JvmStatic
+ public fun testNodeCreationCases(): List = listOf(
+ Arguments.of(0, 0),
+ Arguments.of(1, 1),
+ Arguments.of(-1, -1)
+ )
+
+ @JvmStatic
+ public fun testBalanceTreeCases(): List = listOf(
+
+ //Does not require balance
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 1, 1, 1,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ ),
+
+ //Simple left rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 0, 0, 1,
+ null,
+ AVLTreeNode(
+ 1, 1, 2,
+ null,
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ )
+ ),
+
+ //Simple right rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 2, 2, 1,
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ null
+ ),
+ null
+ )
+ )
+
+ )
+
+ @JvmStatic
+ public fun checkBalanceFactor(): List = listOf(
+
+ Arguments.of(0, null),
+
+ Arguments.of(0,
+ AVLTreeNode(0, 0, 1, null, null)
+ ),
+
+ Arguments.of(1,
+ AVLTreeNode(
+ 0, 0, 2,
+ null,
+ AVLTreeNode(0, 0, 1, null, null)
+ )
+ ),
+
+ Arguments.of(-1,
+ AVLTreeNode(
+ 0, 0, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ null
+ )
+ ),
+
+ Arguments.of(0,
+ AVLTreeNode(
+ 0, 0, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(0, 0, 1, null, null)
+ )
+ )
+
+ )
+
+ @JvmStatic
+ public fun testBalanceCases(): List = listOf(
+
+ //Does not require balance
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ ),
+
+ //Simple left rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 0, 0, 3,
+ null,
+ AVLTreeNode(
+ 1, 1, 2,
+ null,
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ )
+ ),
+
+ //Simple right rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 2, 2, 3,
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ null
+ ),
+ null
+ )
+ ),
+
+ //Simple left right rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 2, 2, 3,
+ AVLTreeNode(
+ 0, 0, 2,
+ null,
+ AVLTreeNode(1, 1, 1, null, null)
+ ),
+ null
+ )
+ ),
+
+ //Simple right left rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 0, 0, 3,
+ null,
+ AVLTreeNode(
+ 2, 2, 2,
+ AVLTreeNode(1, 1, 1, null, null),
+ null
+ )
+ )
+ )
+
+ )
+
+ @JvmStatic
+ public fun testNodeRotateLeftCases(): List = listOf(
+
+ //Null check
+ Arguments.of(
+ //Expected
+ AVLTreeNode(0, 0, 1, null, null),
+ //Testing
+ AVLTreeNode(0, 0, 1, null, null)
+ ),
+
+ //Simple left rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 0, 0, 3,
+ null,
+ AVLTreeNode(
+ 1, 1, 2,
+ null,
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ )
+ ),
+
+ //Left rotation with children
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 3, 3, 3,
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ AVLTreeNode(
+ 4, 4, 2,
+ null,
+ AVLTreeNode(5, 5, 1, null, null)
+ )
+ ),
+ //Testing
+ AVLTreeNode(
+ 1, 1, 4,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(
+ 3, 3, 3,
+ AVLTreeNode(2, 2, 1, null, null),
+ AVLTreeNode(
+ 4, 4, 2,
+ null,
+ AVLTreeNode(5, 5, 1, null, null)
+ )
+ )
+ )
+ )
+
+ )
+
+ @JvmStatic
+ public fun testNodeRotateRightCases(): List = listOf(
+
+ //Null check
+ Arguments.of(
+ //Expected
+ AVLTreeNode(0, 0, 1, null, null),
+ //Testing
+ AVLTreeNode(0, 0, 1, null, null)
+ ),
+
+ //Simple right rotation
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ ),
+ //Testing
+ AVLTreeNode(
+ 2, 2, 3,
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ null
+ ),
+ null
+ )
+ ),
+
+ //Right rotation with children
+ Arguments.of(
+ //Expected
+ AVLTreeNode(
+ 2, 2, 3,
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ null
+ ),
+ AVLTreeNode(
+ 4, 4, 2,
+ AVLTreeNode(3, 3, 1, null, null),
+ AVLTreeNode(5, 5, 1, null, null)
+ )
+ ),
+ //Testing
+ AVLTreeNode(
+ 4, 4, 4,
+ AVLTreeNode(
+ 2, 2, 3,
+ AVLTreeNode(
+ 1, 1, 2,
+ AVLTreeNode(0, 0, 1, null, null),
+ null
+ ),
+ AVLTreeNode(3, 3, 1, null, null)
+ ),
+ AVLTreeNode(5, 5, 1, null, null)
+ )
+ )
+
+ )
+
+ }
+}
diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt
new file mode 100644
index 0000000..226b109
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/binary_trees/BSTreeTest.kt
@@ -0,0 +1,388 @@
+package tree_tripper.binary_trees
+
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.assertTimeout
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import tree_tripper.binary_trees.assistants.BSTreeTestAssistant
+import tree_tripper.iterators.IterationOrders
+import java.time.Duration
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+public class BSTreeTest {
+ private lateinit var tree: BSTreeTestAssistant
+
+ @BeforeEach
+ public fun setup() {
+ tree = BSTreeTestAssistant()
+ }
+
+ @Test
+ @DisplayName("tree initialization")
+ public fun testTreeInitialization() {
+ tree.assertNullRoot()
+ Assertions.assertEquals(tree.size, 0, "Incorrect a tree initialization.")
+ }
+
+ @Test
+ @DisplayName("create node")
+ public fun testCreateNode() {
+ tree.assertWasCreatedNode(1, -1)
+ }
+
+ @Test
+ @DisplayName("update root")
+ public fun testUpdateRoot() {
+ tree.assertWasUpdatedRoot(1, -1)
+ }
+
+ @Test
+ @DisplayName("balance tree")
+ public fun testBalanceTree() {
+ tree.assertWasBalancedTree(1, -1)
+ }
+
+ @Test
+ @DisplayName("insert root")
+ public fun testInsertRoot() {
+ tree.insert(1, -1)
+ Assertions.assertEquals(tree.size, 1, "Incorrect resizing tree size.")
+ Assertions.assertEquals(tree.getRoot(), Pair(1, -1), "Incorrect insert root")
+
+ tree.insert(1, 0)
+ Assertions.assertEquals(tree.size, 1, "Incorrect resizing tree size.")
+ Assertions.assertEquals(tree.getRoot(), Pair(1, 0), "Incorrect change root")
+ }
+
+ @Test()
+ @DisplayName("insert children root")
+ public fun testInsertChildrenRoot() {
+ tree.insert(2, -2)
+ tree.insert(1, -1)
+ tree.insert(3, -3)
+ tree.assertIsBSTree()
+ Assertions.assertEquals(tree.size, 3, "Incorrect resizing tree size.")
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("getSizeAndTimeArguments")
+ @DisplayName("insert with size and time")
+ public fun testInsertWithSizeAndTime(size: Int, seconds: Long) {
+ assertTimeout(Duration.ofSeconds(seconds)) {
+ repeat(size) {
+ val keyRandom = Random.nextInt(-1000, 1000)
+ tree.insert(keyRandom, keyRandom)
+ }
+ }
+
+ tree.assertIsBSTree()
+ }
+
+ @Test
+ @DisplayName("if absent insert root")
+ public fun testInsertIfAbsentRoot() {
+ Assertions.assertEquals(tree.insertIfAbsent(1, -1), true)
+ Assertions.assertEquals(tree.size, 1, "Incorrect resizing tree size.")
+ Assertions.assertEquals(tree.getRoot(), Pair(1, -1), "Incorrect insert root")
+
+ Assertions.assertEquals(tree.insertIfAbsent(1, 1), false)
+ Assertions.assertEquals(tree.size, 1, "Incorrect resizing tree size.")
+ Assertions.assertEquals(tree.getRoot(), Pair(1, -1), "Incorrect change root")
+ }
+
+ @Test
+ @DisplayName("search root")
+ public fun testSearchNode() {
+ Assertions.assertEquals(tree.search(0), null, "Incorrect search in a empty tree.")
+
+ tree.insert(1, -1)
+ Assertions.assertEquals(tree.search(1), -1, "Incorrect search an existent root.")
+ }
+
+ @Test
+ @DisplayName("search children root")
+ public fun testSearchChildrenNode() {
+ tree.insert(2, -2)
+ tree.insert(1, -1)
+ tree.insert(3, -3)
+ Assertions.assertEquals(tree.search(1), -1, "Incorrect search an existent child root.")
+ Assertions.assertEquals(tree.search(3), -3, "Incorrect search an existent child root.")
+ Assertions.assertEquals(tree.search(0), null, "Incorrect search a non-existent child root.")
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("getSizeAndTimeArguments")
+ @DisplayName("search with size and time")
+ public fun testSearchWithSizeAndTime(size: Int, seconds: Long) {
+ val arrayKeys = IntArray(size)
+ var index = 0
+ repeat(size) {
+ val keyRandom = Random.nextInt(-1000, 1000)
+ arrayKeys[index++] = keyRandom
+ tree.insert(keyRandom, keyRandom * (-1))
+ }
+
+ assertTimeout(Duration.ofSeconds(seconds)) {
+ repeat(10) {
+ val keyRandom = arrayKeys[Random.nextInt(0, size - 1)]
+ Assertions.assertEquals(tree.search(keyRandom), (-1) * keyRandom,
+ "Incorrect search an existent node.")
+
+ if ((keyRandom - 10) !in arrayKeys)
+ Assertions.assertEquals(tree.search(keyRandom - 10), null,
+ "Incorrect search a non-existent node.")
+ }
+ }
+
+ tree.assertIsBSTree()
+ }
+
+ @Test
+ @DisplayName("search of default root")
+ public fun testSearchOrDefault() {
+ Assertions.assertEquals(tree.searchOrDefault(1, 0), 0,
+ "Incorrect return of search a non-existent child root.")
+
+ tree.insert(1, -1)
+ Assertions.assertEquals(tree.searchOrDefault(1, 0), -1,
+ "Incorrect return of search an existent child root.")
+ }
+
+ @Test
+ @DisplayName("contains")
+ public fun testContains() {
+ Assertions.assertEquals(tree.contains(1), false, "Incorrect return of search a non-existent node.")
+
+ tree.insert(1, -1)
+ Assertions.assertEquals(tree.contains(1), true, "Incorrect return of search an existent node.")
+ }
+
+ @Test
+ @DisplayName("set with brackets")
+ public fun testSet() {
+ tree[1] = -1
+ Assertions.assertEquals(tree.getRoot(), Pair(1, -1), "Incorrect set.")
+
+ tree[1] = 0
+ Assertions.assertEquals(tree.getRoot(), Pair(1, 0), "Incorrect change of the value.")
+ }
+
+ @Test
+ @DisplayName("get with brackets")
+ public fun testGet() {
+ Assertions.assertEquals(tree[1], null, "Incorrect get a non-existent node.")
+
+ tree[1] = -1
+ Assertions.assertEquals(tree[1], -1, "Incorrect get an existent node.")
+ }
+
+ @Test
+ @DisplayName("get maximum in subtree without children")
+ public fun testGetMaxInSubtree() {
+ Assertions.assertEquals(tree.getMaxInSubtree(0), null,
+ "Incorrect search a maximum key in a empty tree.")
+
+ tree[1] = -1
+ Assertions.assertEquals(tree.getMaxInSubtree(1), Pair(1, -1),
+ "Incorrect search a maximum key in subtree without children.")
+ Assertions.assertEquals(tree.getRoot(), Pair(1, -1), "A tree is damaged.")
+ }
+
+ @Test
+ @DisplayName("get maximum in subtree with children")
+ public fun testGetMaxInSubtreeWithChildren() {
+ tree[5] = -5
+ tree[1] = -1
+ tree[3] = -3
+ tree[2] = -2
+ Assertions.assertEquals(tree.getMaxInSubtree(1), Pair(3, -3),
+ "Incorrect search a maximum key in subtree with children.")
+ tree.assertIsBSTree()
+ }
+
+ @Test
+ @DisplayName("get maximum")
+ public fun testGetMax() {
+ Assertions.assertEquals(tree.getMax(), null, "Incorrect search a maximum key in a empty tree.")
+
+ tree[2] = -2
+ tree[3] = -3
+ tree[1] = -1
+ tree[4] = -4
+ Assertions.assertEquals(tree.getMax(), Pair(4, -4), "Incorrect search a maximum key in a tree.")
+ }
+
+ @Test
+ @DisplayName("get minimum in subtree without children")
+ public fun testGetMinInSubtree() {
+ Assertions.assertEquals(tree.getMinInSubtree(0), null,
+ "Incorrect search a minimum key in a empty tree.")
+
+ tree[1] = -1
+ Assertions.assertEquals(tree.getMinInSubtree(1), Pair(1, -1),
+ "Incorrect search a minimum key in subtree without children.")
+ Assertions.assertEquals(tree.getRoot(), Pair(1, -1), "A tree is damaged.")
+ }
+
+ @Test
+ @DisplayName("get minimum in subtree with children")
+ public fun testGetMinInSubtreeWithChildren() {
+ tree[5] = -5
+ tree[1] = -1
+ tree[3] = -3
+ tree[2] = -2
+ Assertions.assertEquals(tree.getMinInSubtree(3), Pair(2, -2),
+ "Incorrect search a minimum key in a subtree.")
+ tree.assertIsBSTree()
+ }
+
+ @Test
+ @DisplayName("get minimum")
+ public fun testGetMin() {
+ Assertions.assertEquals(tree.getMin(), null, "Incorrect search a minimum key in a empty tree.")
+
+ tree[2] = -2
+ tree[3] = -3
+ tree[1] = -1
+ tree[4] = -4
+ Assertions.assertEquals(tree.getMin(), Pair(1, -1), "Incorrect search a minimum key in a tree.")
+ }
+
+ @Test
+ @DisplayName("remove root without children")
+ public fun testRemove() {
+ tree[1] = -1
+ Assertions.assertEquals(tree.remove(1), -1, "Incorrect remove root.")
+ Assertions.assertEquals(0, tree.size)
+ tree.assertNullRoot()
+
+ Assertions.assertEquals(tree.remove(1), null, "Incorrect remove a non-existent root.")
+ Assertions.assertEquals(0, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove root with children")
+ public fun testRemoveWithChildren() {
+ tree[2] = -2
+ tree[1] = -1
+ tree[3] = -3
+ Assertions.assertEquals(tree.remove(2), -2, "Incorrect remove a root.")
+ Assertions.assertEquals(2, tree.size)
+ tree.assertIsBSTree()
+
+ Assertions.assertEquals(tree.search(1), -1, "Incorrect remove a root and lose the left child.")
+ Assertions.assertEquals(tree.search(3), -3, "Incorrect remove a root and lose the right child.")
+
+ Assertions.assertEquals(tree.remove(1), -1, "Incorrect remove a root.")
+ tree.assertIsBSTree()
+ Assertions.assertEquals(1, tree.size)
+
+ Assertions.assertEquals(tree.search(3), -3, "Incorrect remove a root and lose the right child.")
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("getSizeAndTimeArguments")
+ @DisplayName("remove with size and time")
+ public fun testRemoveWithSizeAndTime(size: Int, seconds: Long) {
+ val setKeys: MutableSet = mutableSetOf()
+ repeat(size) {
+ val keyRandom = Random.nextInt(-1000, 1000)
+ setKeys.add(keyRandom)
+ tree[keyRandom] = (-1) * keyRandom
+ }
+
+ assertTimeout(Duration.ofSeconds(seconds)) {
+ repeat(10) {
+ val keyRandom = setKeys.elementAt(Random.nextInt(0, setKeys.size - 1))
+ Assertions.assertEquals(tree.remove(keyRandom), (-1) * keyRandom,
+ "Incorrect return of remove an existent node.")
+ setKeys.remove(keyRandom)
+
+ if ((keyRandom - 10) !in setKeys)
+ Assertions.assertEquals(tree.remove(keyRandom - 10), null,
+ "Incorrect return of remove a non-existent node.")
+ }
+ }
+
+ Assertions.assertEquals(tree.size, setKeys.size)
+ tree.assertIsBSTree()
+ }
+
+ @Test
+ @DisplayName("remove or default")
+ public fun testRemoveOrDefault() {
+ Assertions.assertEquals(tree.removeOrDefault(1, 0), 0,
+ "Incorrect return of remove a non-existent node.")
+ Assertions.assertEquals(0, tree.size)
+
+ tree.insert(1, -1)
+ Assertions.assertEquals(tree.removeOrDefault(1, 0), -1,
+ "Incorrect return of remove an existent node.")
+ Assertions.assertEquals(0, tree.size)
+ }
+
+ @Test
+ @DisplayName("iterator")
+ public fun testIterator() {
+ Assertions.assertFalse(tree.iterator().hasNext(), "Incorrect check next.")
+ }
+
+ @Test
+ @DisplayName("for each")
+ public fun testForEach() {
+ tree[2] = -2
+ tree[1] = -1
+ tree[3] = -3
+ tree[4] = -4
+ val arrayPair = arrayOf(Pair(2, -2), Pair(1, -1), Pair(3, -3), Pair(4, -4))
+ var index = 0
+ tree.forEach(IterationOrders.WIDTH_ORDER) {
+ Assertions.assertEquals(arrayPair[index++], it, "Incorrect iteration.")
+ }
+ }
+
+ @Test
+ @DisplayName("tree to string")
+ public fun testToString() {
+ var builder = StringBuilder("BSTreeTestAssistant(")
+ tree.forEach { builder.append("${it.first}: ${it.second}, ") }
+ builder.append(')')
+ Assertions.assertEquals(tree.toString(), builder.toString(), "Incorrect construction string.")
+
+ builder = StringBuilder("BSTreeTestAssistant(")
+ tree[2] = -2
+ tree[1] = -1
+ tree[3] = -3
+ tree[4] = -4
+ tree.forEach { builder.append("${it.first}: ${it.second}, ") }
+ repeat(2) { builder.deleteCharAt(builder.length - 1) }
+ builder.append(')')
+ Assertions.assertEquals(tree.toString(), builder.toString(), "Incorrect construction string.")
+ }
+
+ @Test
+ @DisplayName("tree to string with tree view")
+ public fun testToStringWithTreeView() {
+ tree[2] = -2
+ tree[1] = -1
+ tree[3] = -3
+ tree[4] = -4
+ val string = "BSTreeTestAssistant(\n\t\t(4: -4)\n\t(3: -3)\n(2: -2)\n\t(1: -1)\n)"
+ Assertions.assertEquals(tree.toStringWithTreeView(), string, "Incorrect construction string.")
+ }
+
+ public companion object {
+ @JvmStatic
+ fun getSizeAndTimeArguments() = listOf(
+ Arguments.of(100, 1L),
+ Arguments.of(10000, 1L)
+ )
+ }
+
+}
diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/RBTreeTest.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/RBTreeTest.kt
new file mode 100644
index 0000000..f553378
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/binary_trees/RBTreeTest.kt
@@ -0,0 +1,483 @@
+package tree_tripper.binary_trees
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.DisplayName
+import tree_tripper.binary_trees.assistants.RBTreeTestAssistant
+import tree_tripper.nodes.binary_nodes.RBTreeNode
+import kotlin.random.Random
+import kotlin.random.nextInt
+
+
+class RBTreeTest {
+ private lateinit var tree: RBTreeTestAssistant
+ private val randomizer = Random(42)
+
+ @BeforeEach
+ public fun setup() {
+ tree = RBTreeTestAssistant()
+ }
+
+ @Test
+ @DisplayName("is root after initializing equals null")
+ public fun testInitializing() {
+ tree.assertRoot(null) { "Root is not null after init" }
+ tree.assertIsRBTree()
+ Assertions.assertEquals(0, tree.size)
+ }
+
+ @Test
+ @DisplayName("insert sorted elements")
+ public fun testInsertSortedElements() {
+ for (i in 0..20) {
+ tree.insert(i, i)
+ tree.assertIsRBTree()
+ Assertions.assertEquals(i + 1, tree.size)
+ }
+
+ }
+
+ @Test
+ @DisplayName("insert reversed sorted elements")
+ public fun testInsertReverseSortedElements() {
+ for (i in 20 downTo 0) {
+ tree.insert(i, i)
+ tree.assertIsRBTree()
+ Assertions.assertEquals((20 - i) + 1, tree.size)
+ }
+
+ }
+
+ @Test
+ @DisplayName("insert a lot of unsorted elements")
+ public fun testInsertUnsortedElements() {
+ val elements: MutableSet = mutableSetOf()
+ for (i in 0..256) {
+ val value = randomizer.nextInt()
+ tree.insert(value, value)
+ elements.add(value)
+ tree.assertIsRBTree()
+ Assertions.assertEquals(elements.size, tree.size)
+ }
+ }
+
+ @Test
+ @DisplayName("remove not contains element")
+ public fun testRemoveNotContainsElement() {
+ for (i in 0..20) tree.insert(i, i)
+ Assertions.assertEquals(null, tree.remove(25))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(21, tree.size)
+
+ Assertions.assertEquals(null, tree.remove(-100))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(21, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove root with children")
+ public fun testRemoveRootWithChildren() {
+ for (i in 0..20) tree.insert(i, i)
+ val root: Pair = tree.getRoot()
+ Assertions.assertEquals(root.second, tree.remove(root.first))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(20, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove black node with children")
+ public fun testRemoveBlackNodeWithChildren() {
+ for (i in 0..20) tree.insert(i, i)
+ Assertions.assertEquals(15, tree.remove(15))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(20, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove red node with children")
+ public fun testRemoveRedNodeWithChildren() {
+ for (i in 0..20) tree.insert(i, i)
+ Assertions.assertEquals(1, tree.remove(1))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(20, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove random node")
+ public fun testRemoveRandomNodeChildren() {
+ for (i in 0..20) tree.insert(i, i)
+ val key = randomizer.nextInt(0..20)
+ try {
+ Assertions.assertEquals(key, tree.remove(key))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(20, tree.size)
+ } catch (e: AssertionError) {
+ throw AssertionError(
+ "Try remove node with key $key from tree: ${tree.toStringWithTreeView()}",
+ e
+ )
+ }
+ }
+
+ @Test
+ @DisplayName("remove root without children")
+ public fun testRemoveRootWithoutChildren() {
+ tree.insert(0, 0)
+ Assertions.assertEquals(0, tree.remove(0))
+ Assertions.assertEquals(0, tree.size)
+
+ }
+
+ @Test
+ @DisplayName("remove black node without children")
+ public fun testRemoveBlackNodeWithoutChildren() {
+ for (i in 0..20) tree.insert(i, i)
+ Assertions.assertEquals(6, tree.remove(6))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(20, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove red node without children")
+ public fun testRemoveRedNodeWithoutChildren() {
+ for (i in 0..21) tree.insert(i, i)
+ Assertions.assertEquals(20, tree.remove(20))
+ tree.assertIsRBTree()
+ Assertions.assertEquals(21, tree.size)
+ }
+
+ @Test
+ @DisplayName("remove root with one left child")
+ public fun testRemoveRootWithOneLeftChild() {
+ tree.insert(0, 0)
+ tree.insert(-1, -1)
+ Assertions.assertEquals(0, tree.remove(0))
+ Assertions.assertEquals(1, tree.size)
+ Assertions.assertEquals(Pair(-1, -1), tree.getRoot())
+ }
+
+ @Test
+ @DisplayName("remove min node at empty tree")
+ public fun testRemoveMinNodeAtEmptyTree() {
+ tree.assertRemoveMinNode(
+ treeView = null,
+ expected = null
+ )
+ }
+
+ @Test
+ @DisplayName("remove min node without children")
+ public fun testRemoveMinNodeWithoutChildren() {
+ tree.assertRemoveMinNode(
+ treeView = RBTreeNode(1, 1, isRed = false),
+ expected = null
+ )
+ tree.assertRemoveMinNode(
+ treeView = RBTreeNode(1, 1, isRed = true),
+ expected = null
+ )
+ }
+
+ @Test
+ @DisplayName("remove min node with red child")
+ public fun testRemoveMinNodeWithRedChild() {
+ tree.assertRemoveMinNode(
+ treeView = RBTreeNode(
+ 1, 1, isRed = false,
+ leftChild = RBTreeNode(0, 0, isRed = true),
+ rightChild = null
+ ),
+ expected = RBTreeNode(1, 1, isRed = false)
+ )
+ }
+
+ @Test
+ @DisplayName("move right node without children")
+ public fun testModeRightNodeWithoutChildren() {
+ tree.assertMoveRightNode(
+ treeView = RBTreeNode(0, 0, isRed = false),
+ expected = RBTreeNode(0, 0, isRed = false),
+ )
+ }
+
+ @Test
+ @DisplayName("move right node with red child")
+ public fun testModeRightNodeWithRedChild() {
+ tree.assertMoveRightNode(
+ treeView = RBTreeNode(
+ 0, 0, isRed = false,
+ leftChild = RBTreeNode(-1, -1, isRed = true),
+ rightChild = null
+ ),
+ expected = RBTreeNode(
+ 0, 0, isRed = false,
+ leftChild = RBTreeNode(-1, -1, isRed = true),
+ rightChild = null
+ ),
+ )
+ }
+
+ @Test
+ @DisplayName("move right node with children")
+ public fun testModeRightNodeWithChildren() {
+ tree.assertMoveRightNode(
+ treeView = RBTreeNode(
+ 0, 0, isRed = false,
+ leftChild = RBTreeNode(-1, -1, isRed = false),
+ rightChild = RBTreeNode(1, 1, isRed = false)
+ ),
+ expected = RBTreeNode(
+ 0, 0, isRed = true,
+ leftChild = RBTreeNode(-1, -1, isRed = true),
+ rightChild = RBTreeNode(1, 1, isRed = true)
+ ),
+ )
+ }
+
+ @Test
+ @DisplayName("move right node with children and left child of left child is red")
+ public fun testModeRightNodeWithChildrenAndLeftChildOfLeftChildIsRed() {
+ tree.assertMoveRightNode(
+ treeView = RBTreeNode(
+ 0, 0, isRed = false,
+ leftChild = RBTreeNode(
+ -1, -1, isRed = false,
+ leftChild = RBTreeNode(-2, -2, isRed = true),
+ rightChild = null
+ ),
+ rightChild = RBTreeNode(1, 1, isRed = false)
+ ),
+ expected = RBTreeNode(
+ -1, -1, isRed = false,
+ leftChild = RBTreeNode(
+ -2, -2, isRed = false
+ ),
+ rightChild = RBTreeNode(
+ 0, 0, isRed = false,
+ leftChild = null,
+ rightChild = RBTreeNode(1, 1, isRed = true)
+ )
+ ),
+ )
+ }
+
+ @Test
+ @DisplayName("remove min node with two child")
+ public fun testRemoveMinNodeWithTwoChild() {
+ tree.assertRemoveMinNode(
+ treeView = RBTreeNode(
+ 1, 1, isRed = false,
+ leftChild = RBTreeNode(0, 0, isRed = false),
+ rightChild = RBTreeNode(2, 2, isRed = false),
+ ),
+ expected = RBTreeNode(
+ 2, 2, isRed = true,
+ leftChild = RBTreeNode(1, 1, isRed = true),
+ rightChild = null
+ )
+ )
+ }
+
+ @Test
+ @DisplayName("remove min node with big subtree")
+ public fun testRemoveMinNodeWithBigSubtree() {
+ tree.assertRemoveMinNode(
+ treeView = RBTreeNode(
+ 2, 2, isRed = false,
+ leftChild = RBTreeNode(
+ 0, 0, isRed = false,
+ leftChild = RBTreeNode(-1, -1, isRed = false),
+ rightChild = RBTreeNode(1, 1, isRed = false)
+ ),
+ rightChild = RBTreeNode(
+ 4, 4, isRed = false,
+ leftChild = RBTreeNode(3, 3, isRed = false),
+ rightChild = RBTreeNode(5, 5, isRed = false)
+ ),
+ ),
+ expected = RBTreeNode(
+ 4, 4, isRed = true,
+ leftChild = RBTreeNode(
+ 2, 2, isRed = true,
+ leftChild = RBTreeNode(
+ 1, 1, isRed = false,
+ leftChild = RBTreeNode(0, 0, isRed = true),
+ rightChild = null
+ ),
+ rightChild = RBTreeNode(3, 3, isRed = false)
+ ),
+ rightChild = RBTreeNode(5, 5, isRed = false)
+ )
+ )
+ }
+
+ @Test
+ @DisplayName("left rotate node without right")
+ public fun testLeftRotateNodeWithoutRightChild() {
+ val node = RBTreeNode(
+ 0, 0, false,
+ RBTreeNode(-1, -1, true),
+ null,
+ )
+ tree.assertNodeLeftRotation(
+ node, node
+ )
+ }
+
+ @Test
+ @DisplayName("left rotate subtree")
+ public fun testLeftRotateOf() {
+ tree.assertNodeLeftRotation(
+ RBTreeNode(
+ 2, 2, false,
+ RBTreeNode(
+ 0, 0, true,
+ RBTreeNode(-1, -1, false),
+ RBTreeNode(1, 1, false)
+ ),
+ RBTreeNode(3, 3, false)
+ ),
+ RBTreeNode(
+ 0, 0, false,
+ RBTreeNode(-1, -1, false),
+ RBTreeNode(
+ 2, 2, true,
+ RBTreeNode(1, 1, false),
+ RBTreeNode(3, 3, false),
+ )
+ ),
+ )
+ }
+
+ @Test
+ @DisplayName("right rotate node without left")
+ public fun testRightRotateNodeWithoutLeftChild() {
+ val node = RBTreeNode(
+ 0, 0, false,
+ null,
+ RBTreeNode(1, 1, true)
+ )
+ tree.assertNodeRightRotation(
+ node, node
+ )
+ }
+
+ @Test
+ @DisplayName("right rotate of subtree")
+ public fun testRightRotate() {
+ tree.assertNodeRightRotation(
+ RBTreeNode(
+ 0, 0, false,
+ RBTreeNode(-1, -1, false),
+ RBTreeNode(
+ 2, 2, true,
+ RBTreeNode(1, 1, false),
+ RBTreeNode(3, 3, false),
+ )
+ ),
+ RBTreeNode(
+ 2, 2, false,
+ RBTreeNode(
+ 0, 0, true,
+ RBTreeNode(-1, -1, false),
+ RBTreeNode(1, 1, false)
+ ),
+ RBTreeNode(3, 3, false)
+ ),
+ )
+ }
+
+ @Test
+ @DisplayName("flip colors of node and repeat it")
+ public fun testFlipColors() {
+ val node = RBTreeNode(0, 0, false, RBTreeNode(-1, -1), RBTreeNode(1, 1))
+ tree.assertNodeColorFlip(
+ RBTreeNode(
+ 0, 0, true,
+ RBTreeNode(-1, -1, false),
+ RBTreeNode(1, 1, false)
+ ), node
+ )
+ tree.assertNodeColorFlip(
+ RBTreeNode(0, 0, false, RBTreeNode(-1, -1), RBTreeNode(1, 1)),
+ node
+ )
+ }
+
+ @Test
+ @DisplayName("is left child of red null node")
+ public fun testIsRedLeftChildOfNullNode() {
+ tree.assertNodeLeftChildColor(false, null)
+ }
+
+ @Test
+ @DisplayName("is red left child of node which property isRed equals true")
+ public fun testIsRedLeftChildOfNodeWhichPropertyEqualsRed() {
+ tree.assertNodeLeftChildColor(
+ true,
+ RBTreeNode(
+ 0, 0, false,
+ RBTreeNode(-1, -1, true),
+ null
+ )
+ )
+ }
+
+ @Test
+ @DisplayName("is red left child of node with property isRed equals false")
+ public fun testIsRedLeftChildOfNodeWhichPropertyEqualsBlack() {
+ tree.assertNodeLeftChildColor(
+ false,
+ RBTreeNode(
+ 0, 0, true,
+ RBTreeNode(-1, -1, false),
+ null
+ )
+ )
+ }
+
+ @Test
+ @DisplayName("is red null node")
+ public fun testIsRedNullNode() {
+ tree.assertNodeColor(false, null)
+ }
+
+ @Test
+ @DisplayName("is red node with property isRed equals true")
+ public fun testIsRedNodeWithPropertyEqualsRed() {
+ tree.assertNodeColor(true, RBTreeNode(0, 0, true))
+ }
+
+ @Test
+ @DisplayName("is red node with property isRed equals false")
+ public fun testIsRedNodeWithPropertyEqualsBlack() {
+ tree.assertNodeColor(false, RBTreeNode(0, 0, false))
+ }
+
+
+ @Test
+ @DisplayName("create node")
+ public fun testCreateNode() {
+ tree.assertNodeCreation(0, 0)
+ }
+
+ @Test
+ @DisplayName("update root as null")
+ public fun testUpdateRootAsNull() {
+ tree.assertUpdateRoot(null)
+ }
+
+ @Test
+ @DisplayName("update root as red node")
+ public fun testUpdateRootAsRedNode() {
+ tree.assertUpdateRoot(RBTreeNode(0, 0, true))
+ }
+
+ @Test
+ @DisplayName("update root as black node")
+ public fun testUpdateRootAsBlackNode() {
+ tree.assertUpdateRoot(RBTreeNode(0, 0, false))
+ }
+
+}
\ No newline at end of file
diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/AVLTreeTestAssistant.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/AVLTreeTestAssistant.kt
new file mode 100644
index 0000000..7e2cd8c
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/AVLTreeTestAssistant.kt
@@ -0,0 +1,46 @@
+package tree_tripper.binary_trees.assistants
+
+import assertBinaryNodeDataEquals
+import assertBinaryNodeDeepEquals
+import tree_tripper.binary_trees.AVLTree
+import tree_tripper.nodes.binary_nodes.AVLTreeNode
+
+
+public class AVLTreeTestAssistant, V> : AVLTree() {
+
+ fun assertRoot(node: AVLTreeNode?, lazyMassage: () -> String) {
+ try {
+ assertBinaryNodeDataEquals(root, node) {root, expected -> root.height == expected.height}
+ } catch (e: AssertionError) {
+ throw AssertionError(lazyMassage(), e)
+ }
+ }
+
+ fun assertNodeCreation(key: K, value: V) {
+ assertBinaryNodeDeepEquals(createNode(key, value), AVLTreeNode(key, value)) {node1, node2 -> node1.height == node2.height}
+ }
+
+ fun assertBalanceTree(expected: AVLTreeNode, node: AVLTreeNode) {
+ assertBinaryNodeDeepEquals(expected, balanceTree(node)) {node1, node2 -> node1.height == node2.height}
+ }
+
+ fun assertBalanceFactor(expected: Int, node: AVLTreeNode?) {
+ val factor = balanceFactor(node)
+ assert(factor == expected) {
+ "Invalid height balance of $node, balance factor: $factor, expected: $expected."
+ }
+ }
+
+ fun assertBalance(expected: AVLTreeNode, node: AVLTreeNode) {
+ assertBinaryNodeDeepEquals(expected, balance(node)) {node1, node2 -> node1.height == node2.height}
+ }
+
+ fun assertNodeRightRotation(expected: AVLTreeNode, node: AVLTreeNode) {
+ assertBinaryNodeDeepEquals(expected, rotateRight(node)) {node1, node2 -> node1.height == node2.height}
+ }
+
+ fun assertNodeLeftRotation(expected: AVLTreeNode, node: AVLTreeNode) {
+ assertBinaryNodeDeepEquals(expected, rotateLeft(node)) {node1, node2 -> node1.height == node2.height}
+ }
+
+}
diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/BSTreeTestAssistant.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/BSTreeTestAssistant.kt
new file mode 100644
index 0000000..875aba5
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/BSTreeTestAssistant.kt
@@ -0,0 +1,55 @@
+package tree_tripper.binary_trees.assistants
+
+import assertBinaryNodeDeepEquals
+import org.junit.jupiter.api.Assertions
+import tree_tripper.binary_trees.BSTree
+import tree_tripper.nodes.binary_nodes.BSTreeNode
+import java.util.*
+
+
+public class BSTreeTestAssistant, V>: BSTree() {
+
+ public fun assertNullRoot() {
+ Assertions.assertEquals(root, null, "Incorrect a root initialization.")
+ }
+
+ public fun assertWasCreatedNode(key: K, value: V) {
+ assertBinaryNodeDeepEquals(createNode(key, value), BSTreeNode(key, value))
+ }
+
+ public fun assertWasUpdatedRoot(key: K, value: V) {
+ val node = createNode(key, value)
+ updateRoot(node)
+ Assertions.assertEquals(root, node, "Incorrect a root update.")
+ }
+
+ public fun assertWasBalancedTree(key: K, value: V) {
+ val node = createNode(key, value)
+ Assertions.assertEquals(balanceTree(node), node, "Incorrect a tree balance.")
+ }
+
+ public fun assertIsBSTree() {
+ if (root == null) throw NullPointerException("Root is null")
+ val queue: Queue> = LinkedList(listOfNotNull(root))
+
+ while (queue.isNotEmpty()) {
+ val node = queue.remove()
+ val nodeLeft = node.leftChild
+ if (nodeLeft != null)
+ assert(nodeLeft.key < node.key)
+ { "Incorrect the binary search tree structure: a left child key is no less than the parent key." }
+ val nodeRight = node.rightChild
+ if (nodeRight != null)
+ assert(nodeRight.key > node.key)
+ { "Incorrect the binary search tree structure: a left child key is no more than the parent key." }
+
+ queue.addAll(node.getChildren())
+ }
+ }
+
+ public fun getRoot(): Pair {
+ val root = this.root ?: throw NullPointerException("Root is null")
+ return Pair(root.key, root.value)
+ }
+
+}
diff --git a/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/RBTreeTestAssistant.kt b/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/RBTreeTestAssistant.kt
new file mode 100644
index 0000000..ac601d7
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/binary_trees/assistants/RBTreeTestAssistant.kt
@@ -0,0 +1,107 @@
+package tree_tripper.binary_trees.assistants
+
+import assertBinaryNodeDataEquals
+import assertBinaryNodeDeepEquals
+import org.junit.jupiter.api.Assertions
+import tree_tripper.binary_trees.RBTree
+import tree_tripper.nodes.binary_nodes.RBTreeNode
+import tree_tripper.nodes.notNullNodeAction
+import java.util.Queue
+import java.util.LinkedList
+
+
+public class RBTreeTestAssistant, V>: RBTree() {
+
+ public fun assertIsRBTree() {
+ assert(!isRedColor(root)) {"Root of RBTree is red. Must be black."}
+ val queue: Queue> = LinkedList>(listOfNotNull(root))
+
+ while (queue.isNotEmpty()) {
+ val node: RBTreeNode = queue.remove()
+ val leftCompareResult: Int = notNullNodeAction(
+ node.leftChild, -1
+ ) { leftChild -> leftChild.key.compareTo(node.key) }
+ val rightCompareResult: Int = notNullNodeAction(
+ node.rightChild, 1
+ ) { rightChild -> rightChild.key.compareTo(node.key) }
+
+ assert(leftCompareResult <= -1) {
+ "Left child of $node is not a BST node: keys compare result: $leftCompareResult"
+ }
+ assert(rightCompareResult >= 1) {
+ "Right child of $node is not a BST node: keys compare result: $rightCompareResult"
+ }
+ assert(!isRedColor(node.rightChild)) {"Right child of node at RBTree is red. Its must be black."}
+ if (isRedColor(node)) {
+ assert(!isRedLeftChild(node)) {"Left child of red node at RBTree is red. Its must be black."}
+ }
+ queue.addAll(node.getChildren())
+ }
+ assertBlackHeight(root)
+ }
+
+ private fun assertBlackHeight(node: RBTreeNode?): Int {
+ if (node == null) return 1
+ val left = assertBlackHeight(node.leftChild)
+ val right = assertBlackHeight(node.rightChild)
+ Assertions.assertEquals(left, right)
+ return (if (node.isRed) 0 else 1) + left
+ }
+
+ public fun assertRoot(node: RBTreeNode?, lazyMassage: () -> String) {
+ try {
+ assertBinaryNodeDataEquals(root, node) {rootNode, expectedNode -> rootNode.isRed == expectedNode.isRed}
+ } catch (e: AssertionError) {
+ throw AssertionError(lazyMassage(), e)
+ }
+ }
+
+ public fun assertNodeColor(expected: Boolean, node: RBTreeNode?) {
+ Assertions.assertEquals(expected, isRedColor(node))
+ }
+
+ public fun assertNodeLeftChildColor(expected: Boolean, node: RBTreeNode?) {
+ Assertions.assertEquals(expected, isRedLeftChild(node))
+ }
+
+ public fun assertNodeLeftRotation(expected: RBTreeNode, node: RBTreeNode) {
+ assertBinaryNodeDeepEquals(expected, rotateLeft(node)) {n1, n2 -> n1.isRed == n2.isRed}
+ }
+
+ public fun assertNodeRightRotation(expected: RBTreeNode, node: RBTreeNode) {
+ assertBinaryNodeDeepEquals(expected, rotateRight(node)) {n1, n2 -> n1.isRed == n2.isRed}
+ }
+
+ public fun assertNodeColorFlip(expected: RBTreeNode, node: RBTreeNode) {
+ flipColors(node)
+ assertBinaryNodeDeepEquals(expected, node) {n1, n2 -> n1.isRed == n2.isRed}
+ }
+
+ public fun assertNodeCreation(key: K, value: V) {
+ assertBinaryNodeDeepEquals(createNode(key, value), RBTreeNode(key, value)) { n1, n2 -> n1.isRed == n2.isRed}
+ }
+
+ public fun assertUpdateRoot(node: RBTreeNode?) {
+ updateRoot(node)
+ assertBinaryNodeDataEquals(
+ root,
+ if (node != null) RBTreeNode(node.key, node.value, false) else null
+ )
+ }
+
+ public fun getRoot(): Pair {
+ val root = this.root ?: throw NullPointerException("Tree is empty can't get root pair")
+ return Pair(root.key, root.value)
+ }
+
+ public fun assertRemoveMinNode(treeView: RBTreeNode?, expected: RBTreeNode?) {
+ val result = removeMinNode(treeView)
+ assertBinaryNodeDeepEquals(expected, result) {n1, n2 -> n1.isRed == n2.isRed}
+ }
+
+ public fun assertMoveRightNode(treeView: RBTreeNode, expected: RBTreeNode) {
+ val result = moveRedRight(treeView)
+ assertBinaryNodeDeepEquals(expected, result) {n1, n2 -> n1.isRed == n2.isRed}
+ }
+
+}
\ No newline at end of file
diff --git a/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt b/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt
new file mode 100644
index 0000000..3ac0388
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/iterators/BinarySearchTreeIteratorTest.kt
@@ -0,0 +1,136 @@
+package tree_tripper.iterators
+
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import tree_tripper.nodes.binary_nodes.BSTreeNode
+
+
+class BinarySearchTreeIteratorTest {
+ lateinit var iterator: BinarySearchTreeIterator>
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testIteratorCases")
+ @DisplayName("test iterator at width order")
+ public fun testWidthOrderIterator(expected: List, root: BSTreeNode) {
+ iterator = BinarySearchTreeIterator(root)
+ var index: Int = 0
+
+ while (iterator.hasNext()) {
+ Assertions.assertEquals(iterator.next(), Pair(expected[index], expected[index]))
+ index++
+ }
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testGetALotOfElementsCases")
+ @DisplayName("try get more elements than iterator has")
+ public fun testGetALotOfElements(order: IterationOrders) {
+ iterator = BinarySearchTreeIterator(null, order)
+ Assertions.assertFalse(iterator.hasNext())
+ Assertions.assertThrows(NoSuchElementException::class.java) { iterator.next() }
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testIteratorCases")
+ @DisplayName("test iterator at increase order")
+ public fun testIncreasingOrderIterator(expected: List, root: BSTreeNode) {
+ iterator = BinarySearchTreeIterator(root, IterationOrders.INCREASING_ORDER)
+ val sortedElements = expected.sorted()
+ var index: Int = 0
+
+ while (iterator.hasNext()) {
+ Assertions.assertEquals(iterator.next(), Pair(sortedElements[index], sortedElements[index]))
+ index++
+ }
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testIteratorCases")
+ @DisplayName("test iterator at decrease order")
+ public fun testDecreasingOrderIterator(expected: List, root: BSTreeNode) {
+ iterator = BinarySearchTreeIterator(root, IterationOrders.DECREASING_ORDER)
+ val sortedElements = expected.sorted().reversed()
+ var index: Int = 0
+
+ while (iterator.hasNext()) {
+ Assertions.assertEquals(iterator.next(), Pair(sortedElements[index], sortedElements[index]))
+ index++
+ }
+ }
+
+ companion object {
+
+ @JvmStatic
+ fun testGetALotOfElementsCases(): List = listOf(
+ Arguments.of(IterationOrders.WIDTH_ORDER),
+ Arguments.of(IterationOrders.INCREASING_ORDER),
+ Arguments.of(IterationOrders.DECREASING_ORDER),
+ )
+
+ @JvmStatic
+ fun testIteratorCases(): List = listOf(
+ Arguments.of(listOf(0), BSTreeNode(0, 0)),
+ Arguments.of(
+ listOf(0, -5, 5),
+ BSTreeNode(
+ 0, 0,
+ BSTreeNode(-5, -5),
+ BSTreeNode(5, 5),
+ )
+ ),
+ Arguments.of(
+ listOf(0, -5, 5, -10),
+ BSTreeNode(
+ 0, 0,
+ BSTreeNode(
+ -5, -5,
+ BSTreeNode(-10, -10),
+ null,
+ ),
+ BSTreeNode(5, 5),
+ )
+ ),
+ Arguments.of(
+ listOf(0, -5, 5, -10, 10),
+ BSTreeNode(
+ 0, 0,
+ BSTreeNode(
+ -5, -5,
+ BSTreeNode(-10, -10),
+ null,
+ ),
+ BSTreeNode(
+ 5, 5,
+ null,
+ BSTreeNode(10, 10),
+ )
+ )
+ ),
+ Arguments.of(
+ listOf(0, -5, 5, -10, -3, 3, 10, 4),
+ BSTreeNode(
+ 0, 0,
+ BSTreeNode(
+ -5, -5,
+ BSTreeNode(-10, -10),
+ BSTreeNode(-3, -3),
+ ),
+ BSTreeNode(
+ 5, 5,
+ BSTreeNode(
+ 3, 3,
+ null,
+ BSTreeNode(4, 4)
+ ),
+ BSTreeNode(10, 10),
+ )
+ )
+ )
+ )
+
+ }
+
+}
diff --git a/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt
new file mode 100644
index 0000000..c21815d
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/nodes/UtilsTest.kt
@@ -0,0 +1,43 @@
+package tree_tripper.nodes
+
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import tree_tripper.nodes.binary_nodes.BSTreeNode
+
+
+public class UtilsTest {
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeUpdateCases")
+ @DisplayName("util of node update")
+ public fun testNodeUpdate(expected: Boolean, node: BSTreeNode?) {
+ var isActivateAction: Boolean = false
+ notNullNodeUpdate(node) { isActivateAction = true }
+ Assertions.assertEquals(expected, isActivateAction)
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeActionCases")
+ @DisplayName("util of action on node")
+ public fun testNodeAction(expected: Boolean, node: BSTreeNode?) {
+ Assertions.assertEquals(expected, notNullNodeAction(node, false) { expected })
+ }
+
+ companion object {
+ @JvmStatic
+ fun testNodeUpdateCases(): List = listOf(
+ Arguments.of(false, null),
+ Arguments.of(true, BSTreeNode(0, 0)),
+ )
+
+ @JvmStatic
+ fun testNodeActionCases(): List = listOf(
+ Arguments.of(false, null),
+ Arguments.of(true, BSTreeNode(0, 0)),
+ )
+ }
+
+}
diff --git a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt
new file mode 100644
index 0000000..ca1a2b2
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/AVLTreeNodeTest.kt
@@ -0,0 +1,80 @@
+package tree_tripper.nodes.binary_nodes
+
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+
+
+class AVLTreeNodeTest {
+
+ @Test
+ @DisplayName("node initialization")
+ public fun nodeInitialization() {
+ val node = AVLTreeNode(0, 0)
+ Assertions.assertEquals(1, node.height) {"The height is not 1 by standard initialize."}
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testUpdateHeight")
+ @DisplayName("update height")
+ public fun testUpdateHeight(expected: Int, node: AVLTreeNode) {
+ node.updateHeight()
+ Assertions.assertEquals(expected, node.height) {"The height does not match the expected."}
+ }
+
+ companion object {
+
+ @JvmStatic
+ public fun testUpdateHeight(): List = listOf(
+
+ Arguments.of(1,
+ AVLTreeNode(0, 0, 0, null, null)
+ ),
+
+ Arguments.of(2,
+ AVLTreeNode(
+ 0, 0, 0,
+ AVLTreeNode(1, 1, 1, null, null),
+ null
+ )
+ ),
+
+ Arguments.of(2,
+ AVLTreeNode(
+ 0, 0, 0,
+ null,
+ AVLTreeNode(1, 1, 1, null, null)
+ )
+ ),
+
+ Arguments.of(2,
+ AVLTreeNode(
+ 0, 0, 0,
+ AVLTreeNode(1, 1, 1, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ ),
+
+ Arguments.of(3,
+ AVLTreeNode(
+ 0, 0, 0,
+ AVLTreeNode(1, 1, 2, null, null),
+ AVLTreeNode(2, 2, 1, null, null)
+ )
+ ),
+
+ Arguments.of(3,
+ AVLTreeNode(
+ 0, 0, 0,
+ AVLTreeNode(1, 1, 1, null, null),
+ AVLTreeNode(2, 2, 2, null, null)
+ )
+ )
+
+ )
+
+ }
+}
diff --git a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/BSTreeNodeTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/BSTreeNodeTest.kt
new file mode 100644
index 0000000..612d22e
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/BSTreeNodeTest.kt
@@ -0,0 +1,72 @@
+package tree_tripper.nodes.binary_nodes
+
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+
+public class BSTreeNodeTest {
+
+ @Test
+ @DisplayName("node initialization")
+ public fun testNodeInitialization() {
+ val node = BSTreeNode(1, -1)
+ assertEquals(node.key, 1, "Incorrect a key assignment")
+ assertEquals(node.value, -1, "Incorrect a value assignment")
+ assertEquals(node.leftChild, null, "Incorrect a left child assignment")
+ assertEquals(node.rightChild, null, "Incorrect a right child assignment")
+ }
+
+ @Test
+ @DisplayName("get children")
+ public fun testGetChildren() {
+ val node = BSTreeNode(2, -2)
+ assertEquals(node.getChildren(), listOf())
+ val nodeLeft = BSTreeNode(1, -1)
+ node.leftChild = nodeLeft
+ assertEquals(node.getChildren(), listOf(nodeLeft))
+ val nodeRight = BSTreeNode(3, -3)
+ node.rightChild = nodeRight
+ assertEquals(node.getChildren(), listOf(nodeLeft, nodeRight))
+ }
+
+ @Test
+ @DisplayName("to string")
+ public fun testToString() {
+ val node = BSTreeNode(1, -1)
+ assertEquals(node.toString(), "BSTreeNode(key=1, value=-1)")
+ }
+
+ @Test
+ @DisplayName("to string simple view")
+ public fun testToStringSimpleView() {
+ val node = BSTreeNode(1, -1)
+ assertEquals(node.toStringSimpleView(), "(1: -1)")
+ }
+
+ @Test
+ @DisplayName("node to string with subtree view")
+ public fun testNodeToStringWithSubtreeView() {
+ val builder = StringBuilder()
+ val node = BSTreeNode(1, -1)
+ node.toStringWithSubtreeView(0, builder)
+ assertEquals(builder.toString(), "${node.toStringSimpleView()}\n")
+ }
+
+ @Test
+ @DisplayName("node with children to string with subtree view")
+ public fun testNodeWithChildrenToStringWithSubtreeView() {
+ val builder = StringBuilder()
+ val node = BSTreeNode(2, -2)
+ val nodeLeft = BSTreeNode(1, -1)
+ node.leftChild = nodeLeft
+ val nodeRight = BSTreeNode(3, -3)
+ node.rightChild = nodeRight
+ node.toStringWithSubtreeView(0, builder)
+ assertEquals(builder.toString(),
+ "\t${nodeRight.toStringSimpleView()}\n" +
+ "${node.toStringSimpleView()}\n" +
+ "\t${nodeLeft.toStringSimpleView()}\n")
+ }
+
+}
\ No newline at end of file
diff --git a/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt
new file mode 100644
index 0000000..ac95991
--- /dev/null
+++ b/lib/src/test/kotlin/tree_tripper/nodes/binary_nodes/RBTreeNodeTest.kt
@@ -0,0 +1,81 @@
+package tree_tripper.nodes.binary_nodes
+
+import assertBinaryNodeDeepEquals
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+
+
+public class RBTreeNodeTest {
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeSimpleInitializeCases")
+ @DisplayName("node simple initialization")
+ public fun testNodeSimpleInitialize(key: Int, value: Int?) {
+ val node = RBTreeNode(key, value)
+ Assertions.assertEquals(key, node.key) { "Key of node is not equal." }
+ Assertions.assertEquals(value, node.value) { "Value of node is not equal." }
+ Assertions.assertTrue(node.isRed) { "Color of node is not red." }
+ Assertions.assertNull(node.leftChild) { "Left child of node is not null." }
+ Assertions.assertNull(node.rightChild) { "Right child of node is not null." }
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeColorTypeInitializeCases")
+ @DisplayName("node initialization with color")
+ public fun testNodeColorTypeInitialize(isRed: Boolean) {
+ val node = RBTreeNode(0, 0, isRed)
+ Assertions.assertEquals(isRed, node.isRed) { "Color of node is not equal." }
+ Assertions.assertNull(node.leftChild) { "Left child of node is not null." }
+ Assertions.assertNull(node.rightChild) { "Right child of node is not null." }
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testNodeFullInitializeCases")
+ @DisplayName("node initialization with color and children")
+ public fun testNodeFullInitialize(leftChild: RBTreeNode?, rightChild: RBTreeNode?) {
+ val node = RBTreeNode(0, 0, false, leftChild, rightChild)
+ assertBinaryNodeDeepEquals(leftChild, node.leftChild) { n1, n2 -> n1.isRed == n2.isRed }
+ assertBinaryNodeDeepEquals(rightChild, node.rightChild) { n1, n2 -> n1.isRed == n2.isRed }
+ }
+
+ @ParameterizedTest(name = "{displayName}[{index}] {argumentsWithNames}")
+ @MethodSource("testToStringSimpleViewCases")
+ @DisplayName("to string simple view")
+ public fun testToStringSimpleView(expected: String, node: RBTreeNode) {
+ Assertions.assertEquals(expected, node.toStringSimpleView())
+ }
+
+ companion object {
+ @JvmStatic
+ fun testNodeSimpleInitializeCases(): List = listOf(
+ Arguments.of(-1, -1),
+ Arguments.of(0, 0),
+ Arguments.of(2, 2),
+ Arguments.of(3, null),
+ )
+
+ @JvmStatic
+ fun testNodeColorTypeInitializeCases(): List = listOf(
+ Arguments.of(false),
+ Arguments.of(true),
+ )
+
+ @JvmStatic
+ fun testNodeFullInitializeCases(): List = listOf(
+ Arguments.of(null, null),
+ Arguments.of(RBTreeNode(1, null), null),
+ Arguments.of(null, RBTreeNode(-1, null)),
+ Arguments.of(RBTreeNode(-1, null), RBTreeNode(-1, null)),
+ )
+
+ @JvmStatic
+ fun testToStringSimpleViewCases(): List = listOf(
+ Arguments.of("(-1: null) - RED", RBTreeNode(-1, null)),
+ Arguments.of("(0: 0) - BLACK", RBTreeNode(0, 0, false)),
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/scripts/csv-reports-printer.py b/scripts/csv-reports-printer.py
new file mode 100644
index 0000000..4ee0bbe
--- /dev/null
+++ b/scripts/csv-reports-printer.py
@@ -0,0 +1,198 @@
+import argparse
+import csv
+import sys
+import typing
+from text_colorize import ANSIColors, TextStyle, colorize
+
+
+COLUMNS_TYPES = [
+ '_MISSED',
+ '_COVERED',
+]
+
+CSV_COLUMNS = [
+ 'PACKAGES',
+ 'CLASS',
+ 'BRANCH_MISSED',
+ 'BRANCH_COVERED',
+ 'LINE_MISSED',
+ 'LINE_COVERED',
+ 'METHOD_MISSED',
+ 'METHOD_COVERED',
+]
+DISPLAY_COLUMNS = [
+ 'PACKAGES',
+ 'CLASS',
+ 'BRANCH',
+ 'LINE',
+ 'METHOD',
+]
+DEFAULT_LABEL_SIZE = 8
+
+
+def create_row_info() -> dict:
+ return {
+ key: 0 for key in CSV_COLUMNS
+ }
+
+
+def is_valid_lib(group: str, lib_name: str) -> bool:
+ if len(lib_name) == 0:
+ return True
+ return group == lib_name
+
+
+def parse_args(args: list[str]) -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ prog="CSV Jacoco Test Reports Printer",
+ description="Program read csv file with jacoco report and print it at terminal at stdin",
+ )
+
+ parser.add_argument(
+ "-i", "--input", required=True,
+ help="setup path to CSV file with jacoco report information",
+ metavar=">"
+ )
+ parser.add_argument(
+ "-l", "--lib",
+ help="setup library name to remove from package path",
+ default="",
+ metavar=">"
+ )
+ parser.add_argument(
+ "-p", "--package-print",
+ help="setup flag to 'ON' to print packages of files at report (default 'OFF')",
+ action='store_true',
+ default=False
+ )
+
+ return parser.parse_args(args)
+
+
+def read_csv(namespace: argparse.Namespace) -> typing.Optional[dict]:
+ table = []
+ max_packages_name_length = 20
+ max_classes_name_length = 20
+
+ with open(getattr(namespace, "input"), 'r') as file:
+ reader = csv.reader(file)
+ for row in reader:
+ if len(row) == 0:
+ break
+ if (row[0] == "GROUP") or not is_valid_lib(row[0], getattr(namespace, "lib", "")):
+ continue
+
+ row_info = create_row_info()
+ is_skipped = False
+ for key in row_info.keys():
+ if key not in CSV_COLUMNS:
+ row_info.pop(key)
+
+ index = CSV_COLUMNS.index(key) + 1
+ row_info[key] = row[index]
+
+ if key == "PACKAGES":
+ max_packages_name_length = max(max_packages_name_length, len(row_info[key]))
+ elif key == "CLASS":
+ if '(' in row_info[key] or ')' in row_info[key] or ' ' in row_info[key]:
+ is_skipped = True
+ break
+ elif '.' in row_info[key]:
+ row_info[key] = row_info[key].split('.')[-1]
+
+ max_classes_name_length = max(max_classes_name_length, len(row_info[key]))
+
+ if not is_skipped:
+ table.append(row_info)
+ return {
+ "table": table,
+ "max_packages_name_length": max_packages_name_length,
+ "max_classes_name_length": max_classes_name_length
+ }
+
+
+def create_label(text: str, lbl_size: int, color: ANSIColors = ANSIColors.BLACK):
+ if len(text) >= lbl_size:
+ text = text[:lbl_size]
+
+ if len(text) % 2 != 0:
+ text = ' ' + text
+
+ color_text = colorize(
+ f"{{:^{lbl_size}}}".format(text),
+ color,
+ TextStyle.BOLD
+ )
+ return f'| {color_text} |'
+
+
+def colorize_percent_label(percent: int) -> str:
+ color = ANSIColors.RED
+ if 50 <= percent < 75:
+ color = ANSIColors.YELLOW
+ elif 75 <= percent <= 100:
+ color = ANSIColors.GREEN
+
+ return create_label(f"{percent}%", DEFAULT_LABEL_SIZE, color)
+
+
+def display_columns(max_packages_name_length: int, max_classes_name_length: int) -> int:
+ global DISPLAY_COLUMNS, DEFAULT_LABEL_SIZE
+ for column in DISPLAY_COLUMNS:
+ lbl_size = DEFAULT_LABEL_SIZE
+ if column == "CLASS":
+ lbl_size = max_classes_name_length
+ elif column == "PACKAGES":
+ lbl_size = max_packages_name_length
+
+ lbl = create_label(column, lbl_size, ANSIColors.PURPLE)
+ print(lbl, end="")
+ print()
+
+
+def display_csv_data(namespace: argparse.Namespace, csv_data_dict: dict) -> None:
+ global DISPLAY_COLUMNS, COLUMNS_TYPES
+ if not getattr(namespace, "package_print", False):
+ DISPLAY_COLUMNS.remove("PACKAGES")
+
+ if getattr(namespace, 'lib'):
+ print(f"Jacoco Covered Report Info for module named '{getattr(namespace, 'lib')}':")
+
+ max_packages_name_length = csv_data_dict.get("max_packages_name_length", 20)
+ max_classes_name_length = csv_data_dict.get("max_classes_name_length", 20)
+ table: list[dict] = csv_data_dict.get("table", [])
+ display_columns(max_packages_name_length, max_classes_name_length)
+
+ for row in table:
+ for column in DISPLAY_COLUMNS:
+ lbl = ""
+ if column in ["PACKAGES", "CLASS"]:
+ lbl = create_label(
+ row[column],
+ max_packages_name_length if column == "PACKAGES" else max_classes_name_length,
+ ANSIColors.YELLOW
+ )
+ else:
+ vals = [int(row[column + _type]) for _type in COLUMNS_TYPES]
+ percent = int(round((vals[1] - vals[0]) / vals[1], 2) * 100) if vals[1] != 0 else 100
+ lbl = colorize_percent_label(
+ percent,
+ )
+
+ print(lbl, end="")
+ print()
+
+
+if __name__ == "__main__":
+ ns = parse_args(sys.argv[1:])
+
+ try:
+ csv_data = read_csv(ns)
+ except Exception as e:
+ print(
+ f"Can't read csv file: '{getattr(ns, 'input')}', get exception: '{e}'",
+ file=sys.stderr
+ )
+ sys.exit(-1)
+
+ display_csv_data(ns, csv_data)
diff --git a/scripts/test-result-printer.py b/scripts/test-result-printer.py
new file mode 100644
index 0000000..5f9e987
--- /dev/null
+++ b/scripts/test-result-printer.py
@@ -0,0 +1,185 @@
+import argparse
+import sys, os
+import xml.etree.ElementTree as ET
+from text_colorize import ANSIColors, TextStyle, colorize
+
+
+class TestCase:
+
+ def __init__(self, name: str, is_passed: bool) -> None:
+ self.name = str(name)
+ self.is_passed = bool(is_passed)
+
+ def toString(self, indent: int = 0):
+ return "\t" * indent + f"{self.name} -> {self.result_type()}"
+
+ def result_type(self) -> str:
+ return colorize(
+ 'PASSED' if self.is_passed else 'FAILURE',
+ ANSIColors.GREEN if self.is_passed else ANSIColors.RED,
+ TextStyle.ITALIC
+ )
+
+ def __bool__(self):
+ return self.is_passed
+
+
+class ParametrisedTestCase(TestCase):
+
+ def __init__(self, name: str, cases: list[TestCase]) -> None:
+ super().__init__(name, all(cases))
+ self.cases = cases
+
+ def add_case(self, case: TestCase):
+ self.is_passed = self.is_passed and case.is_passed
+ self.cases.append(case)
+
+ def toString(self, indent: int = 0, also_failed: bool = False):
+ inline_cases = []
+ for case in self.cases:
+ if (also_failed and case.is_passed):
+ continue
+
+ inline_cases.append(case.toString(indent + 1))
+
+ inline_cases = '\n'.join(inline_cases).rstrip()
+ return super().toString(indent) + f"\n{inline_cases}"
+
+
+def parse_args(args: list[str]) -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ prog="Test Result Printer",
+ description="Program read xml files with tests results and print it at terminal at stdin",
+ )
+ parser.add_argument(
+ "-d", "--dir", required=True,
+ help="setup path to directory with xml files with tests information",
+ metavar=">"
+ )
+ parser.add_argument(
+ "-a", "--all",
+ action="store_true",
+ help="setup mode of diplay type to print all test information to ON (default OFF)",
+ )
+ parser.add_argument(
+ "-f", "--all-failures",
+ action="store_true",
+ help="setup mode of diplay type to print also test information which failed to ON (default OFF)",
+ )
+ return parser.parse_args(args)
+
+
+def parse_test_result(test_path: str) -> tuple[ET.Element, dict[str, TestCase]]:
+ tree_root = ET.parse(test_path).getroot()
+ cases = dict()
+ for child in tree_root:
+ if child.tag != "testcase":
+ continue
+
+ name = child.get("name", "uncknown test cases")
+ is_passed = child.find("failure") is None
+ if '[' in name:
+ primary_name = name.split('[')[0]
+ args = '[' + "[".join(name.split('[')[1:])
+
+ case: ParametrisedTestCase = cases.get(primary_name, ParametrisedTestCase(primary_name, []))
+ case.add_case(TestCase(args, is_passed))
+ cases[primary_name] = case
+ else:
+ cases[name] = TestCase(name, is_passed)
+
+ return (tree_root, cases,)
+
+
+def display_all_test_result(tree_root: ET.Element, cases: dict[str, TestCase]):
+ print(
+ "Tests of",
+ tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"),
+ sep=" "
+ )
+ for name in sorted(cases.keys()):
+ print(cases[name].toString(indent=1))
+
+ passed_test_count = int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0))
+ print(
+ colorize(f"Passed: {passed_test_count}", ANSIColors.GREEN, TextStyle.BOLD),
+ colorize(f"Failures: {tree_root.attrib.get('failures', 0)}", ANSIColors.RED, TextStyle.BOLD),
+ f"Time: {tree_root.attrib.get('time', 0.0)}",
+ sep=" ",
+ end=os.linesep * 2
+ )
+
+
+def display_failures_test_result(tree_root: ET.Element, cases: dict[str, TestCase]):
+ failed_tests = []
+ for name in sorted(cases.keys()):
+ if not cases[name].is_passed:
+ failed_tests.append(cases[name])
+
+ if len(failed_tests) == 0:
+ return
+
+ print(
+ "Failed tests of",
+ tree_root.attrib.get("name", "UncnownTestSuite").split('.')[-1].replace("Test", ":"),
+ sep=" "
+ )
+ for case in failed_tests:
+ if isinstance(case, ParametrisedTestCase):
+ print(case.toString(indent=1, also_failed=True))
+ elif isinstance(case, TestCase):
+ print(case.toString(indent=1))
+
+ passed_test_count = int(tree_root.attrib.get("tests", 0)) - int(tree_root.attrib.get("failures", 0))
+ print(
+ colorize(f"Passed: {passed_test_count}", ANSIColors.GREEN, TextStyle.BOLD),
+ colorize(f"Failures: {tree_root.attrib.get('failures', 0)}", ANSIColors.RED, TextStyle.BOLD),
+ f"Time: {tree_root.attrib.get('time', 0.0)}",
+ sep=" ",
+ end=os.linesep * 2
+ )
+
+
+if __name__ == "__main__":
+ ns = parse_args(sys.argv[1:])
+
+ tests_result_dir = getattr(ns, "dir")
+ childs = os.listdir(tests_result_dir)
+ tests_results: list[tuple[ET.Element, dict[str, TestCase]]] = []
+ for child in sorted(childs):
+ child_path = os.path.join(tests_result_dir, child)
+ if not os.path.isfile(child_path):
+ continue
+
+ if not (child.startswith("TEST") and child.endswith(".xml")):
+ continue
+
+ try:
+ tests_results.append(parse_test_result(child_path))
+ except Exception as e:
+ print(f"Can't display ttest information at file '{child}': {e}", file=sys.stderr)
+
+ tests_count = 0
+ tests_failed_count = 0
+ time_of_all_tests = 0
+ for test_result in tests_results:
+ tree_root: ET.Element = test_result[0]
+ tests_count += int(tree_root.attrib.get("tests", 0))
+ tests_failed_count += int(tree_root.attrib.get("failures", 0))
+ time_of_all_tests += float(tree_root.attrib.get('time', 0.0))
+
+
+ print(
+ colorize(f"Count of tests: {tests_count}", ANSIColors.YELLOW, TextStyle.BOLD),
+ colorize(f"Count of passed tests: {tests_count - tests_failed_count}", ANSIColors.GREEN, TextStyle.BOLD),
+ colorize(f"Count of failured tests: {tests_failed_count}", ANSIColors.RED, TextStyle.BOLD),
+ colorize(f"Time: {time_of_all_tests}", ANSIColors.BLUE, TextStyle.BOLD),
+ sep=os.linesep,
+ end=os.linesep * 2
+ )
+
+ for test_result in tests_results:
+ if getattr(ns, "all", False):
+ display_all_test_result(test_result[0], test_result[1])
+ elif getattr(ns, "all_failures", False):
+ display_failures_test_result(test_result[0], test_result[1])
diff --git a/scripts/text_colorize.py b/scripts/text_colorize.py
new file mode 100644
index 0000000..ce38225
--- /dev/null
+++ b/scripts/text_colorize.py
@@ -0,0 +1,20 @@
+import enum
+
+class ANSIColors(enum.IntEnum):
+ PURPLE = 35
+ BLUE = 34
+ GREEN = 32
+ YELLOW = 33
+ RED = 31
+ BLACK = 30
+ WHITE = 37
+
+
+class TextStyle(enum.IntEnum):
+ SIMPLE = 0
+ BOLD = 1
+ ITALIC = 3
+
+
+def colorize(text: str, color: ANSIColors, style: TextStyle = TextStyle.SIMPLE) -> str:
+ return f"\033[{style};{color}m{text}\033[0m"
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..7de2ad7
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,15 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.6/userguide/multi_project_builds.html in the Gradle documentation.
+ * This project uses @Incubating APIs which are subject to change.
+ */
+
+plugins {
+ // Apply the foojay-resolver plugin to allow automatic download of JDKs
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
+}
+
+rootProject.name = "tree-trippers"
+include("lib")