diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..543f93e --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,34 @@ +name: Measure coverage + +on: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout sources + uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Run Test + run: | + chmod +x gradlew + ./gradlew test + - name: JaCoCO Coverage + id: jacoco + uses: Madrapps/jacoco-report@v1.6.1 + with: + paths: | + ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + skip-if-no-changes: true + + diff --git a/.github/workflows/format_check.yml b/.github/workflows/format_check.yml new file mode 100644 index 0000000..fcc613e --- /dev/null +++ b/.github/workflows/format_check.yml @@ -0,0 +1,11 @@ +name: Run detekt - static code analyzer +on: + push: +jobs: + detekt: + runs-on: ubuntu-latest + steps: + - name: "checkout" + uses: actions/checkout@v2 + - name: "detekt" + uses: natiginfo/action-detekt-all@1.17.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..76034f9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Test + +on: + push: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Setup JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Build with Gradle + run: ./gradlew build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3353073 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +### Gradle ### +.gradle +build +build.gradle.kts +gradle.properties + +### IntelliJ IDEA ### +.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d2a9f2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Magomedov Islam, Damir Yunusov, Sofya Grishkova + +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..3f2ae74 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) [![CodeFactor](https://www.codefactor.io/repository/github/spbu-coding-2023/trees-4/badge)](https://www.codefactor.io/repository/github/spbu-coding-2023/trees-4) + +## About Tree Lib + +Tree Lib is the kotlin library for working with 3 different binary tree implementations: Binary Search Tree (BST), +AVL-Tree (AVL) and Red-Black Tree (RB). + +## How to use + +To init an object, you must provide key and value type. + +``` +val treeExample: BSTree<, > +``` + +for example + +``` +val treeExample = BSTree() +``` + +All trees supports basic methods: + +- Add values and keys as nodes to the tree: + + `treeExample.add(key: "abc", value: 1.0)` + +- Change node's value: + + `treeExample.changeVal(key: "abc", value: 37.7)` + +- Remove node from the tree: + + `treeExample.remove(key: "Choose wisely.")` + +- Find node by key: + + `treeExample.findByKey(key: "Anything you can compare!")` + +- Iterate in tree: + + `for (node in treeExample) { ...` + +- ...and much more! + +With binary trees, Tree Lib provides a node type for each of them. +You can read node's key and value(with key() and value() methods), compare +them between each other(although they must share same type), convert them to +pair(toPair()) or string(toString()). + +## License + +Distributed under the MIT License. See `LICENSE.txt` for more information. + +## Contact + +This library was created by: + +* Magomedov Islam (tg @JapEmpLove) - BST +* Damir Yunusov (tg @TopographicOcean) - RB +* Sofya Grishkova (tg @tea_jack) - AVL + +## Acknowledgments + +[README Template](https://github.com/othneildrew/Best-README-Template) + +Links to Wikipages about binary trees: + +* [Binary Tree](https://en.wikipedia.org/wiki/Binary_tree) +* [Binary Search Tree](https://en.wikipedia.org/wiki/Binary_search_tree) +* [Red-Black Tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) +* [AVL-Tree](https://en.wikipedia.org/wiki/AVL_tree) + +[Here](https://www.geeksforgeeks.org/applications-advantages-and-disadvantages-of-binary-search-tree/) are provided +applications, advantages and disadvantages of Binary Search Tree + +Good [Habr article](https://habr.com/ru/articles/150732/) about AVL-Trees diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..b0eff4b --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + kotlin("jvm") version "1.9.23" + jacoco +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") +} + +tasks.test { + useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) +} +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required = true + csv.required = false + html.required = false + html.outputLocation = layout.buildDirectory.dir("jacoco") + } +} + +kotlin { + jvmToolchain(21) +} + +jacoco { + toolVersion = "0.8.11" + reportsDirectory = layout.buildDirectory.dir("reports/jacoco") +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4e88dbd --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Mar 23 10:46:13 MSK 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..a9dd0c9 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "trees-4" + diff --git a/src/main/kotlin/treeLib/bintrees/AVLTree.kt b/src/main/kotlin/treeLib/bintrees/AVLTree.kt new file mode 100644 index 0000000..80af9ee --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/AVLTree.kt @@ -0,0 +1,193 @@ +package treeLib.bintrees + +import treeLib.bintrees.interfaces.BalancedTree +import treeLib.nodes.AVLNode + +class AVLTree, V> : BalancedTree>() { + override var root: AVLNode? = null + override var amountOfNodes = 0 + + override fun remove(key: K): V? { + var value: V? = null + + fun removeRec(node: AVLNode?, key: K): AVLNode? { + if (node == null) return null + + if (key < node.key) { + node.left = removeRec(node.left, key) + } else if (key > node.key) { + node.right = removeRec(node.right, key) + } else { + val nodeLeft = node.left + val nodeRight = node.right + + value = node.value + + if (nodeRight == null) { + return nodeLeft + } + + val min = findMin(nodeRight) + + min?.right = removeMin(nodeRight) + min?.left = nodeLeft + + if (root?.key == key) { + root = min + } + + return balanceNode(min) + } + + return balanceNode(node) + } + + removeRec(root, key) + amountOfNodes -= 1 + + return value + } + + private fun findMin(node: AVLNode?): AVLNode? { + if (node?.left != null) { + return findMin(node.left) + } + + return node + } + + private fun removeMin(node: AVLNode?): AVLNode? { + if (node?.left == null) { + return node?.right + } + + node.left = removeMin(node.left) + + return balanceNode(node) + } + + override fun add(key: K, value: V): AVLNode? { + fun addRec(node: AVLNode?, key: K, value: V): AVLNode? { + if (node == null) { + return AVLNode(key, value) + } + + if (key < node.key) { + node.left = addRec(node.left, key, value) + } else if (key > node.key) { + node.right = addRec(node.right, key, value) + } + + return balanceNode(node) + } + + if (root == null) { + root = AVLNode(key, value) + amountOfNodes += 1 + return root + } + + amountOfNodes += 1 + + return addRec(root, key, value) + } + + private fun rotateLeft(nodeA: AVLNode?): AVLNode? { + if (nodeA != null) { + if (nodeA.right != null) { + val nodeB = nodeA.right + + nodeA.right = nodeB?.left + nodeB?.left = nodeA + + fixHeight(nodeA) + fixHeight(nodeB) + + return nodeB + } else { + throw NullPointerException("Right child node cannot be null.") + } + } else { + throw NullPointerException("Node cannot be null.") + } + } + + private fun rotateRight(nodeA: AVLNode?): AVLNode? { + if (nodeA != null) { + if (nodeA.left != null) { + val nodeB = nodeA.left + + nodeA.left = nodeB?.right + nodeB?.right = nodeA + + fixHeight(nodeA) + fixHeight(nodeB) + + return nodeB + } else { + throw NullPointerException("Left child node cannot be null.") + } + } else { + throw NullPointerException("Node cannot be null.") + } + } + + private fun height(node: AVLNode?): Int { + return node?.height ?: 0 + } + + private fun balanceFactor(node: AVLNode?): Int { + if (node == null) { + throw NullPointerException("Node cannot be null.") + } + + return height(node.right) - height(node.left) + } + + private fun fixHeight(node: AVLNode?) { + if (node != null) { + val heightLeft = height(node.left) + val heightRight = height(node.right) + + if (heightLeft > heightRight) { + node.height = heightLeft + 1 + } else { + node.height = heightRight + 1 + } + } + } + + private fun balanceNode(node: AVLNode?): AVLNode? { + fixHeight(node) + + if (balanceFactor(node) == 2 && node != null) { + if (balanceFactor(node.right) < 0) { + node.right = rotateRight(node.right) + } + + if (node == root) { + root = rotateLeft(node) + return root + } + + return rotateLeft(node) + } + if (balanceFactor(node) == -2 && node != null) { + if (balanceFactor(node.left) > 0) { + node.left = rotateLeft(node.left) + } + + if (node == root) { + root = rotateRight(node) + return root + } + + return rotateRight(node) + } + return node + } + + override fun height(): Int { + return root?.height ?: 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/treeLib/bintrees/BSTree.kt b/src/main/kotlin/treeLib/bintrees/BSTree.kt new file mode 100644 index 0000000..b7d1775 --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/BSTree.kt @@ -0,0 +1,183 @@ +package treeLib.bintrees + +import treeLib.bintrees.interfaces.BinTree +import treeLib.nodes.BSTNode + +class BSTree, V> : BinTree>() { + override var root: BSTNode? = null + override var amountOfNodes = 0 + + fun addPairs(vararg pairs: Pair): Boolean { + var flag = true + + for (pair in pairs) { + if (this.add(pair.first, pair.second) == null) flag = false + } + + return flag + } + + fun removePairs(vararg keys: K): Boolean { + var flag = true + + for (key in keys) { + if (this.remove(key) == null) flag = false + } + + return flag + } + + override fun add(key: K, value: V): BSTNode? { + if (root == null) { + this.root = BSTNode(key, value) + this.amountOfNodes += 1 + return root + } + + var curNode = this.root + + while (curNode != null && curNode.key != key) { + curNode = if (key > curNode.key) { + if (curNode.right == null) { + curNode.right = BSTNode(key, value) + this.amountOfNodes += 1 + + return curNode.right + } + + curNode.right + } + + else{ + if (curNode.left == null) { + curNode.left = BSTNode(key, value) + this.amountOfNodes += 1 + + return curNode.left + } + + curNode.left + } + } + return null + } + + override fun remove(key: K): V? { + var count = 0 + val curNode: BSTNode? + var parent = this.findParent(key) + + if (this.root?.key == key) parent = root?.let { BSTNode(it.key, it.value, it) } + if (parent == null) return null + + curNode = if (parent.right != null && parent.right?.key == key) parent.right + else parent.left + + // Hier wir count amount of children + if (curNode?.right != null) count++ + if (curNode?.left != null) count++ + + //We have no children + if (count == 0) { + + if (parent.right == curNode) { + if (curNode == root) this.root = null + else parent.right = null + } + + else parent.left = null + } + + //We've got ein child + else if (count == 1) { + if (curNode?.left == null) { + if (parent.right == curNode) { + if (curNode == root) this.root = root?.right + else parent.right = curNode?.right + } else parent.left = curNode?.right + } + + else { + if (parent.right == curNode) { + if (curNode == root) this.root = this.root?.left + else parent.right = curNode.left + } else parent.left = curNode.left + } + } + + //Wir haben zwei kinder + else { + var child: BSTNode? = curNode?.right + var parent_child = curNode + + while (child?.left != null) { + if (child.left?.left == null) parent_child = child + child = child.left + } + + if(child == null) return null + curNode?.key = child.key + curNode?.value = child.value + + if (curNode?.right != child) parent_child?.left = child.right + else curNode.right = child.right + } + + this.amountOfNodes -= 1 + return curNode?.value + } + + + internal fun findParent(key: K): BSTNode? { + var parent = this.root + if (root?.key == key) return null + + while (parent != null && (parent.right != null || parent.left != null)) { + if (parent.right != null && parent.right?.key == key || parent.left != null && parent.left?.key == key) return parent + + parent = if (key > parent.key) parent.right + else parent.left + } + + return null + } + + + fun deleteSubTree(key: K): Boolean { + val parent: BSTNode = this.findParent(key) ?: return false + + if (parent.right != null && parent.right?.key == key) parent.right = null + else parent.left = null + + var nodes = 0 + for (i in this) nodes++ + amountOfNodes = nodes + + return true + } + + + fun getSubTree(key: K): BSTree? { + val parent: BSTNode = this.findParent(key) ?: return null + val childTree: BSTree = BSTree() + val child: BSTNode? + + if (parent.right != null && parent.right?.key == key) { + child = childTree.add(key, parent.right?.value ?: return null) + child?.right = parent.right?.right + child?.left = parent.right?.left + } + + else { + child = parent.left?.let { childTree.add(key, it.value)} + child?.right = parent.left?.right + child?.left = parent.left?.left + } + + var nodes = 0 + for (i in childTree) nodes++ + childTree.amountOfNodes = nodes + + return childTree + } +} diff --git a/src/main/kotlin/treeLib/bintrees/RBTree.kt b/src/main/kotlin/treeLib/bintrees/RBTree.kt new file mode 100644 index 0000000..58ce4dd --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/RBTree.kt @@ -0,0 +1,227 @@ +package treeLib.bintrees + +import treeLib.bintrees.interfaces.BalancedTree +import treeLib.nodes.RBNode + +/** + * Class which implements... Red-Black Tree :O + * Takes two types: for key(K) and for value(V) + */ +class RBTree, V> : BalancedTree>() { + override var root: RBNode? = null + override var amountOfNodes: Int = 0 + + /** + * Adds or replaces node to the tree depending on given key: + * 1. Adds node to the tree and returns node, + * 2. If node with given key already exist, it does nothing and returns null + * (to actually change value use changeVal) + */ + override fun add(key: K, value: V): RBNode? { + val treeBranch = ArrayDeque>() //to know who's the parent + val newNode = RBNode(key, value) + + if (root == null) { + root = newNode + } else { + var curNode = root + + while (curNode != null) { + treeBranch.addFirst(curNode) + val nextNode = curNode.moveOn(key) + if (nextNode == curNode) return null //node with given key already exist + curNode = nextNode + } + + val parent = treeBranch.first() + parent.attach(newNode) + } + + amountOfNodes++ + treeBranch.addFirst(newNode) + balancedAdd(treeBranch) + return newNode + } + + /** + * Used in add() method to balance tree :) + */ + private fun balancedAdd(treeBranch: ArrayDeque>) { + var son = treeBranch.removeFirst() + var parent = treeBranch.removeFirstOrNull() + var grandparent = treeBranch.removeFirstOrNull() + + while (parent != null && parent.isRed && grandparent != null) { + val uncle = if (parent == grandparent.right) grandparent.left else grandparent.right + + //uncle is red -> recolor nodes and go higher up the treeBranch + if (uncle?.isRed == true) { + parent.isRed = false + uncle.isRed = false + grandparent.isRed = true + son = grandparent + parent = treeBranch.removeFirstOrNull() + grandparent = treeBranch.removeFirstOrNull() + continue + } + + //uncle is black or null -> do few rotations, recoloring and magic will happen + if (parent == grandparent.right) { + if (son == parent.left) { //we want co-directional parent and son + rotateRight(parent, grandparent) + parent = son + } + + parent.isRed = false + grandparent.isRed = true + rotateLeft(grandparent, treeBranch.firstOrNull()) + } else /*parent == grandparent.left*/ { + if (son == parent.right) { //we want co-directional parent and son too + rotateLeft(parent, grandparent) + parent = son + } + + parent.isRed = false + grandparent.isRed = true + rotateRight(grandparent, treeBranch.firstOrNull()) + } + } + + root?.isRed = false + } + + /** + * Removes node with the same key and returns node's value. + * Or does nothing and returns null if there's no such node. + */ + override fun remove(key: K): V? { + val treeBranch = ArrayDeque?>() //to know who's the parent + var curNode = root ?: return null + + while (curNode.key != key) { + treeBranch.addFirst(curNode) + val nextNode = curNode.moveOn(key) ?: return null //nextNode == null => removing node doesn't exist + curNode = nextNode + } + + var parent = treeBranch.firstOrNull() + val sonRight = curNode.right + val sonLeft = curNode.left + val sonOfRemovedNode = when { + sonRight != null && sonLeft != null -> { + treeBranch.addFirst(curNode) + val copyTo = curNode //Will copy from curNode + curNode = sonLeft + + while (curNode.right != null) { + treeBranch.addFirst(curNode) + curNode = curNode.right ?: throw NullPointerException("Removing node cannot be null.") + } + + parent = treeBranch.first() + val temp = copyTo.value + copyTo.key = curNode.key + copyTo.value = curNode.value + curNode.value = temp //to save deleted node's value + curNode.left + } + sonRight != null -> sonRight + else /*sonLeft != null*/ -> sonLeft + } + + amountOfNodes-- + when (curNode) { //here we are "deleting" the node and replace it with his son + parent?.right -> parent.right = sonOfRemovedNode + parent?.left -> parent.left = sonOfRemovedNode + else -> root = sonOfRemovedNode //only root doesn't have parent + } + + treeBranch.addFirst(sonOfRemovedNode) + if (!curNode.isRed) balancedRemove(treeBranch) + return curNode.value + } + + /** + * Used in remove() method to balance tree :( + */ + private fun balancedRemove(treeBranch: ArrayDeque?>) { + var son = treeBranch.removeFirstOrNull() + var parent = treeBranch.removeFirstOrNull() + var grandparent = treeBranch.removeFirstOrNull() + + //son == son of removed node + if (son?.isRed == true) { + son.isRed = false + return + } + + while (parent != null) { + var brother = if (son == parent.right) parent.left else parent.right + + //we want for brother to be black (´。_。`) + if (brother?.isRed == true) { + brother.isRed = false + parent.isRed = true + + if (son == parent.right) { + rotateRight(parent, grandparent) + grandparent = brother + brother = parent.left + } else /*son == parent.left*/ { + rotateLeft(parent, grandparent) + grandparent = brother + brother = parent.right + } + } + + brother ?: throw IllegalStateException("Balance of RB tree violated: different black height.") + var nephewRight = brother.right + var nephewLeft = brother.left + + //depending on nephew's colors, doing magic for balancing purposes + if (nephewRight?.isRed != true && nephewLeft?.isRed != true && parent.isRed) { + brother.isRed = true + parent.isRed = false + break + } else if (nephewRight?.isRed != true && nephewLeft?.isRed != true && !parent.isRed) { + brother.isRed = true //parent is black --> go higher up + son = parent //the treeBranch and balance again + parent = grandparent + grandparent = treeBranch.removeFirstOrNull() + continue + } + + if (son == parent.right) { + if (nephewRight?.isRed == true && nephewLeft?.isRed != true) { + rotateLeft(brother, parent) // B /nL == new brother + nephewLeft = brother // / \ -> /B == new left nephew + brother = nephewRight // nL nR /nR + brother.isRed = false + nephewLeft.isRed = true + } + + rotateRight(parent, grandparent) + brother.isRed = parent.isRed + parent.isRed = false + nephewLeft?.isRed = false + } else /*son == parent.left*/ { + if (nephewLeft?.isRed == true && nephewRight?.isRed != true) { + rotateRight(brother, parent) // B \nR == new brother + nephewRight = brother // / \ -> \B == new right nephew + brother = nephewLeft // nL nR \nL + brother.isRed = false + nephewRight.isRed = true + } + + rotateLeft(parent, grandparent) + brother.isRed = parent.isRed + parent.isRed = false + nephewRight?.isRed = false + } + + break + } + + root?.isRed = false + } +} diff --git a/src/main/kotlin/treeLib/bintrees/Treap.kt b/src/main/kotlin/treeLib/bintrees/Treap.kt new file mode 100644 index 0000000..156efa4 --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/Treap.kt @@ -0,0 +1,135 @@ +package treeLib.bintrees + +import treeLib.bintrees.interfaces.BinTree +import treeLib.nodes.TreapNode +import java.util.* + +class Treap, V: Comparable > : BinTree>() { + override var root: TreapNode? = null + override var amountOfNodes = 0 + private var subTree = PriorityQueue>(compareByDescending { it.value }) + + + fun collectNodes(node: TreapNode) { + subTree.add(node) + if(node.right != null) node.right?.let{ collectNodes(it) } + if(node.left != null) node.left?.let{ collectNodes(it) } + } + + fun buildSubTree(): TreapNode? { + val baum = Treap() + for(node in subTree) baum.add(node.key, node.value) + subTree.clear() + return baum.root() + } + + fun isThereSuckKey(key: K): TreapNode? { + var curNode = root + + while(curNode != null){ + curNode.let{ + if(it.key < key) curNode = it.right + else if(it.key > key) curNode = it.left + else return curNode + } + } + + + return curNode + } + + override fun remove(key: K): V? { + val curNode = isThereSuckKey(key) ?: return null + var parent: TreapNode? = null + root?.let { parent = findParent(it, it, curNode) } + + var count = 0 + if(curNode.left != null) count++ + if(curNode.right != null) count++ + + + if(count == 0) { + if(curNode == root) root = null + else if(parent?.right == curNode) parent?.right = null + else parent?.left = null + } + + else if(count == 1) { + if(curNode == root) root = root?.right ?: root?.left + else if(parent?.right == curNode) parent?.right = curNode.right ?: curNode.left + else parent?.left = curNode.right ?: curNode.left + } + + else if(count == 2) { + curNode.right?.let { collectNodes(it) } + curNode.left?.let { collectNodes(it) } + val result = buildSubTree() + if(curNode == root) root = result + else if(parent?.right == curNode) parent?.right = result + else parent?.left = result + } + + + return null //Why return anything else? + } + + override fun add(key: K, priority: V): TreapNode? { + if(isThereSuckKey(key) != null) return null + val newNode = TreapNode(key, priority) + + if(root == null) { + root = newNode + amountOfNodes += 1 + + return root + } + + var parent: TreapNode? = null + root?.let { parent = findParent(it, it, newNode) } + + parent?.let{ + if(it.key < key) { + if(it.right == null) it.right = newNode + else { + newNode.right = it.right + collectNodes(newNode) + it.right = buildSubTree() + } + } + + else if(it.key > key)( + if(it.left == null) it.left = newNode + else{ + newNode.left = it.left + collectNodes(newNode) + it.left = buildSubTree() + } + ) + + else { + newNode.right = root + collectNodes(newNode) + root = buildSubTree() + } + + amountOfNodes += 1 + + return newNode + } + + + return root + } + + + fun findParent(curNode: TreapNode, parent: TreapNode, node: TreapNode): TreapNode{ + if(curNode.value <= node.value) { + if(curNode == parent && node != root) return node + return parent + } + + if(curNode.key > node.key) return findParent(curNode.left ?: return curNode, curNode, node) + else if(curNode.key < node.key) return findParent(curNode.right ?: return parent, curNode, node) + else return curNode + } +} \ No newline at end of file diff --git a/src/main/kotlin/treeLib/bintrees/interfaces/BalancedTree.kt b/src/main/kotlin/treeLib/bintrees/interfaces/BalancedTree.kt new file mode 100644 index 0000000..177672e --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/interfaces/BalancedTree.kt @@ -0,0 +1,28 @@ +package treeLib.bintrees.interfaces + +import treeLib.nodes.TreeNode + +abstract class BalancedTree, V, Node_T : TreeNode> : BinTree() { + + //We must provide parent, because don't have link to it in node + protected fun rotateRight(node: Node_T?, parent: Node_T?) { + if (node == null) return + + val nodeLeft = node.left + + node.left = nodeLeft?.right + nodeLeft?.right = node + + if (parent != null) parent.attach(nodeLeft) else root = nodeLeft //root doesn't have parent + } + + protected fun rotateLeft(node: Node_T?, parent: Node_T?) { + if (node == null) return + + val nodeRight = node.right + node.right = nodeRight?.left + nodeRight?.left = node + + if (parent != null) parent.attach(nodeRight) else root = nodeRight //root doesn't have parent + } +} \ No newline at end of file diff --git a/src/main/kotlin/treeLib/bintrees/interfaces/BinTree.kt b/src/main/kotlin/treeLib/bintrees/interfaces/BinTree.kt new file mode 100644 index 0000000..3b81bf4 --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/interfaces/BinTree.kt @@ -0,0 +1,96 @@ +package treeLib.bintrees.interfaces + +import treeLib.nodes.TreeNode + +abstract class BinTree, V, Node_T : TreeNode> : Iterable { + protected abstract var root: Node_T? + protected abstract var amountOfNodes: Int + + abstract fun add(key: K, value: V): Node_T? + + abstract fun remove(key: K): V? + + fun findByKey(key: K): Node_T? { + var curNode = root + + while (curNode != null) + curNode = when { + key > curNode.key -> curNode.right + key < curNode.key -> curNode.left + else -> return curNode + } + + return null + } + + open fun changeVal(key: K, newValue: V): V? { + var curNode = root + + while (curNode != null) + curNode = when { + key > curNode.key -> curNode.right + key < curNode.key -> curNode.left + else -> { + curNode.value = newValue + return newValue + } + } + + return null + } + + fun max(): Node_T? { + var curNode = root + + while (curNode?.right != null) + curNode = curNode.right + + return curNode + } + + fun min(): Node_T? { + var curNode = root + + while (curNode?.left != null) + curNode = curNode.left + + return curNode + } + + open fun root(): Node_T? { + return root + } + + fun clear() { + root = null + amountOfNodes = 0 + } + + fun countNodes(): Int { + return amountOfNodes + } + + /** + * basic In-order iterator + */ + override fun iterator(): Iterator = TreeIterator(root, IterationOrder.IN_ORDER) + + fun preOrderIterator(): Iterator = TreeIterator(root, IterationOrder.PRE_ORDER) + + fun postOrderIterator(): Iterator = TreeIterator(root, IterationOrder.POST_ORDER) + + + private fun countHeight(tNode: Node_T?): Int { + if (tNode == null) return 0 + + val leftChild = countHeight(tNode.left) + val rightChild = countHeight(tNode.right) + + return kotlin.math.max(leftChild, rightChild) + 1 + } + + + open fun height(): Int? { + return countHeight(root) + } +} diff --git a/src/main/kotlin/treeLib/bintrees/interfaces/IterationOrder.kt b/src/main/kotlin/treeLib/bintrees/interfaces/IterationOrder.kt new file mode 100644 index 0000000..9d8b151 --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/interfaces/IterationOrder.kt @@ -0,0 +1,5 @@ +package treeLib.bintrees.interfaces + +enum class IterationOrder { + IN_ORDER, PRE_ORDER, POST_ORDER, +} \ No newline at end of file diff --git a/src/main/kotlin/treeLib/bintrees/interfaces/TreeIterator.kt b/src/main/kotlin/treeLib/bintrees/interfaces/TreeIterator.kt new file mode 100644 index 0000000..40aa11d --- /dev/null +++ b/src/main/kotlin/treeLib/bintrees/interfaces/TreeIterator.kt @@ -0,0 +1,55 @@ +package treeLib.bintrees.interfaces + +import treeLib.nodes.TreeNode + +class TreeIterator, V, Node_T : TreeNode>( + private val root: Node_T?, + private val order: IterationOrder, +) : Iterator { + private val stack: ArrayDeque = ArrayDeque() + + init { + root?.let { treeToStack(root) } + } + + private fun treeToStack(start: Node_T) { + when (order) { + IterationOrder.IN_ORDER -> inOrder(start) + IterationOrder.PRE_ORDER -> preOrder(start) + IterationOrder.POST_ORDER -> postOrder(start) + } + } + + private fun inOrder(curNode: Node_T) { + val leftNode = curNode.left + val rightNode = curNode.right + + leftNode?.let { inOrder(leftNode) } + stack.add(curNode) + rightNode?.let { inOrder(rightNode) } + } + + private fun preOrder(curNode: Node_T) { + val leftNode = curNode.left + val rightNode = curNode.right + + stack.add(curNode) + + leftNode?.let { preOrder(leftNode) } + rightNode?.let { preOrder(rightNode) } + } + + private fun postOrder(curNode: Node_T) { + val leftNode = curNode.left + val rightNode = curNode.right + + leftNode?.let { postOrder(leftNode) } + rightNode?.let { postOrder(rightNode) } + + stack.add(curNode) + } + + override fun hasNext() = stack.isNotEmpty() + + override fun next() = stack.removeFirst() +} \ No newline at end of file diff --git a/src/main/kotlin/treeLib/nodes/AVLNode.kt b/src/main/kotlin/treeLib/nodes/AVLNode.kt new file mode 100644 index 0000000..edff837 --- /dev/null +++ b/src/main/kotlin/treeLib/nodes/AVLNode.kt @@ -0,0 +1,10 @@ +package treeLib.nodes + +class AVLNode, V>( + key: K, + value: V, + right: AVLNode? = null, + left: AVLNode? = null +) : TreeNode>(key, value, right, left) { + internal var height: Int = 1 +} diff --git a/src/main/kotlin/treeLib/nodes/BSTNode.kt b/src/main/kotlin/treeLib/nodes/BSTNode.kt new file mode 100644 index 0000000..0313542 --- /dev/null +++ b/src/main/kotlin/treeLib/nodes/BSTNode.kt @@ -0,0 +1,8 @@ +package treeLib.nodes + +class BSTNode, V>( + key: K, + value: V, + right: BSTNode? = null, + left: BSTNode? = null +) : TreeNode>(key, value, right, left) diff --git a/src/main/kotlin/treeLib/nodes/RBNode.kt b/src/main/kotlin/treeLib/nodes/RBNode.kt new file mode 100644 index 0000000..93c5360 --- /dev/null +++ b/src/main/kotlin/treeLib/nodes/RBNode.kt @@ -0,0 +1,9 @@ +package treeLib.nodes + +class RBNode, V>( + key: K, + value: V, + right: RBNode? = null, + left: RBNode? = null, + internal var isRed: Boolean = true, +) : TreeNode>(key, value, right, left) \ No newline at end of file diff --git a/src/main/kotlin/treeLib/nodes/TreapNode.kt b/src/main/kotlin/treeLib/nodes/TreapNode.kt new file mode 100644 index 0000000..8278d9c --- /dev/null +++ b/src/main/kotlin/treeLib/nodes/TreapNode.kt @@ -0,0 +1,8 @@ +package treeLib.nodes + +class TreapNode, V: Comparable>( + key: K, + value: V, + right: TreapNode? = null, + left: TreapNode? = null +) : TreeNode>(key, value, right, left) \ No newline at end of file diff --git a/src/main/kotlin/treeLib/nodes/TreeNode.kt b/src/main/kotlin/treeLib/nodes/TreeNode.kt new file mode 100644 index 0000000..f940cc9 --- /dev/null +++ b/src/main/kotlin/treeLib/nodes/TreeNode.kt @@ -0,0 +1,43 @@ +package treeLib.nodes + +abstract class TreeNode, V, Node_T : TreeNode>( + internal var key: K, + internal var value: V, + internal var right: Node_T? = null, + internal var left: Node_T? = null, +) : Comparable { + + override fun compareTo(other: Node_T): Int = key.compareTo(other.key) + + override fun hashCode(): Int = Pair(key, value).hashCode() + + override fun equals(other: Any?): Boolean { + return (other as? Node_T != null) && (key == other.key) && (value == other.value) + } + + override fun toString(): String = Pair(key, value).toString() + + fun toPair(): Pair = Pair(key, value) + + fun key(): K = key + + fun value(): V = value + + internal fun attach(node: Node_T?): Boolean { + if (node == null) return false + + when { + this > node -> left = node + this < node -> right = node + else -> return false + } + + return true + } + + internal fun moveOn(otherKey: K): Node_T? = when { + key > otherKey -> left + key < otherKey -> right + else -> this as? Node_T + } +} diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt new file mode 100644 index 0000000..c957045 --- /dev/null +++ b/src/test/kotlin/AVLTreeTest.kt @@ -0,0 +1,220 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import treeLib.bintrees.AVLTree +import treeLib.nodes.AVLNode +import kotlin.math.abs + + +class AVLTreeTest { + var tree = AVLTree() + + @Nested + inner class InsertionTests { + + @BeforeEach + fun setUp() { + tree.add(0, "a") + tree.add(1, "b") + tree.add(-1, "b") + } + + @Test + fun useAddOnEmptyTree() { + tree.clear() + tree.add(0, "a") + assertEquals(0, tree.root()?.key) + assertEquals("a", tree.root()?.value) + } + + @Test + fun addByKey() { + assertEquals(-1, tree.root()?.left?.key) + assertEquals("b", tree.root()?.left?.value) + assertEquals(1, tree.root()?.right?.key) + assertEquals("b", tree.root()?.right?.value) + } + + @Test + fun addDoesNothingIfKeyExists() { + assertEquals(null, tree.root()?.right?.right) + assertEquals(null, tree.root()?.right?.left) + } + } + + @Nested + inner class RemoveTests { + + @BeforeEach + fun setUp() { + for (i in 0..10) { + tree.add(i, "test") + } + } + + @Test + fun removeRoot() { + val key: Int? = tree.root()?.key + if (key != null) { + tree.remove(key) + assertNotNull(tree.root()) + for (i in 0..10) { + if (i == key) { + assertNull(tree.findByKey(key)) + } else { + assertNotNull(tree.findByKey(i)) + } + } + } else { + fail() + } + } + + @Test + fun removeNodeWithNoChildren() { + tree.remove(10) + assertNull(tree.findByKey(10)) + for (i in 0..9) { + assertNotNull(tree.findByKey(i)) + } + } + + @Test + fun removeNodeWithTwoChildren() { + tree.remove(7) + assertNull(tree.findByKey(7)) + for (i in 0..10) { + if (i != 7) { + assertNotNull(tree.findByKey(i)) + } + } + } + + @Test + fun removeNonExistentNode() { + assertNull(tree.remove(999)) + for (i in 0..10) { + assertNotNull(tree.findByKey(i)) + } + } + } + + @Nested + inner class BalanceTests { + private fun isBalanced(node: AVLNode?): Boolean { + fun height(node: AVLNode?): Int { + if (node == null) { + return 0 + } + return node.height + } + + fun balanceFactor(node: AVLNode?): Int { + if (node == null) { + throw NullPointerException("Node cannot be null.") + } + return height(node.right) - height(node.left) + } + + if (node == null) return true + + if (abs(balanceFactor(node)) <= 1 && isBalanced(node.left) && isBalanced(node.right)) { + return true + } + + return false + } + + @BeforeEach + fun setUpRandomTree() { + val ranLen = (2..100).random() + for (i in 1..ranLen) { + val ranItem = (1..10000).random() + tree.add(ranItem, "test") + } + } + + @Test + fun afterInsertAVLIsBalanced() { + assertTrue(isBalanced(tree.root())) + } + + @Test + fun afterRemoveAVLIsBalanced() { + tree.root()?.left?.let { tree.remove(it.key) } + assertTrue(isBalanced(tree.root())) + } + + @Test + fun afterRootRemoveAVLIsBalanced() { + val key: Int? = tree.root()?.key + if (key != null) { + tree.remove(key) + assertTrue(isBalanced(tree.root())) + } + } + } + + @Nested + inner class InterfaceMethods { + + @BeforeEach + fun setUp() { + for (i in 0..10) { + tree.add(i, "test") + } + } + + @Test + fun findReturnsTrueNode() { + val rightNode = tree.findByKey(7) + val leftNode = tree.findByKey(1) + assertEquals(tree.root()?.right, rightNode) + assertEquals(tree.root()?.left, leftNode) + assertNull(tree.findByKey(999)) + } + + @Test + fun changeValChangesVal() { + tree.changeVal(3, "tomato") + tree.changeVal(7, "banana") + tree.changeVal(1, "tea") + + assertEquals("tomato", tree.root()?.value) + assertEquals("banana", tree.root()?.right?.value) + assertEquals("tea", tree.root()?.left?.value) + assertNull(tree.changeVal(99999, "tea")) + } + + @Test + fun findMax() { + assertEquals(tree.findByKey(10), tree.max()) + tree.clear() + assertNull(tree.max()) + } + + @Test + fun findMin() { + assertEquals(tree.findByKey(0), tree.min()) + tree.clear() + assertNull(tree.min()) + } + + @Test + fun countNodes() { + assertEquals(11, tree.countNodes()) + tree.remove(7) + assertEquals(10, tree.countNodes()) + tree.clear() + assertEquals(0, tree.countNodes()) + } + + @Test + fun height() { + assertEquals(4, tree.height()) + tree.clear() + assertEquals(0, tree.height()) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt new file mode 100644 index 0000000..a8260db --- /dev/null +++ b/src/test/kotlin/BSTreeTest.kt @@ -0,0 +1,433 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import treeLib.bintrees.BSTree +import treeLib.nodes.BSTNode + +class BSTreeTest { + var baum = BSTree() + + @Test + fun findParent() { + baum.add(4, "qwerty") + baum.add(2, "Ge") + baum.add(5, "rma") + baum.add(-1, "ny") + baum.add(3, "the") + baum.add(40, "best") + baum.add(44, "Ja") + + assertEquals(BSTNode(5, "rma"), baum.findParent(40)) //Parent with ein Kind + + assertEquals(BSTNode(2, "Ge"), baum.findParent(3)) //Parent with zwei Kinder + + assertEquals(baum.root(), baum.findParent(5)) //When Parent + assertEquals(baum.root(), baum.findParent(2)) //is root + + assertNull(baum.findParent(4)) //Parent where child is root + + assertNull(baum.findParent(34)) //Finding No existing node's parent + } + + + @Nested + inner class TestingAddMethods { + + @Test + fun addToEmptyTree() { + assertEquals(BSTNode(4, "qwerty"), baum.root()) + } + + @BeforeEach + fun setUp() { + baum.add(4, "qwerty") + baum.add(2, "Ge") + baum.add(5, "rma") + baum.add(-1, "ny") + baum.add(3, "the") + baum.add(40, "best") + baum.add(44, "Ja") + } + + @Test + fun checkWaysToNodes() { + val rt = baum.root() + assertEquals(BSTNode(5, "rma"), rt?.right) + assertEquals(BSTNode(3, "the"), rt?.left?.right) + assertEquals(BSTNode(-1, "ny"), rt?.left?.left) + assertEquals(BSTNode(44, "Ja"), rt?.right?.right?.right) + } + + @Test + fun checkAmountsOfNodes() { + assertEquals(7, baum.countNodes()) + assertEquals(BSTNode(10, "Was"), baum.add(10, "Was")) + assertEquals(8, baum.countNodes()) + } + + @Test + fun addPairs() { + baum.addPairs(Pair(1, "ein"), Pair(7, "sieben"), Pair(6, "sechs")) + + assertEquals(10, baum.countNodes()) + assertEquals(1, baum.root()?.left?.left?.right?.key) + assertEquals(6, baum.root()?.right?.right?.left?.left?.key) + } + + @Test + fun addAlreadyExistingNodes() { + assertNull(baum.add(5, "rma")) + assertNull(baum.add(3, "the")) + assertNull(baum.add(40, "best")) + assertNull(baum.add(44, "Ja")) + + assertEquals(7, baum.countNodes()) + } + + @Test + fun addingExistingNodeButWithDifferentValues() { + assertNull(baum.add(5, "Deutschland")) + assertNotEquals("Deutschland", baum.root()?.right?.value) + + assertNull(baum.add(-1, "Deutschland")) + assertNotEquals("Deutschland", baum.root()?.left?.left?.value) + + assertNull(baum.add(4, "Deutschland")) + assertNotEquals("Deutschland", baum.root()?.value) + } + + @Test + fun orderMatters() { + baum.clear() + assertTrue(baum.addPairs(Pair(4, "Vier"), Pair(5, "Funf"), Pair(40, "Vierzig"), Pair(44, "Vierundvierzig"))) + var rt = baum.root() + + assertEquals(5, rt?.right?.key) + assertEquals(44, rt?.right?.right?.right?.key) + + baum.clear() + baum.addPairs(Pair(4, "Vier"), Pair(44, "Vierundvierzig"), Pair(40, "Vierzig"), Pair(5, "Funf")) + rt = baum.root() + + assertEquals(5, rt?.right?.left?.left?.key) + assertEquals(44, rt?.right?.key) + //In other words, with different order we have different ways to nodes + } + } + + @Nested + inner class Remove { + @BeforeEach + fun setUp() { + for (key in listOf(10, 13, 12, 11, 20, 15, 25, -7, 5, -10, 9)) { + baum.add(key, "test") + } + } + + @Test + fun removeNodesWithNoKinder() { + val rt = baum.root() + + assertTrue(baum.removePairs(-10, 11)) + baum.remove(9) + + assertNull(rt?.left?.left) + assertNull(rt?.right?.left?.left) + assertNull(rt?.left?.right?.right) + } + + @Test + fun removeNodesWithEinKind() { + val rt = baum.root() + baum.removePairs(12) + baum.remove(5) + + assertEquals(9, rt?.left?.right?.key) + assertNull(rt?.left?.right?.right) + + assertEquals(11, rt?.right?.left?.key) + assertNull(rt?.right?.left?.left) + + baum.add(12, "test") + baum.remove(11) + assertEquals(BSTNode(12, "test"), rt?.right?.left) + + baum.remove(25) + baum.remove(20) + assertEquals(BSTNode(15, "test"), rt?.right?.right) + } + + @Test + fun removeNodeWithZweiKinderFirstCase() { + //I don't know how to describe any of these cases, just image current tree and I hope you'll get it + val rt = baum.root() + baum.remove(-7) + + assertEquals(5, rt?.left?.key) + assertEquals(-10, rt?.left?.left?.key) + assertEquals(9, rt?.left?.right?.key) + + assertNull(rt?.left?.right?.right) + } + + @Test + fun removeNodeWithZweiKinderSecondCase() { + val rt = baum.root() + assertEquals("test", baum.remove(13)) + + assertEquals(15, rt?.right?.key) + assertEquals(12, rt?.right?.left?.key) + assertEquals(20, rt?.right?.right?.key) + assertEquals(25, rt?.right?.right?.right?.key) + + assertNull(rt?.right?.right?.left) + } + + @Test + fun removeNodeWithZweiKinderThirdCase() { + val rt = baum.root() + + baum.addPairs(Pair(16, "test")) + baum.remove(13) + + assertEquals(16, rt?.right?.right?.left?.key) + } + + @Test + fun removeNodeWithZweiKinderFourthCase() { + val rt = baum.root() + baum.changeVal(25, "Sieg") + baum.remove(20) + + assertNull(rt?.right?.right?.right) + + assertEquals(BSTNode(25, "Sieg"), rt?.right?.right) + assertEquals(BSTNode(15, "test"), rt?.right?.right?.left) + } + + @Test + fun removeNodeWithZweiKinderFifthCase() { + baum.clear() + + baum.addPairs(Pair(-50, "Notf"), Pair(2, "Zwei"), Pair(101, "One hundred and One")) + for (key in listOf(-30, 99, 95, 90, 78, 56, 45, 34, 23, 14, 7, 3, 5, 8)) { + baum.add(key, "test") + } + + val rt = baum.root() + + baum.remove(2) + assertEquals(BSTNode(3, "test"), rt?.right) + + baum.remove(3) + assertEquals(BSTNode(5, "test"), rt?.right) + + val ls = listOf(99, 95, 90, 78, 56, 45, 34, 23, 14, 7, 8, 5).sorted() + for (i in 0..ls.size - 2) { + baum.remove(ls[i]) + assertEquals(BSTNode(ls[i + 1], "test"), rt?.right) + } + + assertNull(rt?.right?.right?.left) + } + + @Test + fun removeNotExistingOderAlreadyRemovedNode() { + assertNull(baum.remove(62)) + assertNull(baum.remove(-24)) + assertFalse(baum.removePairs(-5, 6, 8, 3)) + + assertEquals(11, baum.countNodes()) + + assertEquals("test", baum.remove(13)) + assertNull(baum.remove(13)) + + assertEquals(10, baum.countNodes()) + } + + @Test + fun removeAndChangingAmountOfNode() { + val rt = baum.root() + + assertEquals(11, baum.countNodes()) + + baum.remove(11) + assertEquals(10, baum.countNodes()) + + baum.remove(20) + assertEquals(9, baum.countNodes()) + + baum.remove(5) + assertEquals(8, baum.countNodes()) + + baum.remove(-7) + assertEquals(7, baum.countNodes()) + } + + @Test + fun removeRootWithoutKinder() { + baum.clear() + + baum.add(4, "Heil") + baum.remove(4) + + assertNull(baum.root()) + } + + @Test + fun removeRootWithOnlyRightKinder() { + baum.clear() + + for (key in 0..10) baum.add(key, "test") + + for (i in 0..9) { + baum.remove(i) + assertEquals(BSTNode(i + 1, "test"), baum.root()) + } + + baum.remove(10) + + assertNull(baum.root()) + assertEquals(0, baum.countNodes()) + } + + @Test + fun removeRootWithOnlyLeftKinder() { + baum.clear() + + for (key in 0 downTo -10) baum.add(key, "test") + + for (i in 0 downTo -9) { + baum.remove(i) + assertEquals(BSTNode(i - 1, "test"), baum.root()) + assertEquals(10 + i, baum.countNodes()) + } + + baum.remove(-10) + + assertNull(baum.root()) + assertEquals(0, baum.countNodes()) + } + + @Test + fun removeRootWithTwoKinder() { + baum.remove(10) + assertEquals(BSTNode(11, "test"), baum.root()) + + baum.remove(11) + assertEquals(BSTNode(12, "test"), baum.root()) + + baum.remove(12) + assertEquals(BSTNode(13, "test"), baum.root()) + + baum.remove(13) + assertEquals(BSTNode(15, "test"), baum.root()) + } + + @Test + fun howMuchDoWeNeedToDeleteTheWholeTreeByRemovingEachSingleNode() { + val len = 11 + + for (i in 0..10) { + assertNotNull(baum.root()) + baum.remove(baum.root()?.key!!.toInt()) + } + + assertNull(baum.root()) + assertEquals(0, baum.countNodes()) + } + + } + + + @Nested + inner class SubTree { + @BeforeEach + fun setUp() { + for (key in listOf(10, 13, 12, 11, 20, 15, 25, -7, 5, -10, 9)) { + baum.add(key, "test") + } + } + + @Test + fun deleteTheWholeRightAndLeftSubtee() { + + assertTrue(baum.deleteSubTree(13)) + assertNull(baum.root()?.right) + + assertTrue(baum.deleteSubTree(-7)) + assertNull(baum.root()?.left) + + assertEquals(1, baum.countNodes()) + } + + @Test + fun deleteRootSubTree() { + //You can't delete root in this way + assertFalse(baum.deleteSubTree(10)) + + assertEquals(BSTNode(10, "test"), baum.root()) + assertEquals(11, baum.countNodes()) + } + + @Test + fun deleteSubTreeWithOnlyEinNode() { + val rt = baum.root() + + baum.deleteSubTree(-10) + assertEquals(BSTNode(-7, "test"), rt?.left) + assertNull(rt?.left?.left) + + baum.deleteSubTree(25) + assertEquals(BSTNode(20, "test"), rt?.right?.right) + assertNull(rt?.right?.right?.right) + + baum.deleteSubTree(11) + assertEquals(BSTNode(12, "test"), rt?.right?.left) + assertNull(rt?.right?.left?.left) + } + + @Test + fun deleteNotExistingSubTree() { + assertFalse(baum.deleteSubTree(34)) + assertEquals(11, baum.countNodes()) + } + + @Test + fun deleteSubTreeByAlreadyRemovedKey() { + baum.remove(-7) + assertFalse(baum.deleteSubTree(-7)) + assertEquals(10, baum.countNodes()) + } + + @Test + fun getSubTree() { + var neuBaum = baum.getSubTree(13) + + assertEquals(BSTNode(13, "test"), neuBaum?.root()) + assertEquals(6, neuBaum?.countNodes()) + + + neuBaum = baum.getSubTree(-7) + + assertEquals(BSTNode(-7, "test"), neuBaum?.root()) + assertEquals(4, neuBaum?.countNodes()) + + assertEquals(11, baum.countNodes()) + } + + @Test + fun getRootSubTree() { + //You can't get SubTree where current tree's root is new root + assertNull(baum.getSubTree(10)) + } + + @Test + fun getSubTreeByNotExistingKey() { + assertNull(baum.getSubTree(23)) + assertNull(baum.getSubTree(-5)) + assertNull(baum.getSubTree(0)) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/NodeTest.kt b/src/test/kotlin/NodeTest.kt new file mode 100644 index 0000000..7881ed1 --- /dev/null +++ b/src/test/kotlin/NodeTest.kt @@ -0,0 +1,101 @@ +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import treeLib.nodes.BSTNode +import kotlin.test.* + +class NodeTest { + var node01 = BSTNode(1, 123) + var node02 = BSTNode(2, 321, right = node01) + var node2L = BSTNode(2, 321, left = node02) + var node03 = BSTNode(3, "node02 и node2L отличаются лишь ссылками") + + @BeforeEach + fun init() { + node01 = BSTNode(1, 123) + node02 = BSTNode(2, 321) + node03 = BSTNode(3, "waiting for something to happen?") + } + + @Nested + inner class PublicNodeMethods { + @Test + @DisplayName("Compare to ==") + fun compare0() { + assertEquals(0, node02.compareTo(node2L)) + } + + @Test + @DisplayName("Compare to >") + fun compare1() { + assertTrue(node02.compareTo(node01) > 0) + } + + @Test + @DisplayName("Compare to <") + fun compare2() { + assertTrue(node01.compareTo(node2L) < 0) + } + + @Test + @DisplayName("Hash code on one node twice") + fun hashCode1() { + assertEquals(node03.hashCode(), node03.hashCode()) + } + + @Test + @DisplayName("Hash code on two equal nodes") + fun hashCode2() { + assertEquals(node02.hashCode(), node2L.hashCode()) + } + + @Test + @DisplayName("Hash code on two different nodes") + fun hashCode3() { + assertNotEquals(node01.hashCode(), node02.hashCode()) + } + + @Test + @DisplayName("Equals is true on one node") + fun equals1() { + assertTrue(node03.equals(node03)) + } + + @Test + @DisplayName("Equals is true if node's key and value are equals") + fun equals2() { + assertTrue(node2L.equals(node02) and node02.equals(node2L)) + } + + @Test + @DisplayName("Equals is not equal to null") + fun equals3() { + assertFalse(node03.equals(null)) + } + + @Test + @DisplayName("Equals is false if nodes are different") + fun equals4() { + assertFalse(node01.equals(node02)) + } + + @Test + @DisplayName("To string") + fun toString1() { + assertEquals(node01.toString(), "(1, 123)") + } + + @Test + @DisplayName("To pair") + fun toPair1() { + assertEquals(node01.toPair(), Pair(1, 123)) + } + + @Test + @DisplayName("I mean we I believe that key() and value() works correctly, whatever") + fun keyValue() { + assertEquals(node03.key(), 3) + assertEquals(node03.value(), "waiting for something to happen?") + } + } +} diff --git a/src/test/kotlin/RBTreeTest.kt b/src/test/kotlin/RBTreeTest.kt new file mode 100644 index 0000000..6b20567 --- /dev/null +++ b/src/test/kotlin/RBTreeTest.kt @@ -0,0 +1,471 @@ +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import treeLib.bintrees.RBTree +import treeLib.nodes.RBNode +import kotlin.test.* + +class RBTreeTest { + var emptyTree = RBTree() + + @BeforeEach + fun emptyTree() { + emptyTree.clear() + } + + @Nested + @DisplayName("Add method tests") + inner class AddMethod { + @Test + @DisplayName("Add node to the root") + fun addRoot() { + val root = emptyTree.add(1, 1974) + assertEquals(RBNode(1, 1974), root) + assertEquals(root, emptyTree.root()) + } + + @Test + @DisplayName("Add node that already exist") + fun addExisted() { + emptyTree.add(1, 1974) + assertEquals(null, emptyTree.add(1, 9999)) + assertEquals(RBNode(1, 1974), emptyTree.root()) + } + + @Test + @DisplayName("Add without uncle, right ver") + fun add1R() { + val root = emptyTree.add(0, 0)!! + val right = emptyTree.add(1, 1)!! + val right2 = emptyTree.add(2, 2)!! + assertEquals(root, emptyTree.root()!!.left) + assertEquals(right, emptyTree.root()) + assertEquals(right2, emptyTree.root()!!.right) + assertTrue(root.isRed) + assertFalse(right.isRed) + assertTrue(right2.isRed) + } + + @Test + @DisplayName("Add without uncle, left ver") + fun add1L() { + val root = emptyTree.add(0, 0)!! + val left = emptyTree.add(-1, -1)!! + val left2 = emptyTree.add(-2, -2)!! + assertEquals(root, emptyTree.root()!!.right) + assertEquals(left, emptyTree.root()) + assertEquals(left2, emptyTree.root()!!.left) + assertTrue(root.isRed) + assertFalse(left.isRed) + assertTrue(left2.isRed) + } + + + @Test + @DisplayName("Add with red uncle, right ver") + fun add2R() { + val root = emptyTree.add(0, 0)!! + val left = emptyTree.add(-1, -1)!! + val right = emptyTree.add(1, 1)!! + val right2 = emptyTree.add(2, 2)!! + assertEquals(root, emptyTree.root()) + assertEquals(left, emptyTree.root()!!.left) + assertEquals(right, emptyTree.root()!!.right) + assertEquals(right2, emptyTree.root()!!.right!!.right) + assertFalse(root.isRed) + assertFalse(left.isRed) + assertFalse(right.isRed) + assertTrue(right2.isRed) + } + + @Test + @DisplayName("Add with red uncle, left ver") + fun add2L() { + val root = emptyTree.add(0, 0)!! + val right = emptyTree.add(1, 1)!! + val left = emptyTree.add(-1, -1)!! + val left2 = emptyTree.add(-2, -2)!! + assertEquals(root, emptyTree.root()) + assertEquals(right, emptyTree.root()!!.right) + assertEquals(left, emptyTree.root()!!.left) + assertEquals(left2, emptyTree.root()!!.left!!.left) + assertFalse(root.isRed) + assertFalse(right.isRed) + assertFalse(left.isRed) + assertTrue(left2.isRed) + } + + @Test + @DisplayName("Add with black uncle, right ver") + fun add3R() { + val root = emptyTree.add(0, 0)!! + val left = emptyTree.add(-1, -1)!! + val right = emptyTree.add(1, 1)!! + val right2 = emptyTree.add(3, 3)!! + val right15 = emptyTree.add(2, 2)!! + assertEquals(root, emptyTree.root()) + assertEquals(left, emptyTree.root()!!.left) + assertEquals(right, emptyTree.root()!!.right!!.left) + assertEquals(right2, emptyTree.root()!!.right!!.right) + assertEquals(right15, emptyTree.root()!!.right) + assertFalse(root.isRed) + assertFalse(left.isRed) + assertTrue(right.isRed) + assertFalse(right15.isRed) + assertTrue(right2.isRed) + } + + @Test + @DisplayName("Add with black uncle, left ver") + fun add3L() { + val root = emptyTree.add(0, 0)!! + val right = emptyTree.add(1, 1)!! + val left = emptyTree.add(-1, -1)!! + val left2 = emptyTree.add(-3, -3)!! + val left15 = emptyTree.add(-2, -2)!! + assertEquals(root, emptyTree.root()) + assertEquals(right, emptyTree.root()!!.right) + assertEquals(left, emptyTree.root()!!.left!!.right) + assertEquals(left2, emptyTree.root()!!.left!!.left) + assertEquals(left15, emptyTree.root()!!.left) + assertFalse(root.isRed) + assertFalse(right.isRed) + assertTrue(left.isRed) + assertFalse(left15.isRed) + assertTrue(left2.isRed) + } + + @Test + @DisplayName("Add multiple nodes") + fun addMany() { + val nodesResult = MutableList(10) { emptyTree.add(it, it) } + assertEquals(5, emptyTree.height()) + for (keyValue in 10..19) + nodesResult.addLast(emptyTree.add(keyValue, keyValue)) + assertEquals(6, emptyTree.height()) + val colorResult = arrayListOf( + false, false, false, true, false, + false, false, false, false, false, + false, true, false, true, false, + false, false, true, false, true, + ) + for (node in emptyTree) { + assertEquals(nodesResult.removeFirst(), node) + assertEquals(colorResult.removeFirst(), node.isRed) + } + } + } + + @Nested + @DisplayName("Remove method tests") + inner class RemoveMethod { + var tree3Node = RBTree() + var bft15 = RBTree() + + @BeforeEach + fun initTree() { + tree3Node = RBTree() // 0 + tree3Node.add(0, 0) // / \ + tree3Node.add(10, 10) // -10 10 + tree3Node.add(-10, -10) // + + bft15 = RBTree() //15 node tree (balanced) // 8 + var k = 16 // / \ + for (i in listOf(1, 2, 4, 8)) { // 4 12 + for (j in 0..() + + @Nested + inner class removingRoots() { + @BeforeEach + fun setUp() { + baum.add(20,100) + baum.add(50,90) + baum.add(35,80) + baum.add(40,70) + baum.add(30,60) + baum.add(70,50) + baum.add(60,40) + baum.add(80,30) + } + + @Test + fun removeTheOnlyNode() { + baum.clear() + baum.add(20, 50) + baum.remove(20) + assertEquals(null, baum.root()) + } + + @Test + fun removeRootWithOnlyRightChild() { + baum.remove(20) + assertEquals(TreapNode(50, 90), baum.root()) + } + + @Test + fun removeRootWithOnlyLeftChild() { + baum.clear() + baum.add(2, 20) + baum.add(0, 10) + baum.remove(2) + + assertEquals(TreapNode(0, 10), baum.root()) + } + + @Test + fun removeRootWithTwoChildren() { + baum.add(10, 95) + baum.add(15, 45) + baum.add(0, 65) + baum.remove(20) + val rt = baum.root() + + assertEquals(TreapNode(10, 95), rt) + assertEquals(TreapNode(0,65), rt?.left) + assertEquals(TreapNode(15,45), rt?.right?.left?.left?.left) + assertEquals(TreapNode(50, 90), rt?.right) + } + + } + + @Nested + inner class TestingRemoveMethod { + + @BeforeEach + fun setUp() { + baum.add(20,100) + baum.add(50,90) + baum.add(35,80) + baum.add(40,70) + baum.add(30,60) + baum.add(70,50) + baum.add(60,40) + baum.add(80,30) + } + + @Test + fun removeNodeWithoutChildren() { + val rt = baum.root()?.right?.left + + baum.remove(40) + assertEquals(null, rt?.right) + + baum.remove(30) + assertEquals(null, rt?.left) + + baum.remove(35) + assertEquals(null, baum.root()?.right?.left) + } + + @Test + fun removeNodeWithOnlyOneChild() { + baum.remove(60) + baum.remove(80) + baum.remove(70) + assertEquals(null, baum.root()?.right?.right) + + baum.remove(50) + assertEquals(TreapNode(35, 80), baum.root()?.right) + } + + @Test + fun removeNodeWithTwoChildrenFirstCase() { + val rt = baum.root()?.right + baum.remove(35) + + assertEquals(TreapNode(40, 70), rt?.left) + assertEquals(TreapNode(30,60), rt?.left?.left) + } + + @Test + fun removeNodeWithTwoChildrenSecondCase() { + val rt = baum.root()?.right + baum.remove(70) + + assertEquals(TreapNode(60, 40), rt?.right) + assertEquals(TreapNode(80, 30), rt?.right?.right) + } + + @Test + fun removeNodeWithTwoChildrenThirdCase() { + val rt = baum.root() + baum.remove(50) + + assertEquals(TreapNode(35,80), rt?.right) + assertEquals(TreapNode(30,60), rt?.right?.left) + assertEquals(TreapNode(40,70), rt?.right?.right) + assertEquals(TreapNode(70,50), rt?.right?.right?.right) + assertEquals(TreapNode(60,40), rt?.right?.right?.right?.left) + assertEquals(TreapNode(80,30), rt?.right?.right?.right?.right) + } + + + @Test + fun removeReturnNull() { + assertEquals(null, baum.remove(20)) + assertEquals(null, baum.remove(50)) + assertEquals(null, baum.remove(60)) + assertEquals(null, baum.remove(70)) + } + } + + @Nested + inner class BSTadding { + @Test + fun addingRoot() { + baum.add(24, 50) + assertEquals(TreapNode(24, 50), baum.root()) + } + + + @Test + fun addingLikeInBSTree() { + baum.add(4, 60) + baum.add(7, 50) + baum.add(2, 40) + baum.add(10, 30) + baum.add(-1, 20) + baum.add(3, 10) + baum.add(1, 0) + + val rt = baum.root() + + assertEquals(TreapNode(4, 60), baum.root()) + assertEquals(TreapNode(7,50), rt?.right) + assertEquals(TreapNode(2,40), rt?.left) + assertEquals(TreapNode(10,30), rt?.right?.right) + assertEquals(TreapNode(-1,20), rt?.left?.left) + assertEquals(TreapNode(3,10), rt?.left?.right) + assertEquals(TreapNode(1,0), rt?.left?.left?.right) + } + } + + @Nested + inner class TestingAddingMethod { + + + @Test + fun changingRootToLeft() { + baum.add(2, 10) + baum.add(4,20) + + assertEquals(TreapNode(4,20), baum.root()) + assertEquals(TreapNode(2,10), baum.root()?.left) + } + + @Test + fun changingRootToRight() { + baum.add(4, 10) + baum.add(2,20) + + assertEquals(TreapNode(2,20), baum.root()) + assertEquals(TreapNode(4,10), baum.root()?.right) + } + + @Test + fun addingLikeInBSTreeAndChangingByAddingRootLeftAndRightChildren() { + baum.add(4, 60) + baum.add(7, 50) + baum.add(2, 40) + baum.add(10, 30) + baum.add(-1, 20) + baum.add(3, 10) + baum.add(1, 0) + baum.add(11, 55) + baum.add(0, 45) + + val rt = baum.root() + + assertEquals(TreapNode(4, 60), baum.root()) + assertEquals(TreapNode(11,55), rt?.right) + assertEquals(TreapNode(0,45), rt?.left) + assertEquals(TreapNode(7,50), rt?.right?.left) + assertEquals(TreapNode(10,30), rt?.right?.left?.right) + assertEquals(TreapNode(2,40), rt?.left?.right) + assertEquals(TreapNode(3,10), rt?.left?.right?.right) + assertEquals(TreapNode(1,0), rt?.left?.right?.left) + assertEquals(TreapNode(-1,20), rt?.left?.left) + } + + @Test + fun changingSubTreesFarFromRoot() { + assertEquals(TreapNode(20,100), baum.add(20,100)) + baum.add(50,90) + baum.add(35,80) + assertEquals(TreapNode(40,70), baum.add(40,70)) + baum.add(30,60) + baum.add(70,50) + baum.add(60,40) + assertEquals(TreapNode(80,30), baum.add(80,30)) + baum.add(32, 85) + baum.add(75, 65) + + val rt = baum.root() + + assertEquals(TreapNode(20,100), rt) + assertEquals(TreapNode(32,85), rt?.right?.left) + assertEquals(TreapNode(75,65), rt?.right?.right) + assertEquals(TreapNode(30, 60), rt?.right?.left?.left) + assertEquals(TreapNode(35,80), rt?.right?.left?.right) + assertEquals(TreapNode(40,70), rt?.right?.left?.right?.right) + assertEquals(TreapNode(80,30), rt?.right?.right?.right) + assertEquals(TreapNode(70,50), rt?.right?.right?.left) + assertEquals(TreapNode(60,40), rt?.right?.right?.left?.left) + } + + @Test + fun addingAlreadyExistingKeys() { + baum.add(20,100) + baum.add(50,90) + baum.add(35,80) + baum.add(40,70) + baum.add(30,60) + + assertEquals(null, baum.add(35, 80)) + assertEquals(null, baum.add(35, 40)) + assertEquals(null, baum.add(20, 100)) + assertEquals(null, baum.add(30, 80)) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/TreeIteratorTest.kt b/src/test/kotlin/TreeIteratorTest.kt new file mode 100644 index 0000000..17175e7 --- /dev/null +++ b/src/test/kotlin/TreeIteratorTest.kt @@ -0,0 +1,85 @@ +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import treeLib.bintrees.BSTree +import treeLib.nodes.BSTNode +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +class TreeIteratorTest { + var bft31 = BSTree() + + @BeforeEach + fun init() { + bft31 = BSTree() + var k = 32 + for (i in listOf(1, 2, 4, 8, 16)) { + for (j in 0..