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/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..ae1749f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,29 @@
+---
+name: Bug report
+about: Create a report to help us improve
+labels: bug
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. macOS]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
new file mode 100644
index 0000000..2b337d7
--- /dev/null
+++ b/.github/badges/jacoco.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/mergeable.yml b/.github/mergeable.yml
new file mode 100644
index 0000000..58e342a
--- /dev/null
+++ b/.github/mergeable.yml
@@ -0,0 +1,18 @@
+version: 2
+mergeable:
+ - when: pull_request.*
+ name: "Description check"
+ validate:
+ - do: description
+ no_empty:
+ enabled: true
+ message: Description matter and should not be empty. Provide detail with **what** was changed, **why** it was changed, and **how** it was changed.
+
+ - when: pull_request.opened
+ name: "Greet a contributor"
+ validate: []
+ pass:
+ - do: comment
+ payload:
+ body: >
+ Thanks for creating a pull request! A maintainer will review your changes shortly. Please don't be discouraged if it takes a while.
diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml
new file mode 100644
index 0000000..969b9e3
--- /dev/null
+++ b/.github/workflows/codecoverage.yml
@@ -0,0 +1,63 @@
+name: Measure coverage
+
+on:
+ workflow_dispatch:
+ pull_request:
+
+permissions: write-all
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.head_ref }}
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: zulu
+
+ - name: Create Coverage Report
+ run: |
+ chmod +x gradlew
+ ./gradlew jacocoTestReport
+
+ - if: matrix.os == 'ubuntu-latest'
+ name: Generate Jacoco Badge
+ uses: cicirello/jacoco-badge-generator@v2.11.0
+ with:
+ jacoco-csv-file: lib/build/jacoco/report.csv
+ badges-directory: .github/badges
+ generate-coverage-badge: true
+ coverage-badge-filename: jacoco.svg
+
+ - name: Check for modified files
+ id: git-check
+ run: |
+ modified=$(git status --porcelain)
+ if [ -n "$modified" ]; then
+ echo "modified=true" >> $GITHUB_ENV
+ else
+ echo "modified=false" >> $GITHUB_ENV
+ fi
+ shell: bash
+
+ - name: Commit the badges (if they changed)
+ if: env.git-check == 'true'
+ run: |
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
+ git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
+ git add .github/badges/*
+ git commit -am "ci: autogenerate JaCoCo coverage badge"
+ git push
+ shell: bash
+
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..ed46039
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,21 @@
+name: Build & Run JUnit5 tests
+
+on:
+ workflow_dispatch:
+ pull_request:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: zulu
+
+ - name: Build & Test by Gradle
+ run: ./gradlew build
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8ab266e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,219 @@
+# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,macos,windows,linux,gradle,kotlin
+# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,macos,windows,linux,gradle,kotlin
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+all Patch ###
+# Ignore everything but code style settings and run configurations
+# that are supposed to be shared within teams.
+
+.idea/*
+
+!.idea/codeStyles
+!.idea/runConfigurations
+
+### Kotlin ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+### Gradle ###
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+### Gradle Patch ###
+# Java heap dump
+*.hprof
+
+# End of https://www.toptal.com/developers/gitignore/api/intellij+all,macos,windows,linux,gradle,kotlin
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..787043c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,53 @@
+# 💪 Interested in contributing?
+
+Good boy! Please read the few sections below to understand how to implement new features.
+
+> 😅 Be positive! Even if your changes don't get merged in forest-group, please don't be too sad, you will always be able to run workflows directly from your fork!
+> Meow
+
+## 🤝 Accepted contributions
+
+The following contributions are accepted:
+
+
+ | Section |
+ Changes |
+ Additions |
+ Notes |
+
+
+ | 🧩 Features |
+ ✔️ |
+ ✔️ |
+
+
+ - New features are allowed, but must be optional and backward compatible
+
+ |
+
+
+ | 🧪 Tests |
+ ✔️ |
+ ✔️ |
+
+
+ - Everything that makes forest-group more stable is welcomed!
+
+ |
+
+
+ | 🗃️ Repository |
+ ❌ |
+ ❌ |
+
+
+ - Workflows, license, readmes, etc. usually don't need to be edited
+
+ |
+
+
+
+**Legend**
+* ✔️: Contributions welcomed!
+* ✓: Contributions are welcomed, but must be discussed first
+* ❌: Only maintainers can manage these files
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..5d7d8c4
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) 2024 Gavrilenko Mike, Shakirov Karim, Vlasenco Daniel
+
+ 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..3acbef0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,112 @@
+# forest-group
+
+[![Kotlin 1.9.23][kotlin_img]][kotlin_releases_url]
+[![Tests passing][tests_passing_img]][tests_workflow_url]
+[![Code Coverage][code_coverage_badge_img]][code_coverage_workflow_url]
+[![License][license_img]][repo_license_url]
+
+`forest-group` is a library that lets you easily create and use [Binary search trees](https://en.wikipedia.org/wiki/Binary_search_tree), [AVL trees](https://en.wikipedia.org/wiki/AVL_tree) and [Red-black trees](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) in your applications!
+
+## Table of contents
+
+- [About the project](#about-the-project)
+- [Usage](#usage)
+- [Contributing](#contributing)
+- [License](#license)
+- [Authors](#authors)
+
+## About The Project
+
+Trees are well-known for their speed and efficiency.
+
+But there are lots of tree implementations that are strict to data types that can be stored.
+
+The solution is easy - *just use keys to store anything you want!*
+
+## Usage
+
+To create a tree, pass the key type and your stored data type to a generic. *Note that your key should implement Comparable type.*
+
+```kotlin
+val myBSTree = BSTree()
+
+val myAVLTree = AVLTree()
+
+val myRBTree = RBTree()
+```
+
+You now can simply insert and replace values in your tree:
+
+```kotlin
+val myKey = 10
+
+myBSTree.insert(myKey, "Something important")
+
+val replacedValue = myBSTree.insert(myKey, "Something more important")
+```
+
+You can also search for values and delete values from tree by keys:
+
+```kotlin
+val myValue1 = myBSTree.search(myKey)
+
+val myValue2 = myBSTree.delete(myKey)
+
+println(myValue1 == myValue2) // true
+```
+
+All trees are iterable by Pair(key, value), so they can be used in a for loop.
+
+Iterator implements inorder traversal (every next key is greater than the previous).
+
+```kotlin
+for ((key, value) in myRBTree) {
+ keysList.add(key)
+ valuesList.add(value)
+}
+
+myRBTree.forEach { println(it) } // prints every pair
+```
+
+## Contributing
+
+If you have found a bug, or want to propose some useful feature for our project, please firstly read our [Contribution Rules][contribute_rules_url] and
+do the following:
+1. Fork the Project
+2. Create your Feature Branch (git checkout -b feature/my-feature)
+3. Commit your Changes (git commit -m 'add some feature')
+4. Push to the Branch (git push origin feature/my-feature)
+5. Open a Pull Request
+
+## License
+
+Distributed under the [MIT License][repo_license_url].
+
+## Authors
+
+- [Shakirov Karim](https://github.com/kar1mgh)
+- [Vlasenco Daniel](https://github.com/spisladqo)
+- [Gavrilenko Mike](https://github.com/qrutyy)
+
+_______________________________
+
+[*Java gnomik*][java_gnomik_url]
+
+
+
+[kotlin_img]: https://img.shields.io/badge/Kotlin-%201.9.23-magenta
+[tests_passing_img]: https://img.shields.io/badge/tests-Passing-green
+[license_img]: https://img.shields.io/badge/license-MIT-blue
+[code_coverage_badge_img]: https://raw.githubusercontent.com/spbu-coding-2023/trees-2/main/.github/badges/jacoco.svg
+
+
+
+[tests_workflow_url]: https://github.com/spbu-coding-2023/trees-2/actions/workflows/test.yml
+[repo_license_url]: https://github.com/spbu-coding-2023/trees-2/blob/main/LICENSE.md
+[contribute_rules_url]: https://github.com/spbu-coding-2023/trees-2/blob/main/CONTRIBUTING.md
+[code_coverage_workflow_url]: https://github.com/spbu-coding-2023/trees-2/actions/workflows/codecoverage.yml
+
+
+
+[kotlin_releases_url]: https://kotlinlang.org/docs/releases.html#release-details
+[java_gnomik_url]: https://ibb.co/54hJVd2
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..d4d7e1f
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,15 @@
+# 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"
+junit-jupiter-engine = "5.10.0"
+
+[libraries]
+commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" }
+guava = { module = "com.google.guava:guava", version.ref = "guava" }
+junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter-engine" }
+
+[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..0bbd9c1
--- /dev/null
+++ b/lib/build.gradle.kts
@@ -0,0 +1,81 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * This generated file contains a sample Kotlin library project to get you started.
+ * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.6/userguide/building_java_projects.html in the Gradle documentation.
+ */
+
+plugins {
+ // Apply the java-library plugin for API and implementation separation.
+ `java-library`
+
+ kotlin("jvm") version "1.9.23"
+
+ jacoco
+
+ id("com.ncorti.ktfmt.gradle") version "0.17.0"
+}
+
+repositories {
+ // Use Maven Central for resolving dependencies.
+ mavenCentral()
+}
+
+dependencies {
+ // Use the Kotlin JUnit 5 integration.
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
+
+ // Use the JUnit 5 integration.
+ testImplementation(libs.junit.jupiter.engine)
+
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+
+ // This dependency is exported to consumers, that is to say found on their compile classpath.
+ api(libs.commons.math3)
+
+ // This dependency is used internally, and not exposed to consumers on their own compile classpath.
+ implementation(libs.guava)
+ implementation(kotlin("stdlib-jdk8"))
+}
+
+// Apply a specific Java toolchain to ease working on different environments.
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+}
+
+tasks.named("test") {
+ // Use JUnit Platform for unit tests.
+ useJUnitPlatform()
+ finalizedBy(tasks.jacocoTestReport) // report generates after run
+}
+
+// Configuring JaCoCo plugin settings
+jacoco {
+ toolVersion = "0.8.11"
+ reportsDirectory.set(layout.buildDirectory.dir("coverage")) // directory for reports
+}
+
+tasks.jacocoTestReport {
+ dependsOn(tasks.test) // tests are required to run before generating the report
+
+ reports {
+ xml.required = false
+ html.required = true
+ csv.required = true
+ csv.outputLocation.set(layout.buildDirectory.file("jacoco/report.csv"))
+ }
+}
+
+ktfmt {
+ googleStyle()
+
+ blockIndent.set(4)
+
+ continuationIndent.set(4)
+
+ removeUnusedImports.set(true)
+}
+
+tasks.register("format") { dependsOn(tasks.ktfmtFormat) }
diff --git a/lib/src/main/kotlin/tsl/iterator/BinaryTreeIterator.kt b/lib/src/main/kotlin/tsl/iterator/BinaryTreeIterator.kt
new file mode 100644
index 0000000..c5b83de
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/iterator/BinaryTreeIterator.kt
@@ -0,0 +1,28 @@
+package tsl.iterator
+
+import tsl.nodes.AbstractNode
+import tsl.trees.AbstractBinaryTree
+
+internal class BinaryTreeIterator, V, N : AbstractNode>(
+ tree: AbstractBinaryTree
+) : Iterator> {
+
+ private var stack = ArrayDeque()
+ private var currentNode: N? = tree.root
+
+ override fun hasNext(): Boolean {
+ return (!stack.isEmpty() || currentNode != null)
+ }
+
+ override fun next(): Pair {
+ while (currentNode != null) {
+ currentNode?.let { stack.addLast(it) }
+ currentNode = currentNode?.leftChild
+ }
+
+ val nextNode = stack.removeLast()
+ currentNode = nextNode.rightChild
+
+ return Pair(nextNode.key, nextNode.value)
+ }
+}
diff --git a/lib/src/main/kotlin/tsl/nodes/AVLNode.kt b/lib/src/main/kotlin/tsl/nodes/AVLNode.kt
new file mode 100644
index 0000000..b980b81
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/nodes/AVLNode.kt
@@ -0,0 +1,7 @@
+package tsl.nodes
+
+class AVLNode, V>(key: K, value: V) :
+ AbstractNode>(key, value) {
+
+ internal var height = 0
+}
diff --git a/lib/src/main/kotlin/tsl/nodes/AbstractNode.kt b/lib/src/main/kotlin/tsl/nodes/AbstractNode.kt
new file mode 100644
index 0000000..fadc6c5
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/nodes/AbstractNode.kt
@@ -0,0 +1,11 @@
+package tsl.nodes
+
+abstract class AbstractNode, V, N : AbstractNode>(
+ var key: K,
+ var value: V
+) {
+ internal var leftChild: N? = null
+ internal var rightChild: N? = null
+
+ internal fun hasTwoChildren(): Boolean = (this.leftChild != null && this.rightChild != null)
+}
diff --git a/lib/src/main/kotlin/tsl/nodes/BSTNode.kt b/lib/src/main/kotlin/tsl/nodes/BSTNode.kt
new file mode 100644
index 0000000..1c8529b
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/nodes/BSTNode.kt
@@ -0,0 +1,4 @@
+package tsl.nodes
+
+class BSTNode, V>(key: K, value: V) :
+ AbstractNode>(key, value)
diff --git a/lib/src/main/kotlin/tsl/nodes/RBNode.kt b/lib/src/main/kotlin/tsl/nodes/RBNode.kt
new file mode 100644
index 0000000..92ebc63
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/nodes/RBNode.kt
@@ -0,0 +1,14 @@
+package tsl.nodes
+
+class RBNode, V>(key: K, value: V) :
+ AbstractNode>(key, value) {
+
+ internal var parent: RBNode? = null
+
+ enum class Color {
+ Black,
+ Red,
+ }
+
+ internal var color = Color.Red
+}
diff --git a/lib/src/main/kotlin/tsl/trees/AVLTree.kt b/lib/src/main/kotlin/tsl/trees/AVLTree.kt
new file mode 100644
index 0000000..a9c82bc
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/trees/AVLTree.kt
@@ -0,0 +1,147 @@
+package tsl.trees
+
+import kotlin.math.max
+import tsl.nodes.AVLNode
+
+class AVLTree, V> : AbstractBinaryTree>() {
+
+ public override fun insert(key: K, value: V): V? {
+ val oldValueByKey = search(key)
+
+ root = insertNodeAndBalanceRec(root, key, value)
+
+ return oldValueByKey
+ }
+
+ private fun insertNodeAndBalanceRec(
+ currNode: AVLNode?,
+ keyToInsert: K,
+ valueToInsert: V
+ ): AVLNode {
+
+ if (currNode == null) {
+ val newNode = AVLNode(keyToInsert, valueToInsert)
+ return newNode
+ }
+
+ when {
+ keyToInsert < currNode.key ->
+ currNode.leftChild =
+ insertNodeAndBalanceRec(currNode.leftChild, keyToInsert, valueToInsert)
+ keyToInsert > currNode.key ->
+ currNode.rightChild =
+ insertNodeAndBalanceRec(currNode.rightChild, keyToInsert, valueToInsert)
+ else -> {
+ currNode.value = valueToInsert
+ return currNode
+ }
+ }
+
+ var balancedNode: AVLNode = currNode
+
+ if (getBalanceFactor(currNode) < -1) {
+ currNode.leftChild?.let {
+ if (keyToInsert > it.key) currNode.leftChild = rotateLeft(it)
+
+ balancedNode = rotateRight(currNode)
+ }
+ } else if (getBalanceFactor(currNode) > 1) {
+ currNode.rightChild?.let {
+ if (keyToInsert < it.key) currNode.rightChild = rotateRight(it)
+
+ balancedNode = rotateLeft(currNode)
+ }
+ }
+
+ updateHeight(balancedNode.leftChild)
+ updateHeight(balancedNode.rightChild)
+ updateHeight(balancedNode)
+
+ return balancedNode
+ }
+
+ private fun rotateRight(oldUpperNode: AVLNode): AVLNode {
+ val newUpperNode = oldUpperNode.leftChild ?: return oldUpperNode
+
+ oldUpperNode.leftChild = newUpperNode.rightChild
+ newUpperNode.rightChild = oldUpperNode
+
+ return newUpperNode
+ }
+
+ private fun rotateLeft(oldUpperNode: AVLNode): AVLNode {
+ val newUpperNode = oldUpperNode.rightChild ?: return oldUpperNode
+
+ oldUpperNode.rightChild = newUpperNode.leftChild
+ newUpperNode.leftChild = oldUpperNode
+
+ return newUpperNode
+ }
+
+ private fun updateHeight(node: AVLNode?): Int {
+ if (node == null) return -1
+ node.height = max(getHeight(node.leftChild), getHeight(node.rightChild)) + 1
+ return node.height
+ }
+
+ private fun getBalanceFactor(node: AVLNode): Int =
+ getHeight(node.rightChild) - getHeight(node.leftChild)
+
+ private fun getHeight(node: AVLNode?): Int = node?.height ?: -1
+
+ public override fun delete(key: K): V? {
+ val deletedValue = search(key) ?: return null
+
+ root = deleteNodeAndBalanceRec(root, key)
+
+ return deletedValue
+ }
+
+ private fun deleteNodeAndBalanceRec(currNode: AVLNode?, keyToDelete: K): AVLNode? {
+
+ if (currNode == null) return null
+
+ when {
+ keyToDelete < currNode.key ->
+ currNode.leftChild = deleteNodeAndBalanceRec(currNode.leftChild, keyToDelete)
+ keyToDelete > currNode.key ->
+ currNode.rightChild = deleteNodeAndBalanceRec(currNode.rightChild, keyToDelete)
+ keyToDelete == currNode.key -> {
+ if (currNode.hasTwoChildren()) {
+ val successor = getMinNodeRec(currNode.rightChild)
+ if (successor != null) {
+ currNode.key = successor.key
+ currNode.value = successor.value
+
+ val newSubtree = deleteNodeAndBalanceRec(currNode.rightChild, successor.key)
+ currNode.rightChild = newSubtree
+ }
+ } else {
+ return currNode.leftChild ?: currNode.rightChild
+ }
+ }
+ }
+
+ var balancedNode: AVLNode = currNode
+
+ if (getBalanceFactor(currNode) < -1) {
+ currNode.leftChild?.let {
+ if (getBalanceFactor(it) > 0) currNode.leftChild = rotateLeft(it)
+
+ balancedNode = rotateRight(currNode)
+ }
+ } else if (getBalanceFactor(currNode) > 1) {
+ currNode.rightChild?.let {
+ if (getBalanceFactor(it) < 0) currNode.rightChild = rotateRight(it)
+
+ balancedNode = rotateLeft(currNode)
+ }
+ }
+
+ updateHeight(balancedNode.leftChild)
+ updateHeight(balancedNode.rightChild)
+ updateHeight(balancedNode)
+
+ return balancedNode
+ }
+}
diff --git a/lib/src/main/kotlin/tsl/trees/AbstractBinaryTree.kt b/lib/src/main/kotlin/tsl/trees/AbstractBinaryTree.kt
new file mode 100644
index 0000000..a2379c7
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/trees/AbstractBinaryTree.kt
@@ -0,0 +1,53 @@
+package tsl.trees
+
+import tsl.iterator.BinaryTreeIterator
+import tsl.nodes.AbstractNode
+
+abstract class AbstractBinaryTree, V, N : AbstractNode> :
+ Iterable> {
+
+ internal var root: N? = null
+
+ public abstract fun delete(key: K): V?
+
+ public abstract fun insert(key: K, value: V): V?
+
+ public fun search(key: K): V? = searchNodeRec(root, key)?.value
+
+ protected fun searchNodeRec(currNode: N?, keyToSearch: K): N? {
+ return when {
+ currNode == null -> null
+ keyToSearch == currNode.key -> currNode
+ keyToSearch < currNode.key -> searchNodeRec(currNode.leftChild, keyToSearch)
+ else -> searchNodeRec(currNode.rightChild, keyToSearch)
+ }
+ }
+
+ public fun clear() {
+ root = null
+ }
+
+ public fun isEmpty(): Boolean = root == null
+
+ public fun getMinKey(): K? = getMinNodeRec(root)?.key
+
+ protected fun getMinNodeRec(node: N?): N? {
+ return when {
+ node == null -> null
+ node.leftChild == null -> node
+ else -> getMinNodeRec(node.leftChild)
+ }
+ }
+
+ public fun getMaxKey(): K? = getMaxNodeRec(root)?.key
+
+ protected fun getMaxNodeRec(node: N?): N? {
+ return when {
+ node == null -> null
+ node.rightChild == null -> node
+ else -> getMaxNodeRec(node.rightChild)
+ }
+ }
+
+ public override fun iterator(): Iterator> = BinaryTreeIterator(this)
+}
diff --git a/lib/src/main/kotlin/tsl/trees/BSTree.kt b/lib/src/main/kotlin/tsl/trees/BSTree.kt
new file mode 100644
index 0000000..5834465
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/trees/BSTree.kt
@@ -0,0 +1,77 @@
+package tsl.trees
+
+import tsl.nodes.BSTNode
+
+class BSTree, V> : AbstractBinaryTree>() {
+
+ public override fun insert(key: K, value: V): V? {
+ val nodeToInsert = BSTNode(key, value)
+
+ if (root == null) {
+ root = nodeToInsert
+ return null
+ }
+
+ var currentNode = root
+
+ while (currentNode != null) {
+ when {
+ nodeToInsert.key == currentNode.key -> {
+ val oldValue = currentNode.value
+ currentNode.value = nodeToInsert.value
+ return oldValue
+ }
+ nodeToInsert.key < currentNode.key -> {
+ if (currentNode.leftChild == null) {
+ currentNode.leftChild = nodeToInsert
+ return null
+ }
+ currentNode = currentNode.leftChild
+ }
+ nodeToInsert.key > currentNode.key -> {
+ if (currentNode.rightChild == null) {
+ currentNode.rightChild = nodeToInsert
+ return null
+ }
+ currentNode = currentNode.rightChild
+ }
+ }
+ }
+ return currentNode?.value
+ }
+
+ public override fun delete(key: K): V? {
+ val deletedValue: V? = search(key) ?: return null
+
+ deleteRecursively(root, key)
+
+ return deletedValue
+ }
+
+ private fun deleteRecursively(currentNode: BSTNode?, keyToDelete: K): BSTNode? {
+ if (currentNode == null) return null
+
+ when {
+ keyToDelete < currentNode.key ->
+ currentNode.leftChild = deleteRecursively(currentNode.leftChild, keyToDelete)
+ keyToDelete > currentNode.key ->
+ currentNode.rightChild = deleteRecursively(currentNode.rightChild, keyToDelete)
+ keyToDelete == currentNode.key -> {
+ if (currentNode.hasTwoChildren()) {
+ val minRightNode = getMinNodeRec(currentNode.rightChild)
+ if (minRightNode != null) {
+ currentNode.key = minRightNode.key
+ currentNode.value = minRightNode.value
+ currentNode.rightChild =
+ deleteRecursively(currentNode.rightChild, minRightNode.key)
+ }
+ } else {
+ if (currentNode == root) root = null
+ return currentNode.leftChild ?: currentNode.rightChild
+ }
+ }
+ }
+
+ return currentNode
+ }
+}
diff --git a/lib/src/main/kotlin/tsl/trees/RBTree.kt b/lib/src/main/kotlin/tsl/trees/RBTree.kt
new file mode 100644
index 0000000..69ecc25
--- /dev/null
+++ b/lib/src/main/kotlin/tsl/trees/RBTree.kt
@@ -0,0 +1,278 @@
+package tsl.trees
+
+import tsl.nodes.RBNode
+
+class RBTree, V> : AbstractBinaryTree>() {
+
+ // @return Null if 'insert' method haven't replaced any value. Return value if there was some
+ // replace
+
+ public override fun insert(key: K, value: V): V? {
+
+ val newNode = RBNode(key, value)
+ if (root == null) {
+ root = RBNode(key, value)
+ fixAfterInsertion(newNode)
+ return null
+ }
+
+ var currentNode: RBNode? = root
+ val returnValue: V? = search(key)
+
+ // traverse the tree to find the insertion point (node)
+ while (currentNode != null) {
+ if (currentNode.key > newNode.key) {
+ if (currentNode.leftChild == null) {
+ currentNode.leftChild = newNode
+ newNode.parent = currentNode
+ fixAfterInsertion(newNode)
+ break
+ }
+ currentNode = currentNode.leftChild
+ } else if (currentNode.key < newNode.key) {
+ if (currentNode.rightChild == null) {
+ currentNode.rightChild = newNode
+ newNode.parent = currentNode
+ fixAfterInsertion(newNode)
+ break
+ }
+ currentNode = currentNode.rightChild
+ } else {
+ currentNode.value = value
+ fixAfterInsertion(newNode)
+ break
+ }
+ }
+ return returnValue
+ }
+
+ private fun fixAfterInsertion(node: RBNode?) {
+ if (node?.parent == null) root.also { it?.color = RBNode.Color.Black }
+ var newNode = node
+ var uncle: RBNode?
+ var newRoot = root
+
+ while (newNode?.parent?.color == RBNode.Color.Red) {
+ val currentParent: RBNode? = newNode.parent
+ val currentGrandParent: RBNode? = currentParent?.parent
+ if (currentParent == currentGrandParent?.leftChild) {
+ uncle = currentParent?.parent?.rightChild
+ if (uncle?.color == RBNode.Color.Red) {
+ currentParent?.color = RBNode.Color.Black
+ uncle.color = RBNode.Color.Black
+ currentGrandParent?.color = RBNode.Color.Red
+ newNode = currentGrandParent
+ } else {
+ if (
+ currentParent?.color == RBNode.Color.Red &&
+ newNode.color == RBNode.Color.Red &&
+ newNode == currentParent.rightChild
+ ) {
+ newRoot = rotateLeft(currentParent, newRoot)
+ newNode.color = RBNode.Color.Black
+ newNode.leftChild?.color = RBNode.Color.Red
+ newRoot = rotateRight(currentGrandParent, newRoot)
+ newNode.rightChild?.color = RBNode.Color.Red
+ break
+ }
+ currentParent?.color = RBNode.Color.Black
+ currentGrandParent?.color = RBNode.Color.Red
+ newRoot = rotateRight(currentGrandParent, newRoot)
+ }
+ } else if (currentParent == currentGrandParent?.rightChild) {
+ uncle = newNode.parent?.parent?.leftChild
+ if (uncle?.color == RBNode.Color.Red) {
+ currentParent?.color = RBNode.Color.Black
+ uncle.color = RBNode.Color.Black
+ currentGrandParent?.color = RBNode.Color.Red
+ newNode = currentGrandParent
+ } else {
+ if (
+ currentParent?.color == RBNode.Color.Red &&
+ newNode.color == RBNode.Color.Red &&
+ newNode == currentParent.leftChild
+ ) {
+ currentParent.leftChild?.color = RBNode.Color.Black
+ newRoot = rotateRight(currentParent, newRoot)
+ }
+ currentParent?.color = RBNode.Color.Black
+ currentGrandParent?.color = RBNode.Color.Red
+ newRoot = rotateLeft(currentGrandParent, newRoot)
+ }
+ }
+ }
+ newRoot?.color = RBNode.Color.Black
+ root = newRoot
+ }
+
+ // @return Value if delete was successful and null if it isn't.
+
+ public override fun delete(key: K): V? {
+ val deletingNodeValue = search(key)
+ if (deletingNodeValue == null) return null // in case we do not found the deletinnode
+ root = deleteNode(key)
+ root?.color = RBNode.Color.Black // ensure the root is black after deletion
+ return deletingNodeValue
+ }
+
+ // @return The new root node of the Red\-Black Tree after deletion\.
+
+ private fun deleteNode(
+ key: K,
+ ): RBNode? {
+ var current: RBNode? = searchNodeRec(root, key)
+ if (current == null) return null
+ var newRoot = root
+
+ if (current.hasTwoChildren()) {
+ val successor = getMaxNodeRec(current.leftChild)
+ if (successor?.parent?.leftChild == successor) successor?.parent?.leftChild = null
+ else successor?.parent?.rightChild = null
+ if (successor != null) {
+ current.value = successor.value
+ current.key = successor.key
+ }
+ current = successor
+ if (successor?.color == RBNode.Color.Red) {
+ return newRoot
+ }
+ }
+
+ val child = if (current?.leftChild != null) current.leftChild else current?.rightChild
+ if (child != null) {
+ child.parent = current?.parent
+ if (current?.parent == null) return child
+ if (current == current.parent?.leftChild) {
+ current.parent?.leftChild = child
+ } else {
+ current.parent?.rightChild = child
+ }
+
+ if (current.color == RBNode.Color.Black) {
+ newRoot = fixAfterDeletion(child)
+ }
+ } else if (current?.parent == null) {
+ return null
+ } else {
+ if (current.color == RBNode.Color.Black) {
+ newRoot = fixAfterDeletion(current)
+ }
+ if (current.parent?.leftChild == current) {
+ current.parent?.leftChild = null
+ } else {
+ current.parent?.rightChild = null
+ }
+ }
+ return newRoot
+ }
+
+ // @return The root of the tree after fixing the structure to maintain the red-black properties
+
+ private fun fixAfterDeletion(currentDeletingNode: RBNode?): RBNode? {
+ var fixedRoot = root
+ var currentNode = currentDeletingNode
+ // currentNode - variable that is used to perform various operations and control the loop of
+ // fixing the tree.
+
+ while (currentNode != root && currentNode?.color == RBNode.Color.Black) {
+ if (currentNode == currentNode.parent?.leftChild) {
+ var currentSibling: RBNode? = currentNode.parent?.rightChild
+ if (currentSibling?.color == RBNode.Color.Red) { // 1st case "currentSibling is red"
+ currentSibling.color = RBNode.Color.Black
+ currentNode.parent?.color = RBNode.Color.Red
+ fixedRoot = rotateLeft(currentNode.parent, fixedRoot)
+ } else {
+ // 2nd case "black currentSibling, right child is black"
+ currentSibling?.color = currentNode.parent?.color ?: RBNode.Color.Black
+ currentNode.parent?.color = RBNode.Color.Black
+ currentSibling?.rightChild?.color = RBNode.Color.Black
+ fixedRoot = rotateLeft(currentNode.parent, fixedRoot)
+ currentNode = fixedRoot
+ }
+ } else {
+ var currentSibling: RBNode? = currentNode.parent?.leftChild
+ if (currentSibling?.color == RBNode.Color.Red) {
+ // 1st case "red currentSibling"
+ currentSibling.color = RBNode.Color.Black
+ currentNode.parent?.color = RBNode.Color.Red
+ fixedRoot = rotateRight(currentNode.parent, fixedRoot)
+ } else {
+ // 2nd case "black currentSibling, left child is red"
+ currentSibling?.color = currentNode.parent?.color ?: RBNode.Color.Black
+ currentNode.parent?.color = RBNode.Color.Black
+ currentSibling?.leftChild?.color = RBNode.Color.Black
+ fixedRoot = rotateRight(currentNode.parent, fixedRoot)
+ currentNode = fixedRoot
+ }
+ }
+ }
+ fixAfterInsertion(fixedRoot)
+ currentNode?.color = RBNode.Color.Black
+ return fixedRoot
+ }
+
+ private fun rotateRight(
+ nodeToRotate: RBNode?,
+ currentRoot: RBNode?,
+ ): RBNode? {
+ val leftChild = nodeToRotate?.leftChild
+ var newRoot = currentRoot
+
+ leftChild?.parent = nodeToRotate?.parent
+ nodeToRotate?.parent = leftChild
+
+ if (leftChild?.rightChild != null) {
+ nodeToRotate.leftChild = leftChild.rightChild
+ leftChild.rightChild?.parent = nodeToRotate
+ leftChild.rightChild = nodeToRotate
+ } else {
+ leftChild?.rightChild = nodeToRotate
+ nodeToRotate?.leftChild = null
+ }
+
+ if (leftChild?.parent != null) {
+ if (leftChild.parent?.leftChild == nodeToRotate) {
+ leftChild.parent?.leftChild = leftChild
+ } else {
+ leftChild.parent?.rightChild = leftChild
+ }
+ } else {
+ root = leftChild
+ newRoot = leftChild
+ }
+ return newRoot
+ }
+
+ private fun rotateLeft(
+ nodeToRotate: RBNode?,
+ currentRoot: RBNode?,
+ ): RBNode? {
+ val rightChild = nodeToRotate?.rightChild
+ var newRoot = currentRoot
+
+ rightChild?.parent = nodeToRotate?.parent
+ nodeToRotate?.parent = rightChild
+
+ if (rightChild?.leftChild != null) {
+ nodeToRotate.rightChild = rightChild.leftChild
+ rightChild.leftChild?.parent = nodeToRotate.rightChild
+ rightChild.leftChild = nodeToRotate
+ } else {
+ rightChild?.leftChild = nodeToRotate
+ nodeToRotate?.rightChild = null
+ }
+
+ // rightChild took over "nodeToRotate" place so now its parent should point to rightChild
+ if (rightChild?.parent != null) {
+ if (rightChild.parent?.rightChild == nodeToRotate) {
+ rightChild.parent?.rightChild = rightChild
+ } else {
+ rightChild.parent?.leftChild = rightChild
+ }
+ } else {
+ root = rightChild
+ newRoot = rightChild
+ }
+ return newRoot
+ }
+}
diff --git a/lib/src/test/kotlin/tsl/trees/AVLTreeTest.kt b/lib/src/test/kotlin/tsl/trees/AVLTreeTest.kt
new file mode 100644
index 0000000..4572784
--- /dev/null
+++ b/lib/src/test/kotlin/tsl/trees/AVLTreeTest.kt
@@ -0,0 +1,842 @@
+package tsl.trees
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+class AVLTreeTest {
+ private lateinit var avlTree: AVLTree
+
+ @BeforeEach
+ fun setup() {
+ avlTree = AVLTree()
+ }
+
+ @Nested
+ inner class InsertTests {
+ // Common tests
+
+ @Test
+ fun `insert of new key should return null`() {
+ val expectedValue = null
+ val actualValue = avlTree.insert(1, "I am just a freak")
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `insert of existing key should replace old value with new value and return old value`() {
+ avlTree.insert(1, "But I love you so")
+
+ val expectedReturnValue = "But I love you so"
+ val actualReturnValue = avlTree.insert(1, "Please let me go")
+
+ val expectedNewValue = "Please let me go"
+ val actualNewValue = avlTree.search(1)
+
+ assertEquals(expectedReturnValue, actualReturnValue, "Returned wrong value")
+ assertEquals(expectedNewValue, actualNewValue, "Value wasn't replaced with new value")
+ }
+
+ // Small tree tests
+
+ @Test
+ fun `insertion of left child triggering right rotation should balance small tree`() {
+ avlTree.insert(20, "Take")
+ avlTree.insert(15, "To church")
+ avlTree.insert(4, "Me")
+
+ val expectedStructure = listOf(Pair(4, "Me"), Pair(15, "To church"), Pair(20, "Take"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 15)
+ }
+
+ @Test
+ fun `insertion of right child triggering left rotation should balance small tree`() {
+ avlTree.insert(4, "A")
+ avlTree.insert(15, "B")
+ avlTree.insert(20, "C")
+
+ val expectedStructure = listOf(Pair(4, "A"), Pair(15, "B"), Pair(20, "C"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 15)
+ }
+
+ // Medium tree tests
+
+ @Test
+ fun `insertion of right child triggering left-right rotation should balance medium tree`() {
+ avlTree.insert(20, "I")
+ avlTree.insert(4, "Swear")
+ avlTree.insert(26, "I'll only")
+ avlTree.insert(9, "Make")
+ avlTree.insert(3, "You")
+
+ avlTree.insert(15, "Cry")
+
+ val expectedStructure =
+ listOf(
+ Pair(3, "You"),
+ Pair(4, "Swear"),
+ Pair(9, "Make"),
+ Pair(15, "Cry"),
+ Pair(20, "I"),
+ Pair(26, "I'll only")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 9)
+ }
+
+ @Test
+ fun `insertion of left child triggering left-right rotation should balance medium tree`() {
+ avlTree.insert(20, "E")
+ avlTree.insert(4, "B")
+ avlTree.insert(26, "F")
+ avlTree.insert(3, "A")
+ avlTree.insert(9, "D")
+
+ avlTree.insert(8, "C")
+
+ val expectedStructure =
+ listOf(
+ Pair(3, "A"),
+ Pair(4, "B"),
+ Pair(8, "C"),
+ Pair(9, "D"),
+ Pair(20, "E"),
+ Pair(26, "F")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 9)
+ }
+
+ @Test
+ fun `insertion of right child triggering right-left rotation should balance medium tree`() {
+ avlTree.insert(20, "B")
+ avlTree.insert(4, "A")
+ avlTree.insert(26, "E")
+ avlTree.insert(24, "C")
+ avlTree.insert(30, "F")
+
+ avlTree.insert(25, "D")
+
+ val expectedStructure =
+ listOf(
+ Pair(4, "A"),
+ Pair(20, "B"),
+ Pair(24, "C"),
+ Pair(25, "D"),
+ Pair(26, "E"),
+ Pair(30, "F")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 24)
+ }
+
+ @Test
+ fun `insertion of left child triggering right-left rotation should balance medium tree`() {
+ avlTree.insert(20, "B")
+ avlTree.insert(4, "A")
+ avlTree.insert(26, "E")
+ avlTree.insert(24, "D")
+ avlTree.insert(30, "F")
+
+ avlTree.insert(21, "C")
+
+ val expectedStructure =
+ listOf(
+ Pair(4, "A"),
+ Pair(20, "B"),
+ Pair(21, "C"),
+ Pair(24, "D"),
+ Pair(26, "E"),
+ Pair(30, "F")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 24)
+ }
+
+ // Large tree tests
+
+ @Test
+ fun `insertion of right child triggering left-right rotation should balance large tree`() {
+ avlTree.insert(20, "Master")
+ avlTree.insert(4, "Of puppets")
+ avlTree.insert(26, "I'm pulling")
+ avlTree.insert(3, "Your")
+ avlTree.insert(9, "Strings")
+ avlTree.insert(21, "Twisting")
+ avlTree.insert(30, "Your mind")
+ avlTree.insert(2, "And")
+ avlTree.insert(7, "Smashing")
+ avlTree.insert(11, "Your")
+
+ avlTree.insert(15, "Dreams")
+
+ val expectedStructure =
+ listOf(
+ Pair(2, "And"),
+ Pair(3, "Your"),
+ Pair(4, "Of puppets"),
+ Pair(7, "Smashing"),
+ Pair(9, "Strings"),
+ Pair(11, "Your"),
+ Pair(15, "Dreams"),
+ Pair(20, "Master"),
+ Pair(21, "Twisting"),
+ Pair(26, "I'm pulling"),
+ Pair(30, "Your mind")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 9)
+ }
+
+ @Test
+ fun `insertion of left child triggering left-right rotation should balance large tree`() {
+ avlTree.insert(20, "H")
+ avlTree.insert(4, "C")
+ avlTree.insert(26, "J")
+ avlTree.insert(3, "B")
+ avlTree.insert(9, "F")
+ avlTree.insert(21, "I")
+ avlTree.insert(30, "K")
+ avlTree.insert(2, "A")
+ avlTree.insert(7, "E")
+ avlTree.insert(11, "G")
+
+ avlTree.insert(6, "D")
+
+ val expectedStructure =
+ listOf(
+ Pair(2, "A"),
+ Pair(3, "B"),
+ Pair(4, "C"),
+ Pair(6, "D"),
+ Pair(7, "E"),
+ Pair(9, "F"),
+ Pair(11, "G"),
+ Pair(20, "H"),
+ Pair(21, "I"),
+ Pair(26, "J"),
+ Pair(30, "K")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 9)
+ }
+
+ @Test
+ fun `insertion of right child triggering right-left rotation should balance large tree`() {
+ avlTree.insert(20, "D")
+ avlTree.insert(10, "B")
+ avlTree.insert(30, "I")
+ avlTree.insert(3, "A")
+ avlTree.insert(15, "C")
+ avlTree.insert(25, "F")
+ avlTree.insert(35, "J")
+ avlTree.insert(22, "E")
+ avlTree.insert(27, "G")
+ avlTree.insert(40, "K")
+
+ avlTree.insert(28, "H")
+
+ val expectedStructure =
+ listOf(
+ Pair(3, "A"),
+ Pair(10, "B"),
+ Pair(15, "C"),
+ Pair(20, "D"),
+ Pair(22, "E"),
+ Pair(25, "F"),
+ Pair(27, "G"),
+ Pair(28, "H"),
+ Pair(30, "I"),
+ Pair(35, "J"),
+ Pair(40, "K")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 25)
+ }
+
+ @Test
+ fun `insertion of left child triggering right-left rotation should balance large tree`() {
+ avlTree.insert(20, "D")
+ avlTree.insert(10, "B")
+ avlTree.insert(30, "I")
+ avlTree.insert(3, "A")
+ avlTree.insert(15, "C")
+ avlTree.insert(25, "F")
+ avlTree.insert(35, "J")
+ avlTree.insert(22, "E")
+ avlTree.insert(27, "H")
+ avlTree.insert(40, "K")
+
+ avlTree.insert(26, "G")
+
+ val expectedStructure =
+ listOf(
+ Pair(3, "A"),
+ Pair(10, "B"),
+ Pair(15, "C"),
+ Pair(20, "D"),
+ Pair(22, "E"),
+ Pair(25, "F"),
+ Pair(26, "G"),
+ Pair(27, "H"),
+ Pair(30, "I"),
+ Pair(35, "J"),
+ Pair(40, "K")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 25)
+ }
+ }
+
+ @Nested
+ inner class DeleteTests {
+ // Common tests
+
+ @Test
+ fun `if key doesn't exist deletion should not change tree and return null`() {
+ avlTree.insert(10, "A")
+ avlTree.insert(20, "B")
+ avlTree.insert(30, "C")
+
+ val expectedReturn = null
+ val actualReturn = avlTree.delete(40)
+
+ val expectedStructure = listOf(Pair(10, "A"), Pair(20, "B"), Pair(30, "C"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedReturn, actualReturn, "Returned wrong value")
+ assertEquals(expectedStructure, actualStructure, "The tree changed after no deletion")
+ assertEquals(avlTree.root?.key, 20)
+ }
+
+ @Test
+ fun `if key exists deletion should return associated value and delete node`() {
+ avlTree.insert(10, "A")
+ avlTree.insert(20, "B")
+ avlTree.insert(30, "C")
+
+ val expectedReturn = "A"
+ val actualReturn = avlTree.delete(10)
+
+ val expectedStructure = listOf(Pair(20, "B"), Pair(30, "C"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedReturn, actualReturn, "Returned wrong value")
+ assertEquals(
+ expectedStructure,
+ actualStructure,
+ "The tree has incorrect structure after deletion"
+ )
+ assertEquals(avlTree.root?.key, 20)
+ }
+
+ @Test
+ fun `deletion of last node should make tree empty`() {
+ avlTree.insert(1, "A")
+ avlTree.delete(1)
+
+ val expectedStructure = listOf>()
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(avlTree.isEmpty(), true)
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `deletion of node with one child should replace it with its child`() {
+ avlTree.insert(20, "B")
+ avlTree.insert(10, "A")
+ avlTree.insert(30, "C")
+ avlTree.insert(40, "D")
+
+ avlTree.delete(30)
+
+ val expectedStructure = listOf(Pair(10, "A"), Pair(20, "B"), Pair(40, "D"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 20)
+ }
+
+ // Small tree tests
+
+ @Test
+ fun `deletion of root node should replace it with its successor`() {
+ avlTree.insert(10, "A")
+ avlTree.insert(20, "B")
+ avlTree.insert(30, "C")
+
+ avlTree.delete(20)
+
+ val expectedStructure = listOf(Pair(10, "A"), Pair(30, "C"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 30)
+ }
+
+ @Test
+ fun `deletion of right child triggering right rotation should balance small tree`() {
+ avlTree.insert(40, "D")
+ avlTree.insert(30, "C")
+ avlTree.insert(20, "B")
+ avlTree.insert(10, "A")
+
+ avlTree.delete(40)
+
+ val expectedStructure = listOf(Pair(10, "A"), Pair(20, "B"), Pair(30, "C"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 20)
+ }
+
+ @Test
+ fun `deletion of left child triggering left rotation should balance small tree`() {
+ avlTree.insert(10, "A")
+ avlTree.insert(20, "B")
+ avlTree.insert(30, "C")
+ avlTree.insert(40, "D")
+
+ avlTree.delete(10)
+
+ val expectedStructure = listOf(Pair(20, "B"), Pair(30, "C"), Pair(40, "D"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 30)
+ }
+
+ @Test
+ fun `deletion of right child triggering left-right rotation should balance small tree`() {
+ avlTree.insert(30, "C")
+ avlTree.insert(10, "A")
+ avlTree.insert(40, "D")
+ avlTree.insert(20, "B")
+
+ avlTree.delete(40)
+
+ val expectedStructure = listOf(Pair(10, "A"), Pair(20, "B"), Pair(30, "C"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 20)
+ }
+
+ @Test
+ fun `deletion of left child triggering right-left rotation should balance small tree`() {
+ avlTree.insert(20, "B")
+ avlTree.insert(10, "A")
+ avlTree.insert(40, "D")
+ avlTree.insert(30, "C")
+
+ avlTree.delete(10)
+
+ val expectedStructure = listOf(Pair(20, "B"), Pair(30, "C"), Pair(40, "D"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 30)
+ }
+
+ @Test
+ fun `deletion of node with two children should replace it with its successor in small tree`() {
+ avlTree.insert(20, "B")
+ avlTree.insert(10, "A")
+ avlTree.insert(30, "C")
+ avlTree.insert(40, "D")
+
+ avlTree.delete(20)
+
+ val expectedStructure = listOf(Pair(10, "A"), Pair(30, "C"), Pair(40, "D"))
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 30)
+ }
+
+ // Medium tree tests
+
+ @Test
+ fun `deletion of right child triggering right rotation should balance medium tree`() {
+ avlTree.insert(50, "E")
+ avlTree.insert(30, "C")
+ avlTree.insert(60, "F")
+ avlTree.insert(20, "B")
+ avlTree.insert(40, "D")
+ avlTree.insert(70, "G")
+ avlTree.insert(10, "A")
+
+ avlTree.delete(70)
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "A"),
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 30)
+ }
+
+ @Test
+ fun `deletion of left child triggering left rotation should balance medium tree`() {
+ avlTree.insert(30, "C")
+ avlTree.insert(20, "B")
+ avlTree.insert(50, "E")
+ avlTree.insert(10, "A")
+ avlTree.insert(40, "D")
+ avlTree.insert(60, "F")
+ avlTree.insert(70, "G")
+
+ avlTree.delete(10)
+
+ val expectedStructure =
+ listOf(
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F"),
+ Pair(70, "G")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 50)
+ }
+
+ @Test
+ fun `deletion of node with two children should replace it with its successor in medium tree`() {
+ avlTree.insert(30, "C")
+ avlTree.insert(20, "B")
+ avlTree.insert(50, "E")
+ avlTree.insert(10, "A")
+ avlTree.insert(40, "D")
+ avlTree.insert(60, "F")
+ avlTree.insert(70, "G")
+
+ avlTree.delete(50)
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "A"),
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(60, "F"),
+ Pair(70, "G")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 30)
+ }
+
+ // Large tree tests
+
+ @Test
+ fun `deletion of right child triggering right rotation should balance large tree`() {
+ avlTree.insert(80, "H")
+ avlTree.insert(50, "E")
+ avlTree.insert(120, "L")
+ avlTree.insert(30, "C")
+ avlTree.insert(60, "F")
+ avlTree.insert(100, "J")
+ avlTree.insert(130, "M")
+ avlTree.insert(20, "B")
+ avlTree.insert(40, "D")
+ avlTree.insert(70, "G")
+ avlTree.insert(90, "I")
+ avlTree.insert(110, "K")
+ avlTree.insert(10, "A")
+
+ avlTree.delete(130)
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "A"),
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F"),
+ Pair(70, "G"),
+ Pair(80, "H"),
+ Pair(90, "I"),
+ Pair(100, "J"),
+ Pair(110, "K"),
+ Pair(120, "L")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 80)
+ }
+
+ @Test
+ fun `deletion of left child triggering left rotation should balance large tree`() {
+ avlTree.insert(60, "F")
+ avlTree.insert(20, "B")
+ avlTree.insert(90, "I")
+ avlTree.insert(10, "A")
+ avlTree.insert(40, "D")
+ avlTree.insert(80, "H")
+ avlTree.insert(110, "K")
+ avlTree.insert(30, "C")
+ avlTree.insert(50, "E")
+ avlTree.insert(70, "G")
+ avlTree.insert(100, "J")
+ avlTree.insert(120, "L")
+ avlTree.insert(130, "M")
+
+ avlTree.delete(10)
+
+ val expectedStructure =
+ listOf(
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F"),
+ Pair(70, "G"),
+ Pair(80, "H"),
+ Pair(90, "I"),
+ Pair(100, "J"),
+ Pair(110, "K"),
+ Pair(120, "L"),
+ Pair(130, "M")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 60)
+ }
+
+ @Test
+ fun `deletion of right child triggering double right rotation should balance large tree`() {
+ avlTree.insert(80, "H")
+ avlTree.insert(50, "E")
+ avlTree.insert(110, "K")
+ avlTree.insert(30, "C")
+ avlTree.insert(60, "F")
+ avlTree.insert(100, "J")
+ avlTree.insert(120, "L")
+ avlTree.insert(20, "B")
+ avlTree.insert(40, "D")
+ avlTree.insert(70, "G")
+ avlTree.insert(90, "I")
+ avlTree.insert(10, "A")
+
+ avlTree.delete(120)
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "A"),
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F"),
+ Pair(70, "G"),
+ Pair(80, "H"),
+ Pair(90, "I"),
+ Pair(100, "J"),
+ Pair(110, "K")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 50)
+ }
+
+ @Test
+ fun `deletion of left child triggering double left rotation should balance large tree`() {
+ avlTree.insert(50, "E")
+ avlTree.insert(20, "B")
+ avlTree.insert(80, "H")
+ avlTree.insert(10, "A")
+ avlTree.insert(30, "C")
+ avlTree.insert(70, "G")
+ avlTree.insert(100, "J")
+ avlTree.insert(40, "D")
+ avlTree.insert(60, "F")
+ avlTree.insert(90, "I")
+ avlTree.insert(110, "K")
+ avlTree.insert(120, "L")
+
+ avlTree.delete(10)
+
+ val expectedStructure =
+ listOf(
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F"),
+ Pair(70, "G"),
+ Pair(80, "H"),
+ Pair(90, "I"),
+ Pair(100, "J"),
+ Pair(110, "K"),
+ Pair(120, "L")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 80)
+ }
+
+ @Test
+ fun `deletion of node with two children should replace it with its successor in large tree`() {
+ avlTree.insert(80, "H")
+ avlTree.insert(50, "E")
+ avlTree.insert(120, "L")
+ avlTree.insert(30, "C")
+ avlTree.insert(60, "F")
+ avlTree.insert(100, "J")
+ avlTree.insert(130, "M")
+ avlTree.insert(20, "B")
+ avlTree.insert(40, "D")
+ avlTree.insert(70, "G")
+ avlTree.insert(90, "I")
+ avlTree.insert(110, "K")
+ avlTree.insert(10, "A")
+
+ avlTree.delete(80)
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "A"),
+ Pair(20, "B"),
+ Pair(30, "C"),
+ Pair(40, "D"),
+ Pair(50, "E"),
+ Pair(60, "F"),
+ Pair(70, "G"),
+ Pair(90, "I"),
+ Pair(100, "J"),
+ Pair(110, "K"),
+ Pair(120, "L"),
+ Pair(130, "M")
+ )
+
+ val actualStructure = mutableListOf>()
+
+ for (pair in avlTree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ assertEquals(avlTree.root?.key, 90)
+ }
+ }
+}
diff --git a/lib/src/test/kotlin/tsl/trees/AbstractBinaryTreeTest.kt b/lib/src/test/kotlin/tsl/trees/AbstractBinaryTreeTest.kt
new file mode 100644
index 0000000..a51af98
--- /dev/null
+++ b/lib/src/test/kotlin/tsl/trees/AbstractBinaryTreeTest.kt
@@ -0,0 +1,365 @@
+package tsl.trees
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+/**
+ * This class tests methods implemented in the AbstractTreeClass that are common for all its
+ * inheritors (BSTree, AVLTree, RBTree). BSTree is used in tests as an instance of the
+ * AbstractTreeClass.
+ */
+class AbstractBinaryTreeTest {
+ private lateinit var tree: BSTree
+
+ @BeforeEach
+ fun setup() {
+ tree = BSTree()
+ tree.insert(30, "Danya")
+ tree.insert(10, "Misha")
+ tree.insert(50, "Karim")
+ tree.insert(20, "Moldova")
+ tree.insert(40, "Tatarstan")
+ }
+
+ @Nested
+ inner class SearchTests {
+
+ @Test
+ fun `search of non-existing key should return null`() {
+ val expectedValue = null
+ val actualValue = tree.search(100)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `search of existing key should return associated value`() {
+ val expectedValue = "Danya"
+ val actualValue = tree.search(30)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `search of existing key that is to the left of the root node should return the corresponding value`() {
+ val expectedValue = "Moldova"
+ val actualValue = tree.search(20)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `search of existing key that is to the right of the root node should return the corresponding value`() {
+ val expectedValue = "Tatarstan"
+ val actualValue = tree.search(40)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `empty tree should not change after search`() {
+ tree.clear()
+ tree.search(1)
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+
+ @Test
+ fun `non-empty tree should not change after search`() {
+ tree.search(40)
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "Misha"),
+ Pair(20, "Moldova"),
+ Pair(30, "Danya"),
+ Pair(40, "Tatarstan"),
+ Pair(50, "Karim")
+ )
+
+ val actualStructure = mutableListOf>()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+ }
+
+ @Nested
+ inner class ClearTests {
+
+ @Test
+ fun `empty tree should be empty after clear`() {
+ val emptyTree = BSTree()
+ emptyTree.clear()
+
+ val expectedRoot = null
+ val actualRoot = emptyTree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+
+ @Test
+ fun `not empty tree should be empty after clear`() {
+ tree.clear()
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+ }
+
+ @Nested
+ inner class IsEmptyTests {
+
+ @Test
+ fun `isEmpty should return true if the tree is empty`() {
+ tree.clear()
+
+ val expectedValue = true
+ val actualValue = tree.isEmpty()
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `isEmpty should return false if the tree is not empty`() {
+ val expectedValue = false
+ val actualValue = tree.isEmpty()
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `isEmpty should not change an empty tree`() {
+ tree.clear()
+ tree.isEmpty()
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+
+ @Test
+ fun `isEmpty should not change a non-empty tree`() {
+ tree.isEmpty()
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "Misha"),
+ Pair(20, "Moldova"),
+ Pair(30, "Danya"),
+ Pair(40, "Tatarstan"),
+ Pair(50, "Karim")
+ )
+
+ val actualStructure = mutableListOf>()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+ }
+
+ @Nested
+ inner class GetMinKeyTests {
+
+ @Test
+ fun `getMinKey should return null if the tree is empty`() {
+ tree.clear()
+
+ val expectedKey = null
+ val actualKey = tree.getMinKey()
+
+ assertEquals(expectedKey, actualKey)
+ }
+
+ @Test
+ fun `getMinKey should return min key if the tree is not empty`() {
+ val expectedKey = 10
+ val actualKey = tree.getMinKey()
+
+ assertEquals(expectedKey, actualKey)
+ }
+
+ @Test
+ fun `getMinKey should not change an empty tree`() {
+ tree.clear()
+ tree.getMinKey()
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+
+ @Test
+ fun `getMinKey should not change a non-empty tree`() {
+ tree.getMinKey()
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "Misha"),
+ Pair(20, "Moldova"),
+ Pair(30, "Danya"),
+ Pair(40, "Tatarstan"),
+ Pair(50, "Karim")
+ )
+
+ val actualStructure = mutableListOf>()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+ }
+
+ @Nested
+ inner class GetMaxKeyTests {
+
+ @Test
+ fun `getMaxKey should return null if the tree is empty`() {
+ tree.clear()
+
+ val expectedKey = null
+ val actualKey = tree.getMaxKey()
+
+ assertEquals(expectedKey, actualKey)
+ }
+
+ @Test
+ fun `getMaxKey should return max key if the tree is not empty`() {
+ val expectedKey = 50
+ val actualKey = tree.getMaxKey()
+
+ assertEquals(expectedKey, actualKey)
+ }
+
+ @Test
+ fun `getMaxKey should not change an empty tree`() {
+ tree.clear()
+ tree.getMaxKey()
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+
+ @Test
+ fun `getMaxKey should not change a non-empty tree`() {
+ tree.getMaxKey()
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "Misha"),
+ Pair(20, "Moldova"),
+ Pair(30, "Danya"),
+ Pair(40, "Tatarstan"),
+ Pair(50, "Karim")
+ )
+
+ val actualStructure = mutableListOf>()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+ }
+
+ @Nested
+ inner class IteratorTests {
+
+ @Test
+ fun `hasNext should return false right after it traversed all nodes in a non-empty tree`() {
+ val treeIterator = tree.iterator()
+
+ var count = 0
+ while (treeIterator.hasNext()) {
+ treeIterator.next()
+ count++
+ }
+
+ val expectedQuantity = 5
+ val actualQuantity = count
+
+ assertEquals(expectedQuantity, actualQuantity)
+ }
+
+ @Test
+ fun `hasNext should always return false in an empty tree`() {
+ tree.clear()
+ val treeIterator = tree.iterator()
+
+ var count = 0
+ while (treeIterator.hasNext()) {
+ treeIterator.next()
+ count++
+ }
+
+ val expectedQuantity = 0
+ val actualQuantity = count
+
+ assertEquals(expectedQuantity, actualQuantity)
+ }
+
+ @Test
+ fun `next method should return pairs with bigger keys than it has returned before`() {
+ val treeIterator = tree.iterator()
+
+ var everyNextKeyIsBigger = true
+
+ var currentKey = treeIterator.next().first
+ var previousKey = currentKey
+ var iteration = 0
+ while (treeIterator.hasNext()) {
+ iteration++
+ currentKey = treeIterator.next().first
+ if (currentKey <= previousKey) {
+ everyNextKeyIsBigger = false
+ break
+ }
+ previousKey = currentKey
+ }
+
+ assertEquals(true, everyNextKeyIsBigger)
+ }
+
+ @Test
+ fun `iterator should not change an empty tree`() {
+ tree.clear()
+ val treeIterator = tree.iterator()
+
+ while (treeIterator.hasNext()) treeIterator.next()
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+
+ @Test
+ fun `iterator should not change a not empty tree`() {
+ val treeIterator = tree.iterator()
+
+ while (treeIterator.hasNext()) treeIterator.next()
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "Misha"),
+ Pair(20, "Moldova"),
+ Pair(30, "Danya"),
+ Pair(40, "Tatarstan"),
+ Pair(50, "Karim")
+ )
+
+ val actualStructure = mutableListOf>()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+ }
+}
diff --git a/lib/src/test/kotlin/tsl/trees/BSTreeTest.kt b/lib/src/test/kotlin/tsl/trees/BSTreeTest.kt
new file mode 100644
index 0000000..c3838bb
--- /dev/null
+++ b/lib/src/test/kotlin/tsl/trees/BSTreeTest.kt
@@ -0,0 +1,192 @@
+package tsl.trees
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+class BSTreeTest {
+ private lateinit var tree: BSTree
+
+ @BeforeEach
+ fun setup() {
+ tree = BSTree()
+ tree.insert(30, "java")
+ tree.insert(40, "gnomik")
+ tree.insert(20, "kotlin")
+ tree.insert(10, "kotik")
+ }
+
+ @Nested
+ inner class InsertTests {
+
+ @Test
+ fun `insert of the key that isn't in the tree should return null`() {
+ val expectedValue = null
+ val actualValue = tree.insert(50, "pesik")
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `insert of existing key should replace old value and return it`() {
+ val expectedReturnValue = "kotik"
+ val actualReturnValue = tree.insert(10, "pesik")
+
+ val expectedNewValue = "pesik"
+ val actualNewValue = tree.search(10)
+
+ assertEquals(expectedReturnValue, actualReturnValue, "wrong value returned")
+ assertEquals(expectedNewValue, actualNewValue, "value didn't change")
+ }
+
+ @Test
+ fun `node with the smallest key should be inserted at the most left position`() {
+ tree.insert(5, "pesik")
+
+ val expectedStructure =
+ listOf(
+ Pair(5, "pesik"),
+ Pair(10, "kotik"),
+ Pair(20, "kotlin"),
+ Pair(30, "java"),
+ Pair(40, "gnomik")
+ )
+
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `node with the biggest key should be inserted at the most right position`() {
+ tree.insert(50, "pesik")
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "kotik"),
+ Pair(20, "kotlin"),
+ Pair(30, "java"),
+ Pair(40, "gnomik"),
+ Pair(50, "pesik")
+ )
+
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `node with random key should be inserted at correct position`() {
+ tree.insert(25, "pesik")
+
+ val expectedStructure =
+ listOf(
+ Pair(10, "kotik"),
+ Pair(20, "kotlin"),
+ Pair(25, "pesik"),
+ Pair(30, "java"),
+ Pair(40, "gnomik")
+ )
+
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+ }
+
+ @Nested
+ inner class DeleteTests {
+
+ @Test
+ fun `delete of the non-existing key should return null`() {
+ val expectedValue = null
+ val actualValue = tree.delete(42069)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `delete of existing key should return the corresponding deleted value`() {
+ val expectedValue = "gnomik"
+ val actualValue = tree.delete(40)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `a key should be deleted after deletion`() {
+ tree.delete(30)
+
+ val expectedValue = null
+ val actualValue = tree.search(30)
+
+ assertEquals(expectedValue, actualValue)
+ }
+
+ @Test
+ fun `delete of a node without children shouldn't change tree structure`() {
+ tree.delete(10)
+
+ val expectedStructure = listOf(Pair(20, "kotlin"), Pair(30, "java"), Pair(40, "gnomik"))
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `delete of a node with a left child should replace it with it's child`() {
+ tree.delete(20)
+
+ val expectedStructure = listOf(Pair(10, "kotik"), Pair(30, "java"), Pair(40, "gnomik"))
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `delete of a node with a right child should replace it with it's child`() {
+ tree.insert(50, "pesik")
+ tree.delete(40)
+
+ val expectedStructure =
+ listOf(Pair(10, "kotik"), Pair(20, "kotlin"), Pair(30, "java"), Pair(50, "pesik"))
+
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `delete of a node with two children should replace it with smallest node in the right subtree`() {
+ tree.insert(35, "pesik")
+ tree.delete(30)
+
+ val expectedStructure =
+ listOf(Pair(10, "kotik"), Pair(20, "kotlin"), Pair(35, "pesik"), Pair(40, "gnomik"))
+
+ val actualStructure: MutableList> = mutableListOf()
+ for (pair in tree) actualStructure.add(pair)
+
+ assertEquals(expectedStructure, actualStructure)
+ }
+
+ @Test
+ fun `deletion of last node should make tree empty`() {
+ tree.clear()
+ tree.insert(1, "A")
+ tree.delete(1)
+
+ val expectedRoot = null
+ val actualRoot = tree.root
+
+ assertEquals(expectedRoot, actualRoot)
+ }
+ }
+}
diff --git a/lib/src/test/kotlin/tsl/trees/RBTreeTest.kt b/lib/src/test/kotlin/tsl/trees/RBTreeTest.kt
new file mode 100644
index 0000000..6f40fec
--- /dev/null
+++ b/lib/src/test/kotlin/tsl/trees/RBTreeTest.kt
@@ -0,0 +1,583 @@
+package tsl.trees
+
+import kotlin.random.Random
+import kotlin.test.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import tsl.nodes.RBNode
+
+/*
+ * Some standarts and "variables" that were used for decreasing the amount of words)
+ * S - sibling of deleting node
+ * V - deleting node
+ * U - node that will take place of deleting one
+ */
+
+class RBTreeTest {
+ private lateinit var rbTree: RBTree
+ private lateinit var inorderedTraversalTree: MutableList>
+
+ @BeforeEach
+ fun setup() {
+ rbTree = RBTree()
+ inorderedTraversalTree = mutableListOf()
+ }
+
+ @Nested
+ inner class InsertTests {
+
+ @Test
+ fun `insert if the node with this key doesn't exist`() {
+ rbTree.insert(15, "i")
+ rbTree.insert(10, "am")
+ rbTree.insert(5, "really")
+ val returnValue = rbTree.insert(7, "wordless")
+ assertEquals(null, returnValue)
+ }
+
+ @Test
+ fun `insert if the node with this key exists`() {
+ rbTree.insert(15, "i")
+ rbTree.insert(10, "am")
+ rbTree.insert(5, "really")
+ val returnValue = rbTree.insert(5, "wordless")
+ assertEquals("really", returnValue)
+ }
+
+ @Test
+ fun `insert left child`() {
+ rbTree.insert(17, "legend")
+ rbTree.insert(27, "club")
+ rbTree.insert(49, "poliklinika")
+ val returnValue = rbTree.insert(16, "18+")
+ assertEquals(null, returnValue)
+ assertEquals(16, rbTree.root?.leftChild?.leftChild?.key)
+ }
+
+ @Test
+ fun `insert right child`() {
+ rbTree.insert(17, "legend")
+ rbTree.insert(27, "club")
+ rbTree.insert(49, "poliklinika")
+ val returnValue = rbTree.insert(20, "wordless")
+ assertEquals(null, returnValue)
+ assertEquals(20, rbTree.root?.leftChild?.rightChild?.key)
+ }
+
+ @Test
+ fun `balance negative numbers`() {
+ val rbTree = RBTree()
+ rbTree.insert(-1, "a")
+ rbTree.insert(1, "a")
+ rbTree.insert(0, "a")
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(listOf(Pair(-1, "a"), Pair(0, "a"), Pair(1, "a")), inorderedTraversalTree)
+ }
+ }
+
+ @Nested
+ inner class DeleteTests {
+
+ @Test
+ fun `successful delete (return value check)`() {
+ rbTree.insert(30, "pink")
+ rbTree.insert(20, "white")
+ rbTree.insert(40, "red")
+
+ val returnValue = rbTree.delete(40)
+ assertEquals("red", returnValue)
+ }
+
+ @Test
+ fun `unsuccessful delete (return value check)`() {
+ rbTree.insert(30, "pink")
+ rbTree.insert(20, "white")
+ rbTree.insert(40, "red")
+
+ val returnValue = rbTree.delete(50)
+ assertEquals(null, returnValue)
+ }
+
+ @Test
+ fun `delete if sibling and its left-child are black`() {
+ rbTree.insert(30, "pink")
+ rbTree.insert(20, "white")
+ rbTree.insert(40, "red")
+ rbTree.insert(50, "yellow")
+
+ val returnValue = rbTree.delete(20)
+ assertEquals("white", returnValue)
+ }
+
+ @Test
+ fun `delete right-right case`() {
+ // s is right child of its parent and both children of s are red
+ rbTree.insert(30, "pink")
+ rbTree.insert(18, "white")
+ rbTree.insert(40, "red")
+ rbTree.insert(45, "yellow")
+
+ val returnValue = rbTree.delete(40)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals("red", returnValue)
+ assertEquals(
+ listOf(Pair(18, "white"), Pair(30, "pink"), Pair(45, "yellow")),
+ inorderedTraversalTree
+ )
+ }
+
+ @Test
+ fun `delete left-left case`() {
+ // s is left child of its parent and both children of s are red
+ rbTree.insert(30, "pink")
+ rbTree.insert(40, "white")
+ rbTree.insert(18, "red")
+ rbTree.insert(10, "yellow")
+
+ val returnValue = rbTree.delete(18)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(10, "yellow"), Pair(30, "pink"), Pair(40, "white")),
+ inorderedTraversalTree
+ )
+ assertEquals("red", returnValue)
+ }
+
+ @Test
+ fun `delete right-left case`() {
+ // s is right child of its parent and r is left child of s
+ rbTree.insert(30, "pink")
+ rbTree.insert(40, "white")
+ rbTree.insert(18, "red")
+ rbTree.insert(35, "yellow")
+
+ val returnValue = rbTree.delete(40)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(18, "red"), Pair(30, "pink"), Pair(35, "yellow")),
+ inorderedTraversalTree
+ )
+ assertEquals("white", returnValue)
+ }
+
+ @Test
+ fun `delete left-right case`() {
+ // s is left child of its parent and r is right child
+ rbTree.insert(30, "pink")
+ rbTree.insert(40, "white")
+ rbTree.insert(18, "red")
+ rbTree.insert(20, "yellow")
+
+ val returnValue = rbTree.delete(18)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(20, "yellow"), Pair(30, "pink"), Pair(40, "white")),
+ inorderedTraversalTree
+ )
+ assertEquals("red", returnValue)
+ }
+
+ @Test
+ fun `delete left-red sibling`() {
+ rbTree.insert(20, "pink")
+ rbTree.insert(30, "white")
+ rbTree.insert(10, "red")
+ rbTree.insert(25, "brown")
+ rbTree.insert(35, "gold")
+
+ val returnValue = rbTree.delete(10)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(20, "pink"), Pair(25, "brown"), Pair(30, "white"), Pair(35, "gold")),
+ inorderedTraversalTree
+ )
+ assertEquals("red", returnValue)
+ }
+
+ @Test
+ fun `delete root - sole node in tree`() {
+ rbTree.insert(10, "hihi haha")
+ val returnValue = rbTree.delete(10)
+ assertEquals("hihi haha", returnValue)
+ }
+
+ @Test
+ fun `delete node with two children`() {
+ rbTree.insert(20, "pink")
+ rbTree.insert(30, "white")
+ rbTree.insert(10, "red")
+ rbTree.insert(25, "brown")
+ rbTree.insert(35, "gold")
+
+ val returnValue = rbTree.delete(30)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(10, "red"), Pair(20, "pink"), Pair(25, "brown"), Pair(35, "gold")),
+ inorderedTraversalTree
+ )
+ assertEquals("white", returnValue)
+ }
+
+ @Test
+ fun `delete lonely black node with red sibling`() {
+ rbTree.insert(20, "hi")
+ rbTree.insert(10, "hihi")
+ rbTree.insert(40, "what")
+ rbTree.insert(30, "omg")
+ rbTree.insert(45, "damn")
+ rbTree.insert(33, "!")
+
+ val returnValue = rbTree.delete(10)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(
+ Pair(20, "hi"),
+ Pair(30, "omg"),
+ Pair(33, "!"),
+ Pair(40, "what"),
+ Pair(45, "damn")
+ ),
+ inorderedTraversalTree
+ )
+ assertEquals("hihi", returnValue)
+ }
+
+ @Test
+ fun `delete left-black node with red-right child`() {
+ rbTree.insert(100, "huh")
+ rbTree.insert(80, "?")
+ rbTree.insert(200, "you")
+ rbTree.insert(60, "meow")
+ rbTree.insert(90, ".")
+ rbTree.insert(88, "cat")
+
+ val returnValue = rbTree.delete(60)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(
+ Pair(80, "?"),
+ Pair(88, "cat"),
+ Pair(90, "."),
+ Pair(100, "huh"),
+ Pair(200, "you")
+ ),
+ inorderedTraversalTree
+ )
+ assertEquals("meow", returnValue)
+ }
+
+ @Test
+ fun `delete black node with zero children and black sibling`() {
+ rbTree.insert(110, "walking")
+ rbTree.insert(238, "in")
+ rbTree.insert(88, "of")
+ rbTree.insert(233, "phone")
+
+ val returnValue = rbTree.delete(233)
+ rbTree.delete(88)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(listOf(Pair(110, "walking"), Pair(238, "in")), inorderedTraversalTree)
+ assertEquals("phone", returnValue)
+ }
+
+ @Test
+ fun `delete left-red node with no children`() {
+ val rbTree = RBTree()
+ rbTree.insert(6, "monkey")
+ rbTree.insert(4, "you")
+ rbTree.insert(7, "dog")
+ rbTree.insert(2, "cat")
+ rbTree.insert(5, "dog")
+ rbTree.insert(1, "cat")
+
+ val returnValue = rbTree.delete(7)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(
+ Pair(1, "cat"),
+ Pair(2, "cat"),
+ Pair(4, "you"),
+ Pair(5, "dog"),
+ Pair(6, "monkey")
+ ),
+ inorderedTraversalTree
+ )
+ assertEquals("dog", returnValue)
+ }
+
+ @Test
+ fun `delete black node with two children (left subtree & right leaf)`() {
+ val rbTree = RBTree()
+ rbTree.insert(200, "Im")
+ rbTree.insert(160, "gonna make")
+ rbTree.insert(400, "him an")
+ rbTree.insert(120, "offer")
+ rbTree.insert(180, "he")
+ rbTree.insert(130, "cant refuse")
+
+ val returnValue = rbTree.delete(200)
+ val inorderedTraversalTree: MutableList> = mutableListOf()
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(
+ Pair(120, "offer"),
+ Pair(130, "cant refuse"),
+ Pair(160, "gonna make"),
+ Pair(180, "he"),
+ Pair(400, "him an")
+ ),
+ inorderedTraversalTree
+ )
+ assertEquals("Im", returnValue)
+ }
+
+ @Test
+ fun `fix after deletion case 3 right child red`() {
+ rbTree.insert(15, "C")
+ rbTree.insert(10, "A")
+ rbTree.insert(20, "E")
+ rbTree.insert(22, "F")
+
+ // for case 3 - insert sibling and ensure its child is red
+ rbTree.insert(23, "K")
+
+ // deletion to trigger fixAfterDeletion with Case 3 conditions
+ rbTree.delete(10)
+
+ val currentSibling = rbTree.root?.rightChild
+ assertEquals(null, currentSibling?.rightChild?.color)
+ assertEquals(RBNode.Color.Black, currentSibling?.color)
+ }
+
+ @Test
+ fun `fix after deletion case 3 left child red`() {
+ rbTree.insert(15, "C")
+ rbTree.insert(10, "A")
+ rbTree.insert(20, "E")
+ rbTree.insert(18, "D")
+
+ // for case 3 - insert sibling and ensure its child is red
+ rbTree.insert(17, "Z")
+ // deletion to trigger fixAfterDeletion with Case 3 conditions
+ rbTree.delete(10)
+
+ val currentSibling = rbTree.root?.rightChild
+ assertEquals(RBNode.Color.Black, currentSibling?.color)
+ }
+
+ @Test
+ fun `fix after deletion fourth case`() {
+ rbTree.insert(15, "c")
+ rbTree.insert(10, "a")
+ rbTree.insert(20, "e")
+ rbTree.insert(5, "b")
+ rbTree.insert(4, "f")
+
+ val returnValue = rbTree.delete(4)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(5, "b"), Pair(10, "a"), Pair(15, "c"), Pair(20, "e")),
+ inorderedTraversalTree
+ )
+ assertEquals("f", returnValue)
+ }
+
+ @Test
+ fun `delete right-red sibling`() {
+ rbTree.insert(20, "pink")
+ rbTree.insert(30, "white")
+ rbTree.insert(15, "red")
+ rbTree.insert(10, "brown")
+ rbTree.insert(5, "gold")
+
+ val returnValue = rbTree.delete(30)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(5, "gold"), Pair(10, "brown"), Pair(15, "red"), Pair(20, "pink")),
+ inorderedTraversalTree
+ )
+ assertEquals("white", returnValue)
+ }
+
+ @Test
+ fun `root left rotate`() {
+ rbTree.insert(20, "pink")
+ rbTree.insert(30, "white")
+ rbTree.insert(15, "red")
+ rbTree.insert(10, "brown")
+ rbTree.insert(5, "gold")
+
+ val returnValue = rbTree.delete(20)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(5, "gold"), Pair(10, "brown"), Pair(15, "red"), Pair(30, "white")),
+ inorderedTraversalTree
+ )
+ assertEquals("pink", returnValue)
+ }
+
+ @Test
+ fun `root right rotate`() {
+ rbTree.insert(20, "pink")
+ rbTree.insert(30, "white")
+ rbTree.insert(15, "red")
+ rbTree.insert(10, "brown")
+ rbTree.insert(5, "gold")
+
+ val returnValue = rbTree.delete(30)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(
+ listOf(Pair(5, "gold"), Pair(10, "brown"), Pair(15, "red"), Pair(20, "pink")),
+ inorderedTraversalTree
+ )
+ assertEquals("white", returnValue)
+ }
+
+ @Test
+ fun `zero root deletion`() {
+ rbTree.insert(0, "a")
+
+ val returnValue = rbTree.delete(0)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(listOf(), inorderedTraversalTree)
+ assertEquals("a", returnValue)
+ }
+
+ @Test
+ fun `duplicates test`() {
+ rbTree.insert(-1, "a")
+ rbTree.insert(1, "a")
+ rbTree.insert(0, "a")
+ rbTree.insert(0, "b")
+
+ val returnValue = rbTree.delete(0)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(listOf(Pair(-1, "a"), Pair(1, "a")), inorderedTraversalTree)
+ assertEquals("b", returnValue)
+ }
+
+ @Test
+ fun `zero delete case`() {
+ rbTree.insert(-1, "a")
+ rbTree.insert(1, "a")
+
+ val returnValue = rbTree.delete(0)
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(listOf(Pair(-1, "a"), Pair(1, "a")), inorderedTraversalTree)
+ assertEquals(null, returnValue)
+ }
+
+ @Test
+ fun `random tests`() {
+ val generator = Random(12)
+
+ val randomKeys = mutableListOf()
+ for (i in 1..70) {
+ val randomValue = generator.nextInt()
+ randomKeys.add(randomValue)
+ rbTree.insert(randomValue, "") // this sets up key and default value
+ }
+
+ val len = randomKeys.size
+ for (index in 1..len) {
+ val value: Int = randomKeys.removeLast()
+ rbTree.delete(value)
+ }
+
+ for ((key, value) in rbTree) {
+ inorderedTraversalTree += Pair(key, value)
+ }
+ assertEquals(listOf(), inorderedTraversalTree)
+ }
+ }
+
+ @Nested
+ inner class AuxiliaryTests {
+ @Test
+ fun `insert and balance, color check 1`() {
+ rbTree.insert(10, "A")
+ rbTree.insert(5, "B")
+ rbTree.insert(15, "C")
+ rbTree.insert(3, "D")
+ rbTree.insert(7, "E")
+
+ assertEquals(RBNode.Color.Black, rbTree.root?.color)
+ assertEquals(RBNode.Color.Black, rbTree.root?.leftChild?.color)
+ assertEquals(RBNode.Color.Black, rbTree.root?.rightChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.leftChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.rightChild?.color)
+ }
+
+ @Test
+ fun `insert and balance, color check 2`() {
+ rbTree.insert(10, "A")
+ rbTree.insert(5, "B")
+ rbTree.insert(15, "C")
+ rbTree.insert(3, "D")
+ rbTree.insert(7, "E")
+ rbTree.insert(13, "F")
+ rbTree.insert(17, "G")
+ rbTree.insert(2, "H")
+ rbTree.insert(4, "I")
+ rbTree.insert(6, "J")
+ rbTree.insert(8, "K")
+ rbTree.insert(14, "L")
+ rbTree.insert(16, "M")
+ rbTree.insert(18, "N")
+
+ assertEquals(RBNode.Color.Black, rbTree.root?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.rightChild?.color)
+ assertEquals(RBNode.Color.Black, rbTree.root?.leftChild?.leftChild?.color)
+ assertEquals(RBNode.Color.Black, rbTree.root?.leftChild?.rightChild?.color)
+ assertEquals(RBNode.Color.Black, rbTree.root?.rightChild?.leftChild?.color)
+ assertEquals(RBNode.Color.Black, rbTree.root?.rightChild?.rightChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.leftChild?.leftChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.leftChild?.rightChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.rightChild?.leftChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.leftChild?.rightChild?.rightChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.rightChild?.leftChild?.rightChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.rightChild?.rightChild?.leftChild?.color)
+ assertEquals(RBNode.Color.Red, rbTree.root?.rightChild?.rightChild?.rightChild?.color)
+ }
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..fa97dad
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,19 @@
+pluginManagement {
+ plugins {
+ kotlin("jvm") version "1.9.23"
+ }
+}
+/*
+ * 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.
+ */
+
+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 = "TSL"
+include("lib")