diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c2df060 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: Build + +on: + pull_request: + branches: + - main + - develop + - fixes-after-1st-review + push: + branches: + - main + - develop + - fixes-after-1st-review + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + cache: gradle + distribution: "temurin" + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Build with Gradle + run: ./gradlew build + + - name: Run tests + run: ./gradlew test diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..8e40a86 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,56 @@ +name: Jacoco coverage + +on: + pull_request: + branches: + - main + - develop + - fixes-after-1st-review + push: + branches: + - main + - develop + - fixes-after-1st-review + workflow_dispatch: + +jobs: + coverage: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "temurin" + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Run Coverage + run: ./gradlew jacocoTestReport + + - name: Generate JaCoCo Report + uses: cicirello/jacoco-badge-generator@v2 + with: + generate-branches-badge: true + jacoco-csv-file: lib/build/reports/jacoco/test/jacocoTestReport.csv + + - name: Add coverage to PR + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 + with: + paths: | + ${{ github.workspace }}/lib/build/reports/jacoco/test/jacocoTestReport.xml, + ${{ github.workspace }}/**/build/reports/jacoco/**/debugCoverage.xml + token: ${{ secrets.GITHUB_TOKEN }} + title: '# 🇷🇺 Coverage Report' + update-comment: true + min-coverage-overall: 40 + min-coverage-changed-files: 60 + pass-emoji: '🥳' + fail-emoji: '🤡' diff --git a/.github/workflows/ktlint.yml b/.github/workflows/ktlint.yml new file mode 100644 index 0000000..f58f9f6 --- /dev/null +++ b/.github/workflows/ktlint.yml @@ -0,0 +1,25 @@ +name: ktlint + +on: + pull_request: + branches: + - main + - develop + - fixes-after-1st-review + push: + branches: + - main + - develop + - fixes-after-1st-review + workflow_dispatch: + +jobs: + ktlint: + runs-on: ubuntu-latest + + steps: + - name: "checkout" + uses: actions/checkout@v2 + + - name: "ktlint" + uses: "block42-blockchain-company/ktlint-action@master" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d55f14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,282 @@ +# Created by https://www.toptal.com/developers/gitignore/api/linux,windows,java,kotlin,gradle,intellij+all,intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=linux,windows,java,kotlin,gradle,intellij+all,intellij + +### Intellij ### +# 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 Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### 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 + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# 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 + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# SonarLint plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### 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 + +### Java ### +# 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* + +### Kotlin ### +# Compiled class file + +# Log file + +# BlueJ files + +# Mobile Tools for Java (J2ME) + +# Package Files # + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml + +### 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* + +### 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/linux,windows,java,kotlin,gradle,intellij+all,intellij \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2d2824 --- /dev/null +++ b/README.md @@ -0,0 +1,168 @@ +

badge +badge +badge

+ + +

BinTreeKit

+ +## About Project +`BinTreeKit` is a library that provides implementations for three types of trees: `SimpleBinarySearchTree`, `AVLSearchTree` and `RBSearchTree`. The library is designed to simplify the process of managing hierarchical data, allowing Kotlin developers to focus on building robust and scalable applications. + + +#### Table of Contents +- [About Project](#about-project) +- [Usage](#usage) +- [Library Features](#library-features) + - [Constructors](#constructors) + - [Methods](#methods) + - [Tree Properties](#tree-properties) + - [Iterator](#iterator) + - [Constructors](#constructors-1) + - [Methods](#methods-1) +- [Developers](#developers) +- [Contributing](#contributing) +- [License](#license) + + +## Usage +1. **Importing Classes:** +```kotlin +import main.trees.SimpleBinarySearchTree +import main.trees.AVLSearchTree +import main.trees.rbTree +``` + +2. **Instantiate Trees:** +```kotlin +val map = mapOf(Pair(1, "cat"), Pair(2, "dog")) + +// create a Simple Binary Search Tree +val emptyBstTree = SimpleBinarySearchTree() +val bstTree = SimpleBinarySearchTree(map) + +// create an AVL Search Tree +val emptyAvlTree = AVLSearchTree() +val avlTree = AVLSearchTree(map) + +// create a Red-Black Search Tree +val emptyRbTree = RBSearchTree() +val rbTree = RBSearchTree(map) +``` + + +3. **Use Tree Methods:** +```kotlin +// put key-value pairs with different values of replaceIfExists perematers +bstTree.putAll(map, true) +rbTree.put(4, "horse", false) + +// remove key-value pair from tree and return value +println(rbTree.remove(4)) // output: horse +bstTree.remove(1) + +//get key-value pair from tree +println(avlTree.getMin()) //output: cat + +// pairwise iteration +for (pair in avlTree) { + print("${pair.second}, ") +} // output: cat, dog +``` + + +## Library Features + + +### Constructors +- `constructor(comparator: Comparator? = null)` - constructs a new AbstractBinarySearchTree instance with the given comparator. + + +- `constructor(map: Map, replaceIfExists: Boolean = true, comparator: Comparator? = null)` - constructs a new AbstractBinarySearchTree instance initialized with the contents of the given map. + + > The `comparator` to be used optional for ordering keys in the tree. + + +### Methods + + +- `put(key: K, value: V, replaceIfExists : Boolean = true)` - inserts the specified key-value pair into the tree. + +- `putAll(map: Map, replaceIfExists: Boolean = true)` - inserts all key-value pairs from the given map into the tree. + + > `replaceIfExists` is an optional (default is true) Boolean flag indicating whether to replace the value if the key already exists in the tree. + + +- `remove(key: K): V?` - removes the key-value pair with the specified key from the tree and returns value associated with the removed key, or `null` if the key was not found in the tree. + + +- `get(key: K): V?` - retrieves the value associated with the specified key from the tree and returns value associated with the specified key, or `null` if the key was not found in the tree. + +- `getPair(key: K): Pair?` - retrieves the key-value pair associated with the specified key from the tree and returns the key-value pair associated with the specified key, or `null` if the key was not found in the tree. + +- `getMin(): V?` - retrieves the value associated with the minimum key in the tree and returns the value associated with the minimum key, or `null` if the tree is empty. + +- `getMax(): V?` - retrieves the value associated with the maximum key in the tree and returns the value associated with the maximum key, or `null` if the tree is empty. + +- `getMinKeyPair(): Pair?` - retrieves the key-value pair associated with the minimum key in the tree and returns the key-value pair associated with the minimum key, or `null` if the tree is empty. + +- `getMaxKeyPair(): Pair?` - retrieves the key-value pair associated with the maximum key in the tree and returns the key-value pair associated with the maximum key, or `null` if the tree is empty. + + +- `size(): Long` - returns the number of key-value pairs in the tree. + +- `isEmpty(): Boolean` checks whether the tree is empty and returns `true` if the tree is empty, `false` otherwise. + + +### Tree Properties + +- `size: Long` - the number of key-value pairs currently stored in the tree. + + +- `isEmpty: Boolean` - indicates whether the tree is empty. + + +### Iterator +###### Constructors +- `constructor(vertex: N?)` - constructs a new TreeIterator instance with the specified starting vertex. + + > `vertex` is the starting vertex of the iterator. + +###### Methods + +- `hasNext(): Boolean` - checks if there are more elements in the iteration and returns `true` if there are more elements, `false` otherwise. + + +- `next(): Pair` - retrieves the next key-value pair in the iteration and returns the next key-value pair in the iteration. + + +## Developers +- [vicitori](https://github.com/vicitori) - Victoria Lutsyuk (Simple Binary Search Tree) +- [Szczucki](https://github.com/Szczucki) - Nikita Shchutskii (AVL Search Tree) +- [TerrMen](https://github.com/TerrMen) - Kostya Oreshin (RB Search Tree) + + +## Contributing + +**Quick start** + +1. Create new branch branching off `develop`, name it as feature you want to implement +```bash +git checkout develop +git switch -c my_feature +``` + +2. Commit the changes and write messages according to [commits convention](https://www.conventionalcommits.org/en/v1.0.0/) +```bash +git commit -m "feat: implement new feature" +``` + +3. Push changes to a remote repository +```bash +git push origin my_feature +``` + +4. Open pull request to `develop` + + +## License +This project uses the **APACHE LICENSE, VERSION 2.0**. See the [LICENSE](LICENSE.md) for more info. 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..50adebb --- /dev/null +++ b/lib/build.gradle.kts @@ -0,0 +1,80 @@ +/* + * 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 org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + alias(libs.plugins.jvm) + + // Apply the java-library plugin for API and implementation separation. + `java-library` + + // Code coverage plugin + jacoco + + // Documentation generation + id("org.jetbrains.dokka") version "1.9.20" +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use the Kotlin JUnit 5 integration. + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + // + testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2") + + // Use the JUnit 5 integration. + testImplementation(libs.junit.jupiter.engine) + + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + compileOnly("org.jetbrains.dokka:dokka-core:1.9.20") +} + +// 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() +} + +tasks.named("jacocoTestReport") { + dependsOn(tasks.test) + reports { + csv.required = true + xml.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } +} + +tasks.dokkaHtml { + outputDirectory.set(layout.buildDirectory.dir("documentation/html")) +} + +tasks.dokkaGfm { + outputDirectory.set(layout.buildDirectory.dir("documentation/markdown")) +} + +tasks.register("dokkaHtmlJar") { + dependsOn(tasks.dokkaHtml) + from(tasks.dokkaHtml.flatMap { it.outputDirectory }) + archiveClassifier.set("html-docs") +} + +tasks.register("dokkaJavadocJar") { + dependsOn(tasks.dokkaJavadoc) + from(tasks.dokkaJavadoc.flatMap { it.outputDirectory }) + archiveClassifier.set("javadoc") +} diff --git a/lib/src/main/kotlin/iterator/TreeIterator.kt b/lib/src/main/kotlin/iterator/TreeIterator.kt new file mode 100644 index 0000000..1365945 --- /dev/null +++ b/lib/src/main/kotlin/iterator/TreeIterator.kt @@ -0,0 +1,49 @@ +package iterator + +import vertexes.InterfaceBSTVertex +import java.util.Stack + +/** + * Iterator iterates over the vertices of the tree, visiting each vertex in the order of a depth-first traversal. + * + * [Iterator] interface implementation. + * + * @param K the type of keys in the tree + * @param V the type of values associated with the keys + * @param N the type of tree vertices implementing InterfaceBSTVertex + */ +open class TreeIterator>( + vertex: N?, +) : Iterator> { + protected val stack = Stack() + + init { + // Initialize the iterator with the given vertex by adding it to the stack + vertex?.let { stack.add(it) } + } + + /** + * Returns true if the iterator has more elements. + * + * This method checks if there are more vertices to traverse in the tree. + * + * @return true if the iterator has more elements, otherwise false + */ + override fun hasNext(): Boolean { + return stack.isNotEmpty() + } + + /** + * Returns the next element in the iteration. + * + * This method returns the next vertex in the depth-first traversal of the tree. + * + * @return the next element in the iteration as a Pair containing the key and value of the vertex + */ + override fun next(): Pair { + val nextVertex: N = stack.pop() + nextVertex.leftSon?.let { stack.add(it) } + nextVertex.rightSon?.let { stack.add(it) } + return Pair(nextVertex.key, nextVertex.value) + } +} diff --git a/lib/src/main/kotlin/trees/AVLSearchTree.kt b/lib/src/main/kotlin/trees/AVLSearchTree.kt new file mode 100644 index 0000000..0fb1771 --- /dev/null +++ b/lib/src/main/kotlin/trees/AVLSearchTree.kt @@ -0,0 +1,466 @@ +package trees + +import vertexes.AVLVertex + +/** + * An implementation of a binary search tree that automatically balances itself using AVL rotations. + * + * It extends [AbstractBinarySearchTree] and uses [AVLVertex] as vertices. + * + * If the tree has an incomparable key type and comparator is `null`, if the tree + * is non-empty, when trying to call the search, insert and delete methods, the tree + * will remain unchanged, the operation throws an exception with the message "Key's + * type is incomparable and comparator was not given". + * + * @param K key type + * @param V value type + * @property comparator `Comparator?` type; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + * @property size `Long` type; number of key-value pairs in this tree + * @property root `AVLVertex?` type, `null` by default + */ +open class AVLSearchTree : AbstractBinarySearchTree> { + /** + * Puts a new vertex of a certain type into the tree with a given key and value + * + * @param key `K` type + * @param value `V` type, associated with this key + * @param replaceIfExists `Boolean` type; If `true` - replaces the value if the key already exists. If `false` - ignores it. + */ + override fun put( + key: K, + value: V, + replaceIfExists: Boolean, + ) { + if (isNotEmpty()) { + when (val putRecReturned = putRec(key, value, replaceIfExists, root as AVLVertex)) { + null, root -> {} + else -> root = putRecReturned + } + return + } + root = AVLVertex(key, value) + size++ + } + + /** + * Associates the specified value with the specified key in this tree + * + * @param key `K` type + * @param value `V` type + * @param replaceIfExists `Boolean` type; If `true` - replaces the value if the key already exists. If `false` - ignores it. + * @param vertex `AVLVertex` type; current vertex in the recursion + * @return root vertex of the tree after the operation + */ + private fun putRec( + key: K, + value: V, + replaceIfExists: Boolean, + vertex: AVLVertex, + ): AVLVertex? { + fun putRecShort(vertex: AVLVertex): AVLVertex? { + return putRec(key, value, replaceIfExists, vertex) + } + + val nextCallReturned: AVLVertex? + when (compareKeys(key, vertex.key)) { + -1 -> { + if (vertex.leftSon == null) { + vertex.leftSon = AVLVertex(key, value) + vertex.sonsHeightDiff++ + size++ + return vertex + } + nextCallReturned = putRecShort(vertex.leftSon as AVLVertex) + } + + 0 -> { + if (replaceIfExists) { + vertex.key = key + vertex.value = value + } + return null + } + + else -> { + if (vertex.rightSon == null) { + vertex.rightSon = AVLVertex(key, value) + vertex.sonsHeightDiff-- + size++ + return vertex + } + nextCallReturned = putRecShort(vertex.rightSon as AVLVertex) + } + } + if (nextCallReturned == null) return null + + fun doBalanceChoreWhenLeftSubTreeChanged(): AVLVertex? { + if (nextCallReturned.sonsHeightDiff == 0) return null + if (++vertex.sonsHeightDiff == 2) return balance(vertex) + return vertex + } + + fun doBalanceChoreWhenRightSubTreeChanged(): AVLVertex? { + if (nextCallReturned.sonsHeightDiff == 0) return null + if (--vertex.sonsHeightDiff == -2) return balance(vertex) + return vertex + } + when (nextCallReturned) { + vertex.leftSon -> return doBalanceChoreWhenLeftSubTreeChanged() + vertex.rightSon -> return doBalanceChoreWhenRightSubTreeChanged() + else -> { + if (compareKeys(nextCallReturned.key, vertex.key) == -1) { + vertex.leftSon = nextCallReturned + return doBalanceChoreWhenLeftSubTreeChanged() + } + vertex.rightSon = nextCallReturned + return doBalanceChoreWhenRightSubTreeChanged() + } + } + } + + /** + * Removes the mapping for a key from this tree if it is present + * + * @param key `K` type + * @return the previous value associated with key, or `null` if there was no mapping for key + */ + override fun remove(key: K): V? { + if (isNotEmpty()) { + val removeRecReturned = removeRec(key, root as AVLVertex) + when (removeRecReturned.first) { + RemovalStage.B -> { + if (removeRecReturned.component2() != root) { + root = removeRecReturned.component2() + } + } + + RemovalStage.C -> root = null + else -> {} + } + return removeRecReturned.component3() + } + return null + } + + /** + * An enumeration representing different stages of removal during the removal process. + */ + private enum class RemovalStage { + /** + * Don't need tree changes anymore + */ + A, + + /** + * Probably need some tree changes, but not make the son of the vertex `null` + */ + B, + + /** + * Need to `null` due "Son" property of (if exists) the parent of removed vertex + b + */ + C, + } + + /** + * Recursively removes a key-value pair from the subtree rooted at the given vertex + * + * @param key `K` type + * @param vertex the root of the subtree to remove from + * @return Triple that consists of: + * + * 1) removal stage; + * + * 2) if RemovalStage == a : just a vertex (don't need it later); + * + * if RemovalStage == b : the root of the changed subtree; + * + * if RemovalStage == c : the removed vertex; + * + * 3) a value of the removed vertex (or `null` if key not exists). */ + private fun removeRec( + key: K, + vertex: AVLVertex, + ): Triple, V?> { + /** + * Triple consists of: + * + * 1) removal stage; + * + * 2) if RemovalStage == a : just a vertex (don't need it later); + * if RemovalStage == b : the root of the changed subtree; + * if RemovalStage == c : the removed vertex; + * + * 3) a value of the removed vertex (or `null` if key not exists). + */ + val nextCallReturned: Triple?, V?> + when (compareKeys(key, vertex.key)) { + -1 -> { + if (vertex.leftSon == null) return Triple(RemovalStage.A, vertex, null) + nextCallReturned = removeRec(key, vertex.leftSon as AVLVertex) + } + + 1 -> { + if (vertex.rightSon == null) return Triple(RemovalStage.A, vertex, null) + nextCallReturned = removeRec(key, vertex.rightSon as AVLVertex) + } + + else -> { + return when ((vertex.leftSon == null) to (vertex.rightSon == null)) { + true to true -> { + size-- + Triple(RemovalStage.C, vertex, vertex.value) + } + + true to false -> { + size-- + Triple(RemovalStage.B, vertex.rightSon as AVLVertex, vertex.value) + } + + false to true -> { + size-- + Triple(RemovalStage.B, vertex.leftSon as AVLVertex, vertex.value) + } + + else -> { + val valueOfVertex = vertex.value + Triple( + RemovalStage.B, + replaceSubtreeSRootByLargestInItsLeftSubtree(vertex), + valueOfVertex, + ) + } + } + } + } + + fun doBalanceChoreWhenLeftSubTreeChanged(): Triple, V?> { + if (nextCallReturned.component2().sonsHeightDiff in listOf(-1, 1)) { + return Triple(RemovalStage.A, vertex, nextCallReturned.component3()) + } + if (--vertex.sonsHeightDiff == -2) { + return Triple(RemovalStage.B, balance(vertex), nextCallReturned.component3()) + } + return Triple(RemovalStage.B, vertex, nextCallReturned.third) + } + + fun doBalanceChoreWhenRightSubTreeChanged(): Triple, V?> { + if (nextCallReturned.component2().sonsHeightDiff in listOf(-1, 1)) { + return Triple(RemovalStage.A, vertex, nextCallReturned.component3()) + } + if (++vertex.sonsHeightDiff == 2) { + return Triple(RemovalStage.B, balance(vertex), nextCallReturned.component3()) + } + return Triple(RemovalStage.B, vertex, nextCallReturned.component3()) + } + when (nextCallReturned.component1()) { + RemovalStage.A -> return nextCallReturned + RemovalStage.B -> + when (nextCallReturned.component2()) { + vertex.leftSon -> return doBalanceChoreWhenLeftSubTreeChanged() + vertex.rightSon -> return doBalanceChoreWhenRightSubTreeChanged() + else -> + when (compareKeys(nextCallReturned.component2().key, vertex.key)) { + -1 -> { + vertex.leftSon = nextCallReturned.component2() + return doBalanceChoreWhenLeftSubTreeChanged() + } + + else -> { + vertex.rightSon = nextCallReturned.component2() + return doBalanceChoreWhenRightSubTreeChanged() + } + } + } + + RemovalStage.C -> + when (compareKeys(nextCallReturned.component2().key, vertex.key)) { + -1 -> { + vertex.leftSon = null + return doBalanceChoreWhenLeftSubTreeChanged() + } + + else -> { + vertex.rightSon = null + return doBalanceChoreWhenRightSubTreeChanged() + } + } + } + } + + /** + * Replaces the initially subtree's root by the its left subtree's vertex with largest key, + * having previously removed that vertex + * + * @param subtreeSInitiallyRoot `AVLVertex` type; initially root of the subtree + * @return vertex that is the subtree's root after function was executed + */ + private fun replaceSubtreeSRootByLargestInItsLeftSubtree(subtreeSInitiallyRoot: AVLVertex): AVLVertex { + val substitute = getMaxKeyNodeRec(subtreeSInitiallyRoot.leftSon) as AVLVertex + val removeRecReturned = removeRec(substitute.key, subtreeSInitiallyRoot) + subtreeSInitiallyRoot.key = substitute.key + subtreeSInitiallyRoot.value = substitute.value + return if (removeRecReturned.component1() == RemovalStage.A) { + subtreeSInitiallyRoot + } else { + removeRecReturned.component2() + } + } + + /** + * Balances the subtree + * + * @param curVertex `AVLVertex` type; the root vertex of subtree to be balanced + * @return root vertex of the subtree after balancing + */ + private fun balance(curVertex: AVLVertex): AVLVertex { + var (rightSon, leftSon) = List?>(2) { null } + + fun setSonSHeightDiffsOfTwoVertices(values: Pair) { + curVertex.sonsHeightDiff = values.component1() + if (rightSon != null) { + (rightSon as AVLVertex).sonsHeightDiff = values.component2() + return + } + (leftSon as AVLVertex).sonsHeightDiff = values.component2() + } + + if (curVertex.sonsHeightDiff == -2) { + rightSon = curVertex.rightSon as AVLVertex + return if (rightSon.sonsHeightDiff == 1) { + val rightSonSLeftSon = rightSon.leftSon as AVLVertex + val subtreeRoot = bigRotateLeft(curVertex, rightSon) + setSonSHeightDiffsOfTwoVertices( + when (rightSonSLeftSon.sonsHeightDiff) { + 1 -> 0 to -1 + -1 -> 1 to 0 + else -> 0 to 0 + }, + ) + rightSonSLeftSon.sonsHeightDiff = 0 + subtreeRoot + } else { + val subtreeRoot = rotateLeft(curVertex, rightSon) + setSonSHeightDiffsOfTwoVertices( + if (rightSon.sonsHeightDiff == 0) { + -1 to 1 + } else { + 0 to 0 + }, + ) + subtreeRoot + } + } + leftSon = curVertex.leftSon as AVLVertex + return if (leftSon.sonsHeightDiff == -1) { + val leftSonSRightSon = leftSon.rightSon as AVLVertex + val subtreeRoot = bigRotateRight(curVertex, leftSon) + setSonSHeightDiffsOfTwoVertices( + when (leftSonSRightSon.sonsHeightDiff) { + -1 -> 0 to 1 + 1 -> -1 to 0 + else -> 0 to 0 + }, + ) + leftSonSRightSon.sonsHeightDiff = 0 + subtreeRoot + } else { + val subtreeRoot = rotateRight(curVertex, leftSon) + setSonSHeightDiffsOfTwoVertices( + if (leftSon.sonsHeightDiff == 0) { + 1 to -1 + } else { + 0 to 0 + }, + ) + subtreeRoot + } + } + + /** + * Performs a single left rotation of the subtree + * + * @param curVertex `AVLVertex` type; the current vertex to rotate around (the subtree's root) + * @param rightSon `AVLVertex` type; the right son of the subtree's root + * @return the new root of the subtree after rotation + */ + private fun rotateLeft( + curVertex: AVLVertex, + rightSon: AVLVertex, + ): AVLVertex { + curVertex.rightSon = rightSon.leftSon + rightSon.leftSon = curVertex + return rightSon + } + + /** + * Performs a single right rotation of the subtree + * + * @param curVertex `AVLVertex` type; the current vertex to rotate around (the subtree's root) + * @param leftSon `AVLVertex` type; the left son of the subtree's root + * @return the new root of the subtree after rotation + */ + private fun rotateRight( + curVertex: AVLVertex, + leftSon: AVLVertex, + ): AVLVertex { + curVertex.leftSon = leftSon.rightSon + leftSon.rightSon = curVertex + return leftSon + } + + /** + * Performs a big left rotation of the subtree + * + * @param curVertex `AVLVertex` type; the current vertex to rotate around (the subtree's root) + * @param rightSon `AVLVertex` type; the right son of the subtree's root + * @return the new root of the subtree after rotation + */ + private fun bigRotateLeft( + curVertex: AVLVertex, + rightSon: AVLVertex, + ): AVLVertex { + val curRightSon = rotateRight(rightSon, rightSon.leftSon as AVLVertex) + curVertex.rightSon = curRightSon + return rotateLeft(curVertex, curRightSon) + } + + /** + * Performs a big right rotation of the subtree + * + * @param curVertex `AVLVertex` type; the current vertex to rotate around (the subtree's root) + * @param leftSon `AVLVertex` type; the left son vertex of the subtree's root + * @return the new root of the subtree after rotation + */ + private fun bigRotateRight( + curVertex: AVLVertex, + leftSon: AVLVertex, + ): AVLVertex { + val curLeftSon = rotateLeft(leftSon, leftSon.rightSon as AVLVertex) + curVertex.leftSon = curLeftSon + return rotateRight(curVertex, curLeftSon) + } + + /** + * Constructs a new binary search tree with the specified comparator + * + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor (comparator: Comparator? = null) : super(comparator) + + /** + * Constructs a new binary search tree and puts all key-value pairs from the specified map to this tree + * + * @param map `Map` type + * @param replaceIfExists `Boolean` type. + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + * Supplied only if a [comparator] is present. If comparator is `null`, the value is replaced + * by the last value from the key-value pair in the map, where the key is the one already existing in the tree. + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(map: Map, replaceIfExists: Boolean = true, comparator: Comparator? = null) : super( + map, + replaceIfExists, + comparator, + ) +} diff --git a/lib/src/main/kotlin/trees/AbstractBinarySearchTree.kt b/lib/src/main/kotlin/trees/AbstractBinarySearchTree.kt new file mode 100644 index 0000000..c16753c --- /dev/null +++ b/lib/src/main/kotlin/trees/AbstractBinarySearchTree.kt @@ -0,0 +1,281 @@ +package trees + +import iterator.TreeIterator +import vertexes.InterfaceBSTVertex + +/** + * An abstract class representing a binary search tree. + * + * If the tree has an incomparable key type and comparator is `null`, if the tree + * is non-empty, when trying to call the search, insert and delete methods, the tree + * will remain unchanged, the operation throws an exception with the message "Key's + * type is incomparable and comparator was not given". + * + * @param K key type + * @param V value type + * @param N vertex type implementing the [InterfaceBSTVertex] interface + * @property comparator `Comparator?` type; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + * @property size `Long` type; number of key-value pairs in this tree + * @property root `N?` type, `null` by default + */ +abstract class AbstractBinarySearchTree> { + protected var comparator: Comparator? = null + protected var size: Long = 0L + protected var root: N? = null + + /** + * Returns an iterator over the elements of this tree in a certain sequence + */ + operator fun iterator(): Iterator> { + return TreeIterator(root) + } + + /** + * Returns the number of key-value pairs in this tree + */ + fun size(): Long { + return size + } + + /** + * Returns `true` if this tree contains no key-value pairs, and `false` otherwise + */ + fun isEmpty(): Boolean { + return size == 0L + } + + /** + * Returns `true` if this tree contains at least one key-value pair, and `false` otherwise + */ + fun isNotEmpty(): Boolean { + return size != 0L + } + + /** + * Returns the value associated with the specified key in this tree + * + * @param key `K` type + * @return If the key exists - corresponding value + * If the key does not exist - `null` + */ + fun get(key: K): V? { + return getRec(key) + } + + /** + * Returns a pair containing the specified key-value mapping + * + * @param key `K` type + * @return If the key exists - pair. If the key does not exist - `null`. + */ + fun getPair(key: K): Pair? { + val value = get(key) + return if (value == null) null else Pair(key, value) + } + + /** + * Returns the minimum key in the tree + * + * @return If the tree is not empty - minimum key, and `null` otherwise + */ + fun getMin(): V? { + val minKeyNode = getMinKeyNodeRec() + return if (minKeyNode == null) null else minKeyNode.value + } + + /** + * Returns the maximum key in the tree + * + * @return If the tree is not empty - maximum key, and `null` otherwise + */ + fun getMax(): V? { + val maxKeyNode = getMaxKeyNodeRec() + return if (maxKeyNode == null) null else maxKeyNode.value + } + + /** + * Returns key-value pair with the minimum key in the tree + * + * @return If the tree is not empty - pair with minimum key, and `null` otherwise + */ + fun getMinKeyPair(): Pair? { + val minKeyNode = getMinKeyNodeRec() + return if (minKeyNode == null) null else Pair(minKeyNode.key, minKeyNode.value) + } + + /** + * Returns key-value pair with the maximum key in the tree + * + * @return If the tree is not empty - pair with maximum key, and `null` otherwise + */ + fun getMaxKeyPair(): Pair? { + val maxKeyNode = getMaxKeyNodeRec() + return if (maxKeyNode == null) null else Pair(maxKeyNode.key, maxKeyNode.value) + } + + /** + * Puts a new vertex of a certain type into the tree with a given key and value + * + * @param key `K` type + * @param value `V` type, associated with this key + * @param replaceIfExists `Boolean` type, + * + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + */ + abstract fun put( + key: K, + value: V, + replaceIfExists: Boolean = true, + ) + + /** + * Puts all key-value pairs from the specified map to this tree + * + * @param map `Map` type + * @param replaceIfExists `Boolean` type. + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + * Supplied only if a [comparator] is present. If [comparator] is `null`, the value is replaced + * by the last value from the key-value pair in the map, where the key is the one already existing in the tree. + */ + fun putAll( + map: Map, + replaceIfExists: Boolean = true, + ) { + for (pair in map) put(pair.key, pair.value, replaceIfExists) + } + + /** + * Deletes a vertex by the entered key and returns the value stored in it + * + * @param key `K` type + * @return If a vertex with this key exists in the tree - the value stored in this vertex, otherwise `null` + */ + abstract fun remove(key: K): V? + + /** + * Deletes a vertex by the entered key and returns it as a key-value pair + * + * @param key `K` type + * @return If a vertex with such a key exists in the tree - the key-value pair corresponding to this vertex, otherwise `null` + */ + fun removeAndReturnPair(key: K): Pair? { + val value = remove(key) + return if (value == null) null else Pair(key, value) + } + + /** + * Recursively searches for the value associated with the entered key + * + * @param key `K` type + * @param vertex `N?` type, `root` by default; current vertex being examined + * @return If a vertex with this key exists in the tree - the value stored in this vertex, otherwise `null` + */ + private fun getRec( + key: K, + vertex: N? = root, + ): V? { + if (vertex == null) return null + return when (compareKeys(key, vertex.key)) { + 0 -> vertex.value + -1 -> getRec(key, vertex.leftSon) + else -> getRec(key, vertex.rightSon) + } + } + + /** + * Recursively searches for the value associated with the minimum key in the tree + * + * @param vertex `N?` type, `root` by default; current vertex being examined + * @return If the tree is not empty - the vertex with the minimum key in the tree, otherwise `null` + */ + protected fun getMinKeyNodeRec(vertex: N? = root): N? { + if (vertex == null) { + return null + } else { + return if (vertex.leftSon == null) { + vertex + } else { + getMinKeyNodeRec(vertex.leftSon) + } + } + } + + /** + * Recursively searches for the value associated with the maximum key in the tree + * + * @param vertex `N?` type, `root` by default; current vertex being examined + * @return If the tree is not empty - the vertex with the maximum key in the tree, otherwise `null` + */ + protected fun getMaxKeyNodeRec(vertex: N? = root): N? { + if (vertex == null) { + return null + } else { + return if (vertex.rightSon == null) { + vertex + } else { + getMaxKeyNodeRec(vertex.rightSon) + } + } + } + + /** + * Compares two keys + * + * Comparing with a comparator if it is not `null`, or without one if the comparator is null and + * the keys are of comparable type + * + * @param firstKey `K` type + * @param secondKey `K` type + * @return + * -1 if the first key is less than the second key; + * 0 if they are equal; + * 1 if the first key is greater than the second key. + */ + protected fun compareKeys( + firstKey: K, + secondKey: K, + ): Int { + val cpr = comparator + return if (cpr != null) { + when (cpr.compare(firstKey, secondKey)) { + in Int.MIN_VALUE..-1 -> -1 + 0 -> 0 + else -> 1 + } + } else { + val comparableKey = + firstKey as? Comparable + ?: throw Exception("Key's type is incomparable and comparator wasn't given") + when (comparableKey.compareTo(secondKey)) { + in Int.MIN_VALUE..-1 -> -1 + 0 -> 0 + else -> 1 + } + } + } + + /** + * Constructs a new binary search tree with the specified comparator + * + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(comparator: Comparator? = null) { + this.comparator = comparator + } + + /** + * Constructs a new binary search tree and puts all key-value pairs from the specified map to this tree + * + * @param map `Map` type + * @param replaceIfExists `Boolean` type. + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + * Supplied only if a [comparator] is present. If comparator is `null`, the value is replaced + * by the last value from the key-value pair in the map, where the key is the one already existing in the tree. + * + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(map: Map, replaceIfExists: Boolean = true, comparator: Comparator? = null) { + this.comparator = comparator + putAll(map, replaceIfExists) + } +} diff --git a/lib/src/main/kotlin/trees/RBSearchTree.kt b/lib/src/main/kotlin/trees/RBSearchTree.kt new file mode 100644 index 0000000..96e16d1 --- /dev/null +++ b/lib/src/main/kotlin/trees/RBSearchTree.kt @@ -0,0 +1,426 @@ +package trees + +import vertexes.RBVertex + +/** + * Red-Black Tree implementation. + * + * It extends [AbstractBinarySearchTree] and uses [RBVertex] as vertices. + * Red-Black Tree is a balanced binary search tree, where each vertex is colored either red or black. + * This implementation ensures the following properties: + * + * - Every vertex is either red or black. + * - The root is black. + * - Every leaf is black. + * - If a vertex is red, then both its children are black. + * - Every simple path from a vertex to a descendant leaf has the same number of black vertexes. + * + * If the tree has an incomparable key type and comparator is `null`, if the tree + * is non-empty, when trying to call the search, insert and delete methods, the tree + * will remain unchanged, the operation throws an exception with the message "Key's + * type is incomparable and comparator was not given". + * + * @param K key type + * @param V value type + * @property comparator `Comparator?` type; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + * @property size `Long` type; number of key-value pairs in this tree + * @property root `RBVertex?` type, `null` by default + */ +class RBSearchTree : AbstractBinarySearchTree> { + private val red = RBVertex.Color.RED + private val black = RBVertex.Color.BLACK + + /** + * This method removes the vertex with the given key from the tree and returns its associated value, + * maintaining the properties of the red-black tree. + * + * 4 cases we need to look at: + * + * 1) remove red vertex with 0 children -> just remove vertex + * + * 2) remove red or black vertex with 2 children -> + * find min vertex on the right subtree and swap it's key and value with + * key and value of vertex that we need to remove. + * Now we can work with vertex which has 1 or 0 children + * + * 3) remove black vertex with 1 child -> child can be only red, + * so we just swap child's key and value with key and value that we need to remove + * and look at case 1) + * + * 4) remove black vertex with 0 children -> just remove vertex + * + * @param key `K` type + * @return value associated with the removed vertex, or `null` if the key is not found + */ + override fun remove(key: K): V? { + val vertex: RBVertex = getVertex(key) ?: return null + --size + val value = vertex.value + + if (vertex == root && size == 0L) { + root = null + } else if (needToBalance(vertex)) { + balanceAfterRemove(vertex) + } + + return value + } + + /** + * Determines whether balancing is required after removing a vertex from the Red-Black Search Tree. + * + * @param vertex `RBVertex` type; The vertex to be checked for balancing. + * @return true if further balancing is required, false otherwise. + */ + private fun needToBalance(vertex: RBVertex): Boolean { + when (countChildren(vertex)) { + 0 -> { + if (vertex.color == red) { + replaceVertexBy(vertex, null) + return false + } + return true + } + + 1 -> { + replaceVertexBy(vertex, getChild(vertex)) + return false + } + + 2 -> { + val vertexForSwap = getMinKeyNodeRec(vertex.rightSon) + vertexForSwap?.let { + val key = vertex.key + vertex.key = it.key + it.key = key + + val value = vertex.value + vertex.value = it.value + it.value = value + + needToBalance(vertexForSwap) + } + } + } + return false + } + + /** + * We need to balance tree after removal black vertex with 0 children. + * + * In this fun we need to look at vertex's parent and brother: + * 1) brother is black and brother's rightSon is red -> we paint + * brother in parent's color, parent and brother's rightSon in black + * then rotate left + * + * 2) brother is black and brother's leftSon is red (rightSon - black) -> + * we swap colors of brother and brother's leftSon and rotate right + * then look at case 1 + * + * 3) brother is black and both sons are black -> we make brother red + * then we need to launch algorithm from the parent because of it + * can be red, so we have red parent and red son or black so + * the black height of all subtree decreased + * + * 4) brother is red -> make brother black, parent red and + * rotate left. We move conflict on level below, then we look at the previous cases + * + * @param vertex `RBVertex` type; The child vertex of the removed vertex or `null` if the removed vertex had no children. + */ + private fun balanceAfterRemove(vertex: RBVertex?) { + var currentVertex = vertex + while (currentVertex != root && (currentVertex?.color == black || currentVertex == null)) { + val isBrotherRightSon = (currentVertex == currentVertex?.parent?.leftSon) + var brother: RBVertex? = if (isBrotherRightSon) currentVertex?.parent?.rightSon else currentVertex?.parent?.leftSon + + if (brother?.color == red) { + brother.color = black + currentVertex?.parent?.color = red + val vertexForRotate = currentVertex?.parent + + when (isBrotherRightSon) { + true -> { + vertexForRotate?.let { rotateLeft(vertexForRotate) } + brother = currentVertex?.parent?.rightSon + } else -> { + vertexForRotate?.let { rotateRight(vertexForRotate) } + brother = currentVertex?.parent?.leftSon + } + } + } + + if ((brother?.leftSon?.color == black || brother?.leftSon == null) && + (brother?.rightSon?.color == black || brother?.rightSon == null) + ) { + brother?.color = red + currentVertex = currentVertex?.parent + + when (vertex) { + currentVertex?.leftSon -> currentVertex?.leftSon = null + currentVertex?.rightSon -> currentVertex?.rightSon = null + } + } else { + if ((isBrotherRightSon) && (brother.rightSon?.color == black || brother.rightSon == null)) { + brother.leftSon?.color = black + brother.color = red + rotateRight(brother) + brother = currentVertex?.parent?.rightSon + } else if ((!isBrotherRightSon) && (brother.leftSon?.color == black || brother.leftSon == null)) { + brother.rightSon?.color = black + brother.color = red + rotateLeft(brother) + brother = currentVertex?.parent?.leftSon + } + + val parentColor = currentVertex?.parent?.color + parentColor?.let { brother?.color = parentColor } + currentVertex?.parent?.color = black + val vertexForRotate = currentVertex?.parent + + when (isBrotherRightSon) { + true -> { + brother?.rightSon?.color = black + vertexForRotate?.let { rotateLeft(vertexForRotate) } + if (currentVertex == vertex) currentVertex?.parent?.leftSon = null + } else -> { + brother?.leftSon?.color = black + vertexForRotate?.let { rotateRight(vertexForRotate) } + if (currentVertex == vertex) currentVertex?.parent?.rightSon = null + } + } + currentVertex = root + } + } + currentVertex?.color = black + } + + /** + * Finds a vertex by corresponding key. If such vertex doesn't exist returns `null` + * + * @param key 'K` type + * @return vertex with the corresponding key, or `null` if such vertex doesn't exist + */ + private fun getVertex(key: K): RBVertex? { + var currentVertex: RBVertex? = root + + while (currentVertex?.key != key) { + if (currentVertex == null) return null + when (compareKeys(key, currentVertex.key)) { + -1 -> { + currentVertex = currentVertex.leftSon + } + + 1 -> { + currentVertex = currentVertex.rightSon + } + } + } + return currentVertex + } + + /** + * Finds free place and inserts newVertex, colors it in red. + * + * @param key 'K` type + * @param value `V` type + * @param replaceIfExists `Boolean` type; If `true` - replaces the value if the key already exists. If `false` - ignores it. + */ + override fun put( + key: K, + value: V, + replaceIfExists: Boolean, + ) { + var currentVertex: RBVertex? = root + var parent: RBVertex? = null + var isLeft: Boolean = false + ++size + + while (currentVertex != null) { + when (compareKeys(key, currentVertex.key)) { + -1 -> { + parent = currentVertex + currentVertex = currentVertex.leftSon + isLeft = true + } + + 0 -> { + if (replaceIfExists) currentVertex.value = value + --size + break + } + + 1 -> { + parent = currentVertex + currentVertex = currentVertex.rightSon + isLeft = false + } + } + } + + if (currentVertex == null) { + currentVertex = RBVertex(key, value, null, null, red, parent) + if (root == null) { + root = currentVertex + } else if (isLeft) { + parent?.let { parent.leftSon = currentVertex } + } else { + parent?.let { parent.rightSon = currentVertex } + } + } + + balanceAfterPut(currentVertex) + } + + /** + * Balances the tree after inserting a new vertex. + * + * We need to balance tree in two cases: + * 1) when newVertex is root, so our root is red + * 2) when parent of our newVertex is red(because newVertex is also red) + * + * in first case we just make the root black + * in second case we need to look at the newVertex's uncle + * if uncle is red, we make it black and newVertex's parent black and grandparent red + * launch algorithm to grandfather because now it's color changed to red + * if uncle is black we also make newVertex's parent black, grandparent red + * and rotate it right + * + * @param vertex `RBVertex` type; The newly inserted vertex. + */ + private fun balanceAfterPut(vertex: RBVertex) { + var currentVertex = vertex + + while (currentVertex.parent?.color == red) { + val grandparent = currentVertex.parent?.parent + val isUncleRightSon = (currentVertex.parent == grandparent?.leftSon) + val uncle = if (isUncleRightSon) grandparent?.rightSon else grandparent?.leftSon + + if (uncle?.color == red) { + currentVertex.parent?.color = black + uncle.color = black + grandparent?.color = red + currentVertex = grandparent ?: currentVertex + } else { + if ((isUncleRightSon) && (currentVertex == currentVertex.parent?.rightSon)) { + currentVertex = currentVertex.parent ?: currentVertex + rotateLeft(currentVertex) + } else if ((!isUncleRightSon) && (currentVertex == currentVertex.parent?.leftSon)) { + currentVertex = currentVertex.parent ?: currentVertex + rotateRight(currentVertex) + } + + currentVertex.parent?.color = black + currentVertex.parent?.parent?.color = red + val vertexForRotate = currentVertex.parent?.parent + vertexForRotate?.let { if (isUncleRightSon) rotateRight(vertexForRotate) else rotateLeft(vertexForRotate) } + } + } + root?.color = black + } + + /** + * Counts the number of children of the given vertex + * + * @param vertex `RBVertex` type; The vertex whose children count is to be determined. + * @return The number of children of the given vertex. + */ + private fun countChildren(vertex: RBVertex): Int { + var numOfChild = 0 + if (vertex.leftSon != null) ++numOfChild + if (vertex.rightSon != null) ++numOfChild + return numOfChild + } + + /** + * Retrieves the child vertex of the given vertex + * + * @param vertex `RBVertex` type; vertex The vertex whose child is to be retrieved. + * @return The child vertex of the given vertex. + */ + private fun getChild(vertex: RBVertex) = if (vertex.leftSon != null) vertex.leftSon else vertex.rightSon + + /** + * Replaces the old vertex with the new vertex in the tree structure + * + * @param oldVertex `RBVertex` type; The old vertex to be replaced. + * @param newVertex `RBVertex` type; The new vertex that replaces the old vertex. + */ + private fun replaceVertexBy( + oldVertex: RBVertex, + newVertex: RBVertex?, + ) { + if (root == oldVertex) { + root = newVertex + } else if (oldVertex == oldVertex.parent?.leftSon) { + oldVertex.parent?.leftSon = newVertex + } else { + oldVertex.parent?.rightSon = newVertex + } + newVertex?.parent = oldVertex.parent + } + + /** + * Performs a left rotation on the given vertex. + * + * Suppose that vertex has a rightSon. Swap parent and rightSon, rightSon's leftSon becomes parent's rightSon. + * + * @param vertex `RBVertex` type; The vertex on which the left rotation is to be performed. + */ + private fun rotateLeft(vertex: RBVertex) { + val rightVertex: RBVertex? = vertex.rightSon + vertex.rightSon = rightVertex?.leftSon + rightVertex?.leftSon.let { rightVertex?.leftSon?.parent = vertex } + rightVertex?.parent = vertex.parent + when { + vertex.parent == null -> root = rightVertex + vertex == vertex.parent?.rightSon -> vertex.parent?.rightSon = rightVertex + else -> vertex.parent?.leftSon = rightVertex + } + vertex.parent = rightVertex + rightVertex?.leftSon = vertex + } + + /** + * Performs a right rotation on the given vertex. + * + * Suppose that vertex has a leftSon. Swap parent and leftSon, leftSon's rightSon becomes parent's leftSon. + * @param vertex `RBVertex` type; The vertex on which the right rotation is to be performed. + */ + private fun rotateRight(vertex: RBVertex) { + val leftVertex: RBVertex? = vertex.leftSon + vertex.leftSon = leftVertex?.rightSon + leftVertex?.rightSon.let { leftVertex?.rightSon?.parent = vertex } + leftVertex?.parent = vertex.parent + when { + vertex.parent == null -> root = leftVertex + vertex == vertex.parent?.leftSon -> vertex.parent?.leftSon = leftVertex + else -> vertex.parent?.rightSon = leftVertex + } + vertex.parent = leftVertex + leftVertex?.rightSon = vertex + } + + /** + * Constructs a new binary search tree with the specified comparator + * + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(comparator: Comparator? = null) { + this.comparator = comparator + } + + /** + * Constructs a new binary search tree and puts all key-value pairs from the specified map to this tree + * + * @param map `Map` type + * @param replaceIfExists `Boolean` type. + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + * Supplied only if a [comparator] is present. If comparator is `null`, the value is replaced + * by the last value from the key-value pair in the map, where the key is the one already existing in the tree. + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(map: Map, replaceIfExists: Boolean = true, comparator: Comparator? = null) { + this.comparator = comparator + putAll(map, replaceIfExists) + } +} diff --git a/lib/src/main/kotlin/trees/SimpleBinarySearchTree.kt b/lib/src/main/kotlin/trees/SimpleBinarySearchTree.kt new file mode 100644 index 0000000..b821a80 --- /dev/null +++ b/lib/src/main/kotlin/trees/SimpleBinarySearchTree.kt @@ -0,0 +1,169 @@ +package trees + +import vertexes.SimpleBSTVertex + +/** + * Simple implementation of a binary search tree. + * + * It extends [AbstractBinarySearchTree] and uses [SimpleBSTVertex] as vertices. + * + * If the tree has an incomparable key type and comparator is `null`, if the tree + * is non-empty, when trying to call the search, insert and delete methods, the tree + * will remain unchanged, the operation throws an exception with the message "Key's + * type is incomparable and comparator was not given". + * + * @param K key type + * @param V value type + * @property comparator `Comparator?` type; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + * @property size `Long` type; number of key-value pairs in this tree + * @property root `SimpleBSTVertex?` type, `null` by default + */ +open class SimpleBinarySearchTree : AbstractBinarySearchTree> { + /** + * Puts a new vertex of a certain type into the tree with a given key and value + * + * @param key `K` type + * @param value `V` type + * @param replaceIfExists `Boolean` type, + * + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + */ + override fun put( + key: K, + value: V, + replaceIfExists: Boolean, + ) { + putRec(key, value, replaceIfExists) + } + + /** + * Recursively inserts a key-value pair into the tree. + * + * @param key `K` type + * @param value `V` type + * @param replaceIfExists `Boolean` type, + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + * @param vertex `SimpleBSTVertex?` type, `root` by default; The current vertex in the recursion. + */ + private fun putRec( + key: K, + value: V, + replaceIfExists: Boolean, + vertex: SimpleBSTVertex? = root, + ) { + if (vertex == null) { + root = SimpleBSTVertex(key, value) + size++ + return + } + when (compareKeys(key, vertex.key)) { + 0 -> if (replaceIfExists) vertex.value = value + -1 -> { + if (vertex.leftSon == null) { + vertex.leftSon = SimpleBSTVertex(key, value) + size++ + } else { + putRec(key, value, replaceIfExists, vertex.leftSon) + } + } + + 1 -> { + if (vertex.rightSon == null) { + vertex.rightSon = SimpleBSTVertex(key, value) + size++ + } else { + putRec(key, value, replaceIfExists, vertex.rightSon) + } + } + } + } + + /** + * Removes the key-value pair associated with the specified key from the tree. + * + * @param key `K` type + * @return The value associated with the removed key, or null if the key is not found. + */ + override fun remove(key: K): V? { + val (_, deletedValue, isRemoved) = removeRec(key) + if (isRemoved) size-- + return deletedValue + } + + /** + * Recursively removes the key-value pair associated with the specified key from the tree. + * + * This method traverses the tree recursively to find the node with the given key and removes it. + * If the key is found and the corresponding node has both left and right children, + * the method replaces the node's key and value with those of the minimum key node in its right subtree, + * and then removes the minimum key node from the right subtree. + * If the key is not found, it returns a pair containing the vertex and null value. + * + * @param key `K` type + * @param vertex `SimpleBSTVertex?` type, `root` by default; The current vertex being examined in the recursion. + * @return A pair containing the updated vertex and the value associated with the removed key, or null if the key is not found. + */ + private fun removeRec( + key: K, + vertex: SimpleBSTVertex? = root, + ): Triple?, V?, Boolean> { + if (vertex == null) return Triple(null, null, false) + + when (compareKeys(key, vertex.key)) { + -1 -> { + val (updatedLeftSon, deletedValue, isRemoved) = removeRec(key, vertex.leftSon) + vertex.leftSon = updatedLeftSon + return Triple(vertex, deletedValue, isRemoved) + } + + 1 -> { + val (updatedRightSon, deletedValue, isRemoved) = removeRec(key, vertex.rightSon) + vertex.rightSon = updatedRightSon + return Triple(vertex, deletedValue, isRemoved) + } + + else -> { + val deletedValue: V = vertex.value + if (vertex.leftSon == null || vertex.rightSon == null) { + if (vertex.leftSon == null) { + if (vertex == root) root = root?.rightSon + return Triple(vertex.rightSon, deletedValue, true) + } + if (vertex == root) root = root?.leftSon + return Triple(vertex.leftSon, deletedValue, true) + } + val minKeyRightSubtreeNode: SimpleBSTVertex? = getMinKeyNodeRec(vertex.rightSon) + minKeyRightSubtreeNode?.let { + vertex.key = it.key + vertex.value = it.value + val (updatedRightSon, _, _) = removeRec(it.key, vertex.rightSon) + vertex.rightSon = updatedRightSon + } + return Triple(vertex, deletedValue, true) + } + } + } + + /** + * Constructs a new binary search tree with the specified comparator + * + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(comparator: Comparator? = null) : super(comparator) + + /** + * Constructs a new binary search tree and puts all key-value pairs from the specified map to this tree + * + * @param map `Map` type + * @param replaceIfExists `Boolean` type. + * If `true` - replaces the value if the key already exists. If `false` - ignores it. + * Supplied only if a [comparator] is present. If comparator is `null`, the value is replaced + * by the last value from the key-value pair in the map, where the key is the one already existing in the tree. + * @param comparator `Comparator?` type, `null `by default; used optionally to compare keys in a tree. If `null`, it is expected that keys of comparable type. + */ + constructor(map: Map, replaceIfExists: Boolean = true, comparator: Comparator? = null) : super( + map, + replaceIfExists, + comparator, + ) +} diff --git a/lib/src/main/kotlin/vertexes/AVLVertex.kt b/lib/src/main/kotlin/vertexes/AVLVertex.kt new file mode 100644 index 0000000..05c298a --- /dev/null +++ b/lib/src/main/kotlin/vertexes/AVLVertex.kt @@ -0,0 +1,47 @@ +package vertexes + +/** + * Represents a vertex in an AVL tree + * + * @param K key type + * @param V value type + * @property key + * @property value + * @property leftSon `AVLVertex?` type, + * @property rightSon `AVLVertex?` type + * @property sonsHeightDiff 'Int' type, difference in height between the left and right subtrees + */ + +class AVLVertex( + override var key: K, + override var value: V, +) : InterfaceBSTVertex> { + override var leftSon: AVLVertex? = null + override var rightSon: AVLVertex? = null + + /** + * The difference in height between the left and right subtrees. + */ + var sonsHeightDiff: Int = 0 + + /** + * Constructs vertex for AVL tree with the specified key and value + * + * @param key `K` type + * @param value `V` type + * @param leftSon `AVLVertex?` type + * @param rightSon `AVLVertex?` type + * @param sonsHeightDiff 'Int' type, 0 by default; difference in height between the left and right subtrees + */ + constructor( + key: K, + value: V, + leftSon: AVLVertex?, + rightSon: AVLVertex?, + sonsHeightDiff: Int = 0, + ) : this(key, value) { + this.leftSon = leftSon + this.rightSon = rightSon + this.sonsHeightDiff = sonsHeightDiff + } +} diff --git a/lib/src/main/kotlin/vertexes/InterfaceBSTVertex.kt b/lib/src/main/kotlin/vertexes/InterfaceBSTVertex.kt new file mode 100644 index 0000000..bce3946 --- /dev/null +++ b/lib/src/main/kotlin/vertexes/InterfaceBSTVertex.kt @@ -0,0 +1,19 @@ +package vertexes + +/** + * Represents a generic vertex in a binary search tree + * + * @param K key type + * @param V value type + * @param N child vertices type + * @property key + * @property value + * @property leftSon `N?` type + * @property rightSon `N?` type + */ +interface InterfaceBSTVertex { + var key: K + var value: V + var leftSon: N? + var rightSon: N? +} diff --git a/lib/src/main/kotlin/vertexes/RBVertex.kt b/lib/src/main/kotlin/vertexes/RBVertex.kt new file mode 100644 index 0000000..af5602e --- /dev/null +++ b/lib/src/main/kotlin/vertexes/RBVertex.kt @@ -0,0 +1,50 @@ +package vertexes + +/** + * Represents a vertex in a Red-Black Tree. + * @param K Type of keys. + * @param V Type of values. + * @property key The key associated with this vertex. + * @property value The value associated with this vertex. + * @property color The color of this vertex (red or black). + * @property parent The parent vertex of this vertex. + * @property leftSon The left child vertex of this vertex. + * @property rightSon The right child vertex of this vertex. + */ +class RBVertex( + override var key: K, + override var value: V, +) : InterfaceBSTVertex> { + enum class Color { + RED, + BLACK, + } + + var color: Color = Color.RED + var parent: RBVertex? = null + override var leftSon: RBVertex? = null + override var rightSon: RBVertex? = null + + /** + * Creates a new RBVertex with the specified parameters. + * @param key The key associated with this vertex. + * @param value The value associated with this vertex. + * @param leftSon The left child vertex of this vertex. + * @param rightSon The right child vertex of this vertex. + * @param color The color of this vertex (red or black). + * @param parent The parent vertex of this vertex. + */ + constructor( + key: K, + value: V, + leftSon: RBVertex?, + rightSon: RBVertex?, + color: Color, + parent: RBVertex?, + ) : this(key, value) { + this.leftSon = leftSon + this.rightSon = rightSon + this.parent = parent + this.color = color + } +} diff --git a/lib/src/main/kotlin/vertexes/SimpleBSTVertex.kt b/lib/src/main/kotlin/vertexes/SimpleBSTVertex.kt new file mode 100644 index 0000000..2107434 --- /dev/null +++ b/lib/src/main/kotlin/vertexes/SimpleBSTVertex.kt @@ -0,0 +1,37 @@ +package vertexes + +/** + * Represents a simple vertex in a binary search tree + * + * @param K key type + * @param V value type + * @property key + * @property value + * @property leftSon `SimpleBSTVertex?` type, `null` by default + * @property rightSon `SimpleBSTVertex?` type, `null` by default + */ +class SimpleBSTVertex( + override var key: K, + override var value: V, +) : InterfaceBSTVertex> { + override var leftSon: SimpleBSTVertex? = null + override var rightSon: SimpleBSTVertex? = null + + /** + * Constructs a simple vertex with the specified key and value + * + * @param key `K` type + * @param value `V` type + * @param leftSon `SimpleBSTVertex?` type + * @param rightSon `SimpleBSTVertex?` type + */ + constructor( + key: K, + value: V, + leftSon: SimpleBSTVertex?, + rightSon: SimpleBSTVertex?, + ) : this(key, value) { + this.leftSon = leftSon + this.rightSon = rightSon + } +} diff --git a/lib/src/test/kotlin/iterator/IteratorTests.kt b/lib/src/test/kotlin/iterator/IteratorTests.kt new file mode 100644 index 0000000..508d05b --- /dev/null +++ b/lib/src/test/kotlin/iterator/IteratorTests.kt @@ -0,0 +1,48 @@ +package iterator + +import org.junit.jupiter.api.Assertions.assertEquals +import trees.AVLSearchTree +import vertexes.SimpleBSTVertex +import kotlin.test.Test + +class IteratorTests { + @Test + fun `add in stack`() { + val vertex = SimpleBSTVertex(1, "one") + val iterator = TestIterator(vertex) + assertEquals(1, iterator.getTreeStack().removeLast().key) + } + + @Test + fun `hasNext if stack is not empty`() { + val vertex = SimpleBSTVertex(1, "one") + val iterator = TestIterator(vertex) + assertEquals(true, iterator.hasNext()) + } + + @Test + fun `check if method next() works correctly`() { + val vertex = SimpleBSTVertex(1, "one") + val iterator = TestIterator(vertex) + val deletedVertex = iterator.next() + assertEquals(Pair(1, "one"), deletedVertex) + } + + @Test + fun `hasNext if stack is empty`() { + val vertex = SimpleBSTVertex(1, "one") + val iterator = TestIterator(vertex) + iterator.next() + assertEquals(false, iterator.hasNext()) + } + + @Test + fun `check if method iterator() works correctly`() { + val tree = AVLSearchTree(mapOf(Pair(3, "three"), Pair(1, "one"), Pair(2, "two"), Pair(150, "one-five-zero"))) + val list: MutableList> = mutableListOf() + for (pair in tree) { + list.add(pair) + } + assertEquals(mutableListOf(Pair(2, "two"), Pair(3, "three"), Pair(150, "one-five-zero"), Pair(1, "one")), list) + } +} diff --git a/lib/src/test/kotlin/iterator/TestIterator.kt b/lib/src/test/kotlin/iterator/TestIterator.kt new file mode 100644 index 0000000..d42d08d --- /dev/null +++ b/lib/src/test/kotlin/iterator/TestIterator.kt @@ -0,0 +1,10 @@ +package iterator + +import vertexes.InterfaceBSTVertex +import java.util.Stack + +internal class TestIterator>(vertex: N?) : TreeIterator(vertex) { + fun getTreeStack(): Stack { + return stack + } +} diff --git a/lib/src/test/kotlin/trees/abstractTree/AbstractTreeTest.kt b/lib/src/test/kotlin/trees/abstractTree/AbstractTreeTest.kt new file mode 100644 index 0000000..02e80d2 --- /dev/null +++ b/lib/src/test/kotlin/trees/abstractTree/AbstractTreeTest.kt @@ -0,0 +1,260 @@ +package trees.abstractTree + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import vertexes.SimpleBSTVertex + +class AbstractTreeTest { + private fun makeEmptyTree(): TestTree { + return TestTree() + } + + @Test + fun `isEmpty() returns true if tree is empty `() { + val tree = makeEmptyTree() + assert(tree.isEmpty()) + } + + @Test + fun `isNotEmpty() returns false if tree is empty `() { + val tree = makeEmptyTree() + assertFalse(tree.isNotEmpty()) + } + + @Test + fun `size() returns 0 if tree is empty`() { + val tree = makeEmptyTree() + assert(tree.size() == 0L) + } + + @Test + fun `get() returns null if tree is empty`() { + val tree = makeEmptyTree() + assertNull(tree.get(intArrayOf(0))) + } + + @Test + fun `getMax() returns null if tree is empty`() { + val tree = makeEmptyTree() + assertNull(tree.getMax()) + } + + @Test + fun `getMin() returns null if tree is empty`() { + val tree = makeEmptyTree() + assertNull(tree.getMin()) + } + + @Test + fun `getMaxKeyPair() returns null if tree is empty`() { + val tree = makeEmptyTree() + assertNull(tree.getMaxKeyPair()) + } + + @Test + fun `getMinKeyPair() returns null if tree is empty`() { + val tree = makeEmptyTree() + assertNull(tree.getMinKeyPair()) + } + + private fun makeTreeWithBothRootSSons(): TestTree { + val leftSon = SimpleBSTVertex('1', "!", SimpleBSTVertex('0', ")"), null) + val rightSon = SimpleBSTVertex('4', "$", SimpleBSTVertex('3', "#"), null) + return TestTree(SimpleBSTVertex('2', "@", leftSon, rightSon), 5L) + } + + @Test + fun `isEmpty() returns false if tree is not empty `() { + val tree = makeTreeWithBothRootSSons() + assertFalse(tree.isEmpty()) + } + + @Test + fun `isNotEmpty() returns true if tree is not empty `() { + val tree = makeTreeWithBothRootSSons() + assert(tree.isNotEmpty()) + } + + @Test + fun `size() returns not 0 if tree is not empty`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.size() != 0L) + } + + @Test + fun `get() returns null when tree doesn't contains given key`() { + val tree = makeTreeWithBothRootSSons() + assertNull(tree.get('z')) + } + + @Test + fun `getPair() returns null when tree doesn't contains given key`() { + val tree = makeTreeWithBothRootSSons() + assertNull(tree.getPair('z')) + } + + @Test + fun `get() returns correct value when tree contains given key in left subtree`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.get('1') == "!") + } + + @Test + fun `getPair() returns correct value when tree contains given key in left subtree`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.getPair('1') == ('1' to "!")) + } + + @Test + fun `get() returns correct value when tree contains given key in right subtree`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.get('4') == "$") + } + + @Test + fun `get() returns correct value when root's key was given`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.get('2') == "@") + } + + @Test + fun `get() returns correct value when leaf's key was given`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.get('3') == "#") + } + + @Test + fun `getMin() returns correct value when root has two sons`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.getMin() == ")") + } + + @Test + fun `getMax() returns correct value when root has two sons`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.getMax() == "$") + } + + @Test + fun `getMinKeyPair() returns correct value when root has two sons`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.getMinKeyPair() == ('0' to ")")) + } + + @Test + fun `getMaxKeyPair() returns correct value when root has two sons`() { + val tree = makeTreeWithBothRootSSons() + assert(tree.getMaxKeyPair() == ('4' to "$")) + } + + private fun makeTreeWithOnlyLeftRootSSon(): TestTree { + val leftSon = SimpleBSTVertex('1', "!", SimpleBSTVertex('0', ")"), SimpleBSTVertex('2', "@")) + return TestTree(SimpleBSTVertex('3', "#", leftSon, null), 4L) + } + + @Test + fun `getMax() returns correct value when root has only left son`() { + val tree = makeTreeWithOnlyLeftRootSSon() + assert(tree.getMax() == "#") + } + + private fun makeTreeWithOnlyRightRootSSon(): TestTree { + val rightSon = SimpleBSTVertex('6', "^", SimpleBSTVertex('5', "%"), SimpleBSTVertex('8', "*")) + return TestTree(SimpleBSTVertex('3', "#", null, rightSon), 4L) + } + + @Test + fun `getMin() returns correct value when root has only right son`() { + val tree = makeTreeWithOnlyRightRootSSon() + assert(tree.getMin() == "#") + } + + @Test + fun `removeAndReturnPair() returns null when remove() returned null`() { + val tree = TestTree(removeShouldReturns = null) + assertNull(tree.removeAndReturnPair(1)) + } + + @Test + fun `removeAndReturnPair() returns (given key) to (value that remove() returned) pair`() { + val tree = TestTree(removeShouldReturns = '1') + assert(tree.removeAndReturnPair(3) == (3 to '1')) + } + + @Test + fun `putAll() do correct put() call for each map element (1)`() { + val tree = TestTree() + val map = hashMapOf('a' to 'A', 'b' to 'B', 'c' to 'C', 'a' to 'A') + tree.putAll(map) + for (pair in map) + assertNotNull(tree.putWasCalledWithParams.remove(Triple(pair.component1(), pair.component2(), true))) + } + + @Test + fun `putAll() do correct put() call for each map element (2)`() { + val tree = TestTree() + val map = hashMapOf('a' to 'A', 'b' to 'B', 'c' to 'C', 'a' to 'A') + tree.putAll(map, false) + for (pair in map) + assertNotNull(tree.putWasCalledWithParams.remove(Triple(pair.component1(), pair.component2(), false))) + } + + @Test + fun `compareKeys return 1 when key1 larger than key2 (keys are comparable)`() { + val tree = TestTree() + assert(tree.compareKeysT(18, 14) == 1) + } + + @Test + fun `compareKeys return 0 when key1 equals key2 (keys are comparable)`() { + val tree = TestTree() + assert(tree.compareKeysT(18, 18) == 0) + } + + @Test + fun `compareKeys return -1 when key1 lesser than key2 (keys are comparable)`() { + val tree = TestTree() + assert(tree.compareKeysT(14, 18) == -1) + } + + class CMP : Comparator { + override fun compare( + o1: IntArray, + o2: IntArray, + ): Int { + return o1.sum() - o2.sum() + } + } + + @Test + fun `compareKeys return 1 when key1 larger than key2 (comparator was given)`() { + val tree = TestTree(CMP()) + assert(tree.compareKeysT(intArrayOf(-18, 18), intArrayOf(-1)) == 1) + } + + @Test + fun `compareKeys return 0 when key1 equals key2 (comparator was given)`() { + val tree = TestTree(CMP()) + assert(tree.compareKeysT(intArrayOf(-18, 18), intArrayOf(0)) == 0) + } + + @Test + fun `compareKeys return -1 when key1 lesser than key2 (comparator was given)`() { + val tree = TestTree(CMP()) + assert(tree.compareKeysT(intArrayOf(-18, 18), intArrayOf(1)) == -1) + } + + @Test + fun `compareKeys fall when keys type doesn't implement comparable && comparator wasn't given`() { + var didItFall = false + val tree = TestTree() + try { + tree.compareKeysT(intArrayOf(-18, 18), intArrayOf(1)) + } catch (e: Exception) { + didItFall = true + } + assert(didItFall) + } +} diff --git a/lib/src/test/kotlin/trees/abstractTree/TestTree.kt b/lib/src/test/kotlin/trees/abstractTree/TestTree.kt new file mode 100644 index 0000000..9974358 --- /dev/null +++ b/lib/src/test/kotlin/trees/abstractTree/TestTree.kt @@ -0,0 +1,47 @@ +package trees.abstractTree + +import trees.AbstractBinarySearchTree +import vertexes.SimpleBSTVertex + +class TestTree : AbstractBinarySearchTree> { + var removeShouldReturns: V? = null + var getShouldReturns: V? = null + val putWasCalledWithParams: MutableList> = mutableListOf() + + override fun put( + key: K, + value: V, + replaceIfExists: Boolean, + ) { + putWasCalledWithParams.add(Triple(key, value, replaceIfExists)) + } + + override fun remove(key: K): V? { + return removeShouldReturns + } + + fun compareKeysT( + firstKey: K, + secondKey: K, + ): Int { + return super.compareKeys(firstKey, secondKey) + } + + constructor (root: SimpleBSTVertex, size: Long, comparator: Comparator? = null) : + super(comparator) { + this.root = root + this.size = size + } + + constructor (removeShouldReturns: V?) : super() { + this.removeShouldReturns = removeShouldReturns + } + + constructor (comparator: Comparator? = null) : super(comparator) + + constructor ( + map: Map, + replaceIfExists: Boolean = true, + comparator: Comparator? = null, + ) : super(map, replaceIfExists, comparator) +} diff --git a/lib/src/test/kotlin/trees/avlTree/AVLTreeForTest.kt b/lib/src/test/kotlin/trees/avlTree/AVLTreeForTest.kt new file mode 100644 index 0000000..7266073 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/AVLTreeForTest.kt @@ -0,0 +1,79 @@ +package trees.avlTree + +import trees.AVLSearchTree +import vertexes.AVLVertex +import kotlin.math.max + +class AVLTreeForTest : AVLSearchTree { + fun getRootT(): AVLVertex? { + return root + } + + fun getVertexesInDFSOrder(): MutableList> { + val vertexes: MutableList> = mutableListOf() + getVertexesRec(root, vertexes) + return vertexes + } + + private fun getVertexesRec( + curVertex: AVLVertex?, + vrtList: MutableList>, + ) { + if (curVertex == null) return + vrtList.add(curVertex) + getVertexesRec(curVertex.leftSon, vrtList) + getVertexesRec(curVertex.rightSon, vrtList) + } + + fun getHeights(): MutableMap { + val heights: MutableMap = mutableMapOf() + if (root == null) return heights + getHeightsRec(root as AVLVertex, heights) + return heights + } + + private fun getHeightsRec( + rootOfSubtree: AVLVertex, + heights: MutableMap, + ): Int { + return when ((rootOfSubtree.leftSon == null) to (rootOfSubtree.rightSon == null)) { + true to true -> { + heights.put(rootOfSubtree.key, 0) + 0 + } + + true to false -> { + val height = getHeightsRec(rootOfSubtree.rightSon as AVLVertex, heights) + 1 + heights.put(rootOfSubtree.key, height) + height + } + + false to true -> { + val height = getHeightsRec(rootOfSubtree.leftSon as AVLVertex, heights) + 1 + heights.put(rootOfSubtree.key, height) + height + } + + else -> { + val heightOfLeftSubtree = getHeightsRec(rootOfSubtree.leftSon as AVLVertex, heights) + val heightOfRightSubtree = getHeightsRec(rootOfSubtree.rightSon as AVLVertex, heights) + val height = max(heightOfLeftSubtree, heightOfRightSubtree) + 1 + heights.put(rootOfSubtree.key, height) + max(heightOfLeftSubtree, heightOfRightSubtree) + 1 + } + } + } + + constructor (root: AVLVertex, size: Long, comparator: Comparator? = null) : super(comparator) { + this.root = root + this.size = size + } + + constructor (comparator: Comparator? = null) : super(comparator) + + constructor( + map: Map, + replaceIfExists: Boolean = true, + comparator: Comparator? = null, + ) : super(map, replaceIfExists, comparator) +} diff --git a/lib/src/test/kotlin/trees/avlTree/AuxiliaryFunctions.kt b/lib/src/test/kotlin/trees/avlTree/AuxiliaryFunctions.kt new file mode 100644 index 0000000..3e932ea --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/AuxiliaryFunctions.kt @@ -0,0 +1,60 @@ +package trees.avlTree + +import vertexes.AVLVertex + +object AuxiliaryFunctions { + fun isTreeConsistsOf( + expectedContent: Set>, + tree: AVLTreeForTest, + ): Boolean { + val vertexes = tree.getVertexesInDFSOrder() + val pairsFromVertexes = (Array(vertexes.size) { i -> (vertexes[i].key to vertexes[i].value) }).toSet() + return pairsFromVertexes == expectedContent + } + + fun isTreeSStructureThat( + tree: AVLTreeForTest, + order: Array, + deps: List>, + ): Boolean { + // Triple consists of indexes in order of (1)vertex, one's (2)leftSon and (3)RightSon + val vertexes = tree.getVertexesInDFSOrder() + if (vertexes.size != order.size) return false + for (i in order.indices) + if (order[i] != vertexes[i].key) return false + for (dep in deps) { + if (dep.component2() != null) { + if (vertexes[dep.component1()].leftSon != vertexes[dep.component2() as Int]) { + return false + } + } + if (dep.component3() != null) { + if (vertexes[dep.component1()].rightSon != vertexes[dep.component3() as Int]) { + return false + } + } + } + return true + } + + fun isSonsHeightDiffCorrect(tree: AVLTreeForTest): Boolean { + val vertexes = tree.getVertexesInDFSOrder() + val heights = tree.getHeights() + if (vertexes.size != heights.size) return false + for (vertex in vertexes) { + val expectedSonsHeightDiff = + when ((vertex.leftSon == null) to (vertex.rightSon == null)) { + true to true -> 0 + true to false -> -1 - (heights[(vertex.rightSon as AVLVertex).key] ?: return false) + false to true -> 1 + (heights[(vertex.leftSon as AVLVertex).key] ?: return false) + else -> { + val heightOfLeftSubtree = heights[(vertex.leftSon as AVLVertex).key] ?: return false + val heightOfRightSubtree = heights[(vertex.rightSon as AVLVertex).key] ?: return false + heightOfLeftSubtree - heightOfRightSubtree + } + } + if (expectedSonsHeightDiff != vertex.sonsHeightDiff) return false + } + return true + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/putTest/HaveToDoBigRotation.kt b/lib/src/test/kotlin/trees/avlTree/putTest/HaveToDoBigRotation.kt new file mode 100644 index 0000000..7beabd4 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/putTest/HaveToDoBigRotation.kt @@ -0,0 +1,222 @@ +package trees.avlTree.putTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class HaveToDoBigRotation { + private fun makeTreeForHaveToDoBigLeftRotationPutTest(): AVLTreeForTest { + val rightSonSRightSon = AVLVertex('i', 'I', AVLVertex('g', 'G'), AVLVertex('j', 'J')) + val rightSon = AVLVertex('e', 'E', AVLVertex('d', 'D'), rightSonSRightSon, -1) + val root = AVLVertex('c', 'C', AVLVertex('b', 'B', AVLVertex('a', 'A'), null, 1), rightSon, -1) + return AVLTreeForTest(root, 8L) + } + + // (1) - add grandson's right son, (2) add grandson's left son + @Test + fun `content is correct after entry was added (big left rotation) (1)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('f', 'F') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'd' to 'D', + 'e' to 'E', + 'g' to 'G', + 'f' to 'F', + 'i' to 'I', + 'j' to 'J', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `content is correct after entry was added (big left rotation) (2)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('h', 'H') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'd' to 'D', + 'e' to 'E', + 'h' to 'H', + 'g' to 'G', + 'i' to 'I', + 'j' to 'J', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size increased by 1 after entry was added (big left rotation) (1)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('f', 'F') + assert(tree.size() == 9L) + } + + @Test + fun `size increased by 1 after entry was added (big left rotation) (2)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('h', 'H') + assert(tree.size() == 9L) + } + + @Test + fun `structure is correct after entry was added (big left rotation) (1)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('f', 'F') + val expectedDependencies = + listOf( + Triple(0, 1, 3), + Triple(1, 2, null), + Triple(3, 4, 7), + Triple(4, 5, 6), + Triple(7, null, 8), + ) + val expectedOrder = arrayOf('c', 'b', 'a', 'g', 'e', 'd', 'f', 'i', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `structure is correct after entry was added (big left rotation) (2)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('h', 'H') + val expectedDependencies = + listOf( + Triple(0, 1, 3), + Triple(1, 2, null), + Triple(3, 4, 6), + Triple(4, 5, null), + Triple(6, 7, 8), + ) + val expectedOrder = arrayOf('c', 'b', 'a', 'g', 'e', 'd', 'i', 'h', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added (big left rotation) (1)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('f', 'F') + assert(isSonsHeightDiffCorrect(tree)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added (big left rotation) (2)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('h', 'H') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForHaveToDoBigRightRotationPutTest(): AVLTreeForTest { + val leftSonSLeftSon = AVLVertex('b', 'B', AVLVertex('a', 'A'), AVLVertex('d', 'D')) + val leftSon = AVLVertex('f', 'F', leftSonSLeftSon, AVLVertex('g', 'G'), 1) + val root = AVLVertex('h', 'H', leftSon, AVLVertex('i', 'I', null, AVLVertex('j', 'J'), -1), 1) + return AVLTreeForTest(root, 8L) + } + + // (1) - add grandson's left son, (2) add grandson's right son + @Test + fun `content is correct after entry was added (big right rotation) (1)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('c', 'C') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'd' to 'D', + 'h' to 'H', + 'g' to 'G', + 'f' to 'F', + 'i' to 'I', + 'j' to 'J', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `content is correct after entry was added (big right rotation) (2)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('e', 'E') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'f' to 'F', + 'd' to 'D', + 'e' to 'E', + 'h' to 'H', + 'g' to 'G', + 'i' to 'I', + 'j' to 'J', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size increased by 1 after entry was added (big right rotation) (1)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('c', 'C') + assert(tree.size() == 9L) + } + + @Test + fun `size increased by 1 after entry was added (big right rotation) (2)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('e', 'E') + assert(tree.size() == 9L) + } + + @Test + fun `structure is correct after entry was added (big right rotation) (1)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('c', 'C') + val expectedDependencies = + listOf( + Triple(0, 1, 7), + Triple(7, null, 8), + Triple(1, 2, 5), + Triple(5, null, 6), + Triple(2, 3, 4), + ) + val expectedOrder = arrayOf('h', 'd', 'b', 'a', 'c', 'f', 'g', 'i', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `structure is correct after entry was added (big right rotation) (2)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('e', 'E') + val expectedDependencies = + listOf( + Triple(0, 1, 7), + Triple(7, null, 8), + Triple(1, 2, 4), + Triple(2, 3, null), + Triple(4, 5, 6), + ) + val expectedOrder = arrayOf('h', 'd', 'b', 'a', 'f', 'e', 'g', 'i', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added (big right rotation) (1)`() { + val tree = makeTreeForHaveToDoBigRightRotationPutTest() + tree.put('c', 'C') + assert(isSonsHeightDiffCorrect(tree)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added (big right rotation) (2)`() { + val tree = makeTreeForHaveToDoBigLeftRotationPutTest() + tree.put('e', 'E') + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/putTest/HaveToRotateLeft.kt b/lib/src/test/kotlin/trees/avlTree/putTest/HaveToRotateLeft.kt new file mode 100644 index 0000000..afc2ac4 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/putTest/HaveToRotateLeft.kt @@ -0,0 +1,103 @@ +package trees.avlTree.putTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class HaveToRotateLeft { + private fun makeTreeForHaveToRotateLeftPutTest(): AVLTreeForTest { + val rightSonSRightSon = AVLVertex('h', 'H', AVLVertex('f', 'F'), AVLVertex('i', 'I')) + val rightSon = AVLVertex('e', 'E', AVLVertex('d', 'D'), rightSonSRightSon, -1) + val root = AVLVertex('c', 'C', AVLVertex('b', 'B', AVLVertex('a', 'A'), null, 1), rightSon, -1) + return AVLTreeForTest(root, 8L) + } + + @Test + fun `content is correct after entry was added`() { + val tree = makeTreeForHaveToRotateLeftPutTest() + tree.put('j', 'J') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'd' to 'D', + 'e' to 'E', + 'h' to 'H', + 'f' to 'F', + 'i' to 'I', + 'j' to 'J', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size increased by 1 after entry was added`() { + val tree = makeTreeForHaveToRotateLeftPutTest() + tree.put('j', 'J') + assert(tree.size() == 9L) + } + + @Test + fun `structure is correct after entry was added`() { + val tree = makeTreeForHaveToRotateLeftPutTest() + tree.put('j', 'J') + val expectedDependencies = + listOf( + Triple(0, 1, 3), + Triple(1, 2, null), + Triple(3, 4, 7), + Triple(4, 5, 6), + Triple(7, null, 8), + ) + val expectedOrder = arrayOf('c', 'b', 'a', 'h', 'e', 'd', 'f', 'i', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added`() { + val tree = makeTreeForHaveToRotateLeftPutTest() + tree.put('j', 'J') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForHaveToChangeRootByRotateLeftPutTest(): AVLTreeForTest { + val rightSon = AVLVertex('h', 'H', AVLVertex('f', 'F'), AVLVertex('i', 'I')) + val root = AVLVertex('e', 'E', AVLVertex('d', 'D'), rightSon, -1) + return AVLTreeForTest(root, 5L) + } + + @Test + fun `content is correct after entry was added (rotateLeft changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateLeftPutTest() + tree.put('j', 'J') + val expectedContent = setOf('d' to 'D', 'e' to 'E', 'h' to 'H', 'f' to 'F', 'i' to 'I', 'j' to 'J') + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size increased by 1 after entry was added (rotateLeft changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateLeftPutTest() + tree.put('j', 'J') + assert(tree.size() == 6L) + } + + @Test + fun `structure is correct after entry was added (rotateLeft changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateLeftPutTest() + tree.put('j', 'J') + val expectedDependencies = listOf(Triple(0, 1, 4), Triple(1, 2, 3), Triple(4, null, 5)) + val expectedOrder = arrayOf('h', 'e', 'd', 'f', 'i', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added (rotateLeft changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateLeftPutTest() + tree.put('j', 'J') + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/putTest/HaveToRotateRight.kt b/lib/src/test/kotlin/trees/avlTree/putTest/HaveToRotateRight.kt new file mode 100644 index 0000000..a03fb71 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/putTest/HaveToRotateRight.kt @@ -0,0 +1,103 @@ +package trees.avlTree.putTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class HaveToRotateRight { + private fun makeTreeForHaveToRotateRightPutTest(): AVLTreeForTest { + val leftSonSLeftSon = AVLVertex('c', 'C', AVLVertex('b', 'B'), AVLVertex('d', 'D')) + val leftSon = AVLVertex('f', 'F', leftSonSLeftSon, AVLVertex('e', 'E'), 1) + val root = AVLVertex('h', 'H', leftSon, AVLVertex('i', 'I', null, AVLVertex('j', 'J'), -1), 1) + return AVLTreeForTest(root, 8L) + } + + @Test + fun `content is correct after entry was added `() { + val tree = makeTreeForHaveToRotateRightPutTest() + tree.put('a', 'A') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'd' to 'D', + 'e' to 'E', + 'h' to 'H', + 'f' to 'F', + 'i' to 'I', + 'j' to 'J', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size increased by 1 after entry was added`() { + val tree = makeTreeForHaveToRotateRightPutTest() + tree.put('a', 'A') + assert(tree.size() == 9L) + } + + @Test + fun `structure is correct after entry was added`() { + val tree = makeTreeForHaveToRotateRightPutTest() + tree.put('a', 'A') + val expectedDependencies = + listOf( + Triple(0, 1, 7), + Triple(1, 2, 4), + Triple(2, 3, null), + Triple(4, 5, 6), + Triple(7, null, 8), + ) + val expectedOrder = arrayOf('h', 'c', 'b', 'a', 'f', 'd', 'e', 'i', 'j') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added`() { + val tree = makeTreeForHaveToRotateRightPutTest() + tree.put('a', 'A') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForHaveToChangeRootByRotateRightPutTest(): AVLTreeForTest { + val leftSon = AVLVertex('c', 'C', AVLVertex('b', 'B'), AVLVertex('d', 'D')) + val root = AVLVertex('f', 'F', leftSon, AVLVertex('e', 'E'), 1) + return AVLTreeForTest(root, 5L) + } + + @Test + fun `content is correct after entry was added (rotateRight changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateRightPutTest() + tree.put('a', 'A') + val expectedContent = setOf('a' to 'A', 'b' to 'B', 'c' to 'C', 'd' to 'D', 'e' to 'E', 'f' to 'F') + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size increased by 1 after entry was added (rotateRight changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateRightPutTest() + tree.put('a', 'A') + assert(tree.size() == 6L) + } + + @Test + fun `structure is correct after entry was added (rotateRight changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateRightPutTest() + tree.put('a', 'A') + val expectedDependencies = listOf(Triple(0, 1, 3), Triple(1, 2, null), Triple(3, 4, 5)) + val expectedOrder = arrayOf('c', 'b', 'a', 'f', 'd', 'e') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after entry was added (rotateRight changes root)`() { + val tree = makeTreeForHaveToChangeRootByRotateRightPutTest() + tree.put('a', 'A') + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/putTest/Other.kt b/lib/src/test/kotlin/trees/avlTree/putTest/Other.kt new file mode 100644 index 0000000..2793296 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/putTest/Other.kt @@ -0,0 +1,27 @@ +package trees.avlTree.putTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import vertexes.AVLVertex + +class Other { + private fun makeSize1Tree(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex('b', '+'), 1L) + } + + @Test + fun `tree has no changes after entry with existed key and param replaceIfExists = false was added`() { + val tree = makeSize1Tree() + tree.put('b', '-', false) + assert(tree.size() == 1L) + assert(tree.getRootT()?.value == '+') + } + + @Test + fun `content is correct after init by map`() { + val tree = AVLTreeForTest(hashMapOf('a' to 'A', 'b' to 'B')) + val expectedContent = setOf('a' to 'A', 'b' to 'B') + assert(isTreeConsistsOf(expectedContent, tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/putTest/WithoutBalancing.kt b/lib/src/test/kotlin/trees/avlTree/putTest/WithoutBalancing.kt new file mode 100644 index 0000000..d8cbb0c --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/putTest/WithoutBalancing.kt @@ -0,0 +1,112 @@ +package trees.avlTree.putTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class WithoutBalancing { + private fun makeEmptyTree(): AVLTreeForTest { + return AVLTreeForTest() + } + + @Test + fun `entry became root after it was added to empty tree`() { + val tree = makeEmptyTree() + tree.put(0, '0') + val root = tree.getRootT() + assert((root?.key to root?.value) == (0 to '0')) + } + + @Test + fun `size equals 1 after entry was added to empty tree`() { + val tree = makeEmptyTree() + tree.put(0, '0') + assert(tree.size() == 1L) + } + + private fun makeSize1Tree(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex('b', '+'), 1L) // init tree by root and size (don't use put) + } + + @Test + fun `entry with larger key became right son after it was added to size 1 tree`() { + val tree = makeSize1Tree() + tree.put('c', '+') + val root = tree.getRootT() + assert((root?.rightSon?.key to root?.rightSon?.value) == ('c' to '+')) + } + + @Test + fun `entry with lesser key became left son after it was added to size 1 tree`() { + val tree = makeSize1Tree() + tree.put('a', '-') + val root = tree.getRootT() + assert((root?.leftSon?.key to root?.leftSon?.value) == ('a' to '-')) + } + + @Test + fun `root's sonsHeightDiff decreased by 1 after entry with larger key was added to size 1 tree`() { + val tree = makeSize1Tree() + tree.put('c', '+') + val root = tree.getRootT() + assert(root?.sonsHeightDiff == -1) + } + + @Test + fun `root's sonsHeightDiff increased by 1 after entry with lesser key was added to size 1 tree`() { + val tree = makeSize1Tree() + tree.put('a', '-') + val root = tree.getRootT() + assert(root?.sonsHeightDiff == 1) + } + + @Test + fun `size equals 2 after entry with larger key was added to size 1 tree`() { + val tree = makeSize1Tree() + tree.put('c', '+') + assert(tree.size() == 2L) + } + + @Test + fun `size equals 2 after entry with lesser key was added to size 1 tree`() { + val tree = makeSize1Tree() + tree.put('a', '-') + assert(tree.size() == 2L) + } + + private fun makeTreeForNeedNotBalancingPutTest(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex(4, 'A', AVLVertex(1, 'I'), AVLVertex(5, 'S')), 3L) + } + + @Test + fun `content is correct after entry was added`() { + val tree = makeTreeForNeedNotBalancingPutTest() + tree.put(0, 'O') + assert(isTreeConsistsOf(setOf(0 to 'O', 1 to 'I', 5 to 'S', 4 to 'A'), tree)) + } + + @Test + fun `size increased by 1 after entry was added to 'size 2+' tree`() { + val tree = makeTreeForNeedNotBalancingPutTest() + tree.put(0, 'O') + assert(tree.size() == 4L) + } + + @Test + fun `structure is correct after entry was added to 'size 2+' tree`() { + val tree = makeTreeForNeedNotBalancingPutTest() + tree.put(0, 'O') + val expectedDependencies = listOf(Triple(0, 1, 3), Triple(1, 2, null)) + assert(isTreeSStructureThat(tree, arrayOf(4, 1, 0, 5), expectedDependencies)) + } + + @Test + fun `vertexes' sonsHeightDiff are correct after entry was added to 'size 2+' tree`() { + val tree = makeTreeForNeedNotBalancingPutTest() + tree.put(0, 'O') + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToDoBigRotation.kt b/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToDoBigRotation.kt new file mode 100644 index 0000000..5ea88b7 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToDoBigRotation.kt @@ -0,0 +1,116 @@ +package trees.avlTree.removeTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class HaveToDoBigRotation { + private fun makeTreeForBigLeftRotationChangesRootRemoveTest(): AVLTreeForTest { + val rightSonSLeftSon = AVLVertex('d', 'D', AVLVertex('c', 'C'), AVLVertex('e', 'E')) + val rightSon = AVLVertex('f', 'F', rightSonSLeftSon, AVLVertex('g', 'G'), 1) + val root = AVLVertex('b', 'B', AVLVertex('a', 'A', AVLVertex(' ', ' '), null, 1), rightSon, -1) + return AVLTreeForTest(root, 8L) + } + + @Test + fun `remove returns due value after deletion (bigRotateLeft changes root)`() { + val tree = makeTreeForBigLeftRotationChangesRootRemoveTest() + assert(tree.remove(' ') == ' ') + } + + @Test + fun `content is correct after deletion (bigRotateLeft changes root)`() { + val tree = makeTreeForBigLeftRotationChangesRootRemoveTest() + tree.remove(' ') + val expectedContent = + setOf( + 'd' to 'D', + 'c' to 'C', + 'e' to 'E', + 'f' to 'F', + 'g' to 'G', + 'b' to 'B', + 'a' to 'A', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after deletion (bigRotateLeft changes root)`() { + val tree = makeTreeForBigLeftRotationChangesRootRemoveTest() + tree.remove(' ') + assert(tree.size() == 7L) + } + + @Test + fun `structure is correct after deletion (bigRotateLeft changes root)`() { + val tree = makeTreeForBigLeftRotationChangesRootRemoveTest() + tree.remove(' ') + val expectedDependencies = listOf(Triple(0, 1, 4), Triple(1, 2, 3), Triple(4, 5, 6)) + val expectedOrder = arrayOf('d', 'b', 'a', 'c', 'f', 'e', 'g') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (bigRotateLeft changes root)`() { + val tree = makeTreeForBigLeftRotationChangesRootRemoveTest() + tree.remove(' ') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForBigRightRotationChangesRootRemoveTest(): AVLTreeForTest { + val leftSonSRightSon = AVLVertex('e', 5, AVLVertex('c', 3), AVLVertex('d', 4)) + val leftSon = AVLVertex('b', 2, AVLVertex('a', 1), leftSonSRightSon, -1) + val root = AVLVertex('f', 8, leftSon, AVLVertex('i', 9, null, AVLVertex('k', 10), -1), 1) + return AVLTreeForTest(root, 8L) + } + + @Test + fun `remove returns due value after deletion (bigRotateRight changes root)`() { + val tree = makeTreeForBigRightRotationChangesRootRemoveTest() + assert(tree.remove('k') == 10) + } + + @Test + fun `content is correct after deletion (bigRotateRight changes root)`() { + val tree = makeTreeForBigRightRotationChangesRootRemoveTest() + tree.remove('k') + val expectedContent = + setOf( + 'a' to 1, + 'b' to 2, + 'c' to 3, + 'd' to 4, + 'e' to 5, + 'i' to 9, + 'f' to 8, + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after deletion (bigRotateRight changes root)`() { + val tree = makeTreeForBigRightRotationChangesRootRemoveTest() + tree.remove('k') + assert(tree.size() == 7L) + } + + @Test + fun `structure is correct after deletion (bigRotateRight changes root)`() { + val tree = makeTreeForBigRightRotationChangesRootRemoveTest() + tree.remove('k') + val expectedDependencies = listOf(Triple(0, 1, 4), Triple(1, 2, 3), Triple(4, 5, 6)) + val expectedOrder = arrayOf('e', 'b', 'a', 'c', 'f', 'd', 'i') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (bigRotateRight changes root)`() { + val tree = makeTreeForBigRightRotationChangesRootRemoveTest() + tree.remove('k') + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToRotateLeft.kt b/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToRotateLeft.kt new file mode 100644 index 0000000..a56fbd9 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToRotateLeft.kt @@ -0,0 +1,164 @@ +package trees.avlTree.removeTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class HaveToRotateLeft { + private fun makeTreeForRemoveWithLeftRotation1Test(): AVLTreeForTest { + val rightSonSRightSon = AVLVertex('o', false, null, AVLVertex('p', true), -1) + val rightSon = AVLVertex('m', true, AVLVertex('l', true), rightSonSRightSon, -1) + val root = AVLVertex('k', true, AVLVertex('i', false, AVLVertex('a', false), null, 1), rightSon, -1) + return AVLTreeForTest(root, 7L) + } + + private fun makeTreeForRemoveWithLeftRotation2Test(): AVLTreeForTest { + val rightSonSRightSon = AVLVertex('o', false, AVLVertex('n', true), AVLVertex('p', true), 0) + val rightSon = AVLVertex('m', true, AVLVertex('l', true), rightSonSRightSon, -1) + val root = AVLVertex('k', true, AVLVertex('i', false, AVLVertex('a', false), null, 1), rightSon, -1) + return AVLTreeForTest(root, 8L) + } + + // new subtree's root initially had sonSHeightDiff == -1 in (1) and 0 in (2) + @Test + fun `returns due value after deletion (1)`() { + val tree = makeTreeForRemoveWithLeftRotation1Test() + assert(tree.remove('l') == true) + } + + @Test + fun `returns due value after deletion (2)`() { + val tree = makeTreeForRemoveWithLeftRotation2Test() + assert(tree.remove('l') == true) + } + + @Test + fun `content is correct after deletion (1)`() { + val tree = makeTreeForRemoveWithLeftRotation1Test() + tree.remove('l') + val expectedContent = + setOf( + 'k' to true, + 'i' to false, + 'm' to true, + 'o' to false, + 'p' to true, + 'a' to false, + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `content is correct after deletion (2)`() { + val tree = makeTreeForRemoveWithLeftRotation2Test() + tree.remove('l') + val expectedContent = + setOf( + 'k' to true, + 'i' to false, + 'm' to true, + 'o' to false, + 'p' to true, + 'n' to true, + 'a' to false, + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after deletion (1)`() { + val tree = makeTreeForRemoveWithLeftRotation1Test() + tree.remove('l') + assert(tree.size() == 6L) + } + + @Test + fun `size decreased by 1 after deletion (2)`() { + val tree = makeTreeForRemoveWithLeftRotation2Test() + tree.remove('l') + assert(tree.size() == 7L) + } + + @Test + fun `structure is correct after deletion (1)`() { + val tree = makeTreeForRemoveWithLeftRotation1Test() + tree.remove('l') + val expectedDependencies = listOf(Triple(0, 1, 3), Triple(3, 4, 5), Triple(1, 2, null)) + val expectedOrder = arrayOf('k', 'i', 'a', 'o', 'm', 'p') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `structure is correct after deletion (2)`() { + val tree = makeTreeForRemoveWithLeftRotation2Test() + tree.remove('l') + val expectedDependencies = + listOf( + Triple(0, 1, 3), + Triple(3, 4, 6), + Triple(4, null, 5), + Triple(1, 2, null), + ) + val expectedOrder = arrayOf('k', 'i', 'a', 'o', 'm', 'n', 'p') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (1)`() { + val tree = makeTreeForRemoveWithLeftRotation1Test() + tree.remove('l') + assert(isSonsHeightDiffCorrect(tree)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (2)`() { + val tree = makeTreeForRemoveWithLeftRotation2Test() + tree.remove('l') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForLeftRotationChangesRootRemoveTest(): AVLTreeForTest { + val root = AVLVertex(1, 1, AVLVertex(0, 0), AVLVertex(3, 3, AVLVertex(2, 2), AVLVertex(5, 5)), -1) + return AVLTreeForTest(root, 5L) + } + + @Test + fun `returns due value after deletion (rotateLeft changes root)`() { + val tree = makeTreeForLeftRotationChangesRootRemoveTest() + assert(tree.remove(0) == 0) + } + + @Test + fun `content is correct after deletion (rotateLeft changes root)`() { + val tree = makeTreeForLeftRotationChangesRootRemoveTest() + tree.remove(0) + val expectedContent = setOf(1 to 1, 2 to 2, 3 to 3, 5 to 5) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after deletion (rotateLeft changes root)`() { + val tree = makeTreeForLeftRotationChangesRootRemoveTest() + tree.remove(0) + assert(tree.size() == 4L) + } + + @Test + fun `structure is correct after deletion (rotateLeft changes root)`() { + val tree = makeTreeForLeftRotationChangesRootRemoveTest() + tree.remove(0) + val expectedDependencies = listOf(Triple(0, 1, 3), Triple(1, null, 2)) + val expectedOrder = arrayOf(3, 1, 2, 5) + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (rotateLeft changes root)`() { + val tree = makeTreeForLeftRotationChangesRootRemoveTest() + tree.remove(0) + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToRotateRight.kt b/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToRotateRight.kt new file mode 100644 index 0000000..b30628b --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/removeTest/HaveToRotateRight.kt @@ -0,0 +1,164 @@ +package trees.avlTree.removeTest + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class HaveToRotateRight { + private fun makeTreeForRemoveWithRightRotation1Test(): AVLTreeForTest { + val leftSonSLeftSon = AVLVertex('b', true, AVLVertex('a', false), null, 1) + val leftSon = AVLVertex('d', true, leftSonSLeftSon, AVLVertex('e', false), 1) + val root = AVLVertex('k', true, leftSon, AVLVertex('m', true, null, AVLVertex('o', false), -1), 1) + return AVLTreeForTest(root, 7L) + } + + private fun makeTreeForRemoveWithRightRotation2Test(): AVLTreeForTest { + val leftSonSLeftSon = AVLVertex('b', true, AVLVertex('a', false), AVLVertex('c', true), 0) + val leftSon = AVLVertex('d', true, leftSonSLeftSon, AVLVertex('e', false), 1) + val root = AVLVertex('k', true, leftSon, AVLVertex('m', true, null, AVLVertex('o', false), -1), 1) + return AVLTreeForTest(root, 8L) + } + + // new subtree's root initially had sonSHeightDiff == 1 in (1) and 0 in (2) + @Test + fun `returns due value after deletion (1)`() { + val tree = makeTreeForRemoveWithRightRotation1Test() + assert(tree.remove('e') == false) + } + + @Test + fun `returns due value after deletion (2)`() { + val tree = makeTreeForRemoveWithRightRotation2Test() + assert(tree.remove('e') == false) + } + + @Test + fun `content is correct after deletion (1)`() { + val tree = makeTreeForRemoveWithRightRotation1Test() + tree.remove('e') + val expectedContent = + setOf( + 'k' to true, + 'b' to true, + 'm' to true, + 'o' to false, + 'd' to true, + 'a' to false, + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `content is correct after deletion (2)`() { + val tree = makeTreeForRemoveWithRightRotation2Test() + tree.remove('e') + val expectedContent = + setOf( + 'k' to true, + 'b' to true, + 'm' to true, + 'o' to false, + 'd' to true, + 'a' to false, + 'c' to true, + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after deletion (1)`() { + val tree = makeTreeForRemoveWithRightRotation1Test() + tree.remove('e') + assert(tree.size() == 6L) + } + + @Test + fun `size decreased by 1 after deletion (2)`() { + val tree = makeTreeForRemoveWithRightRotation2Test() + tree.remove('e') + assert(tree.size() == 7L) + } + + @Test + fun `structure is correct after deletion (1)`() { + val tree = makeTreeForRemoveWithRightRotation1Test() + tree.remove('e') + val expectedDependencies = listOf(Triple(0, 1, 4), Triple(1, 2, 3), Triple(4, null, 5)) + val expectedOrder = arrayOf('k', 'b', 'a', 'd', 'm', 'o') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `structure is correct after deletion (2)`() { + val tree = makeTreeForRemoveWithRightRotation2Test() + tree.remove('e') + val expectedDependencies = + listOf( + Triple(0, 1, 5), + Triple(1, 2, 3), + Triple(5, null, 6), + Triple(3, 4, null), + ) + val expectedOrder = arrayOf('k', 'b', 'a', 'd', 'c', 'm', 'o') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (1)`() { + val tree = makeTreeForRemoveWithRightRotation1Test() + tree.remove('e') + assert(isSonsHeightDiffCorrect(tree)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (2)`() { + val tree = makeTreeForRemoveWithRightRotation2Test() + tree.remove('e') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForRightRotationChangesRootRemoveTest(): AVLTreeForTest { + val root = AVLVertex(-1, 1, AVLVertex(-3, 3, AVLVertex(-5, 5), AVLVertex(-2, 2)), AVLVertex(0, 0), 1) + return AVLTreeForTest(root, 5L) + } + + @Test + fun `returns due value after deletion (rotateRight changes root)`() { + val tree = makeTreeForRightRotationChangesRootRemoveTest() + assert(tree.remove(0) == 0) + } + + @Test + fun `content is correct after deletion (rotateRight changes root)`() { + val tree = makeTreeForRightRotationChangesRootRemoveTest() + tree.remove(0) + val expectedContent = setOf(-1 to 1, -2 to 2, -3 to 3, -5 to 5) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after deletion (rotateRight changes root)`() { + val tree = makeTreeForRightRotationChangesRootRemoveTest() + tree.remove(0) + assert(tree.size() == 4L) + } + + @Test + fun `structure is correct after deletion (rotateRight changes root)`() { + val tree = makeTreeForRightRotationChangesRootRemoveTest() + tree.remove(0) + val expectedDependencies = listOf(Triple(0, 1, 2), Triple(2, 3, null)) + val expectedOrder = arrayOf(-3, -5, -1, -2) + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after deletion (rotateRight changes root)`() { + val tree = makeTreeForRightRotationChangesRootRemoveTest() + tree.remove(0) + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/removeTest/withoutBalancing/Other.kt b/lib/src/test/kotlin/trees/avlTree/removeTest/withoutBalancing/Other.kt new file mode 100644 index 0000000..bb47e57 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/removeTest/withoutBalancing/Other.kt @@ -0,0 +1,338 @@ +package trees.avlTree.removeTest.withoutBalancing + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class Other { + private fun makeEmptyTree(): AVLTreeForTest { + return AVLTreeForTest() + } + + @Test + fun `delete from emptyTree returns null`() { + val tree = makeEmptyTree() + assert(tree.remove(0) == null) + } + + private fun makeSize1Tree(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex('b', '+'), 1L) // init tree by root and size (don't use put) + } + + @Test + fun `tree is empty after root of 'size 1' tree was deleted `() { + val tree = makeSize1Tree() + tree.remove('b') + assert(tree.getVertexesInDFSOrder().isEmpty()) + } + + @Test + fun `size equals 0 after root of 'size 1' tree was deleted`() { + val tree = makeSize1Tree() + tree.remove('b') + assert(tree.size() == 0L) + } + + @Test + fun `remove fun return null if entry's key isn't exists(1)`() { + val tree = makeSize1Tree() + assert(tree.remove('a') == null) + } + + @Test + fun `tree has no changes after deletion by non existed larger key`() { + val tree = makeSize1Tree() + tree.remove('c') + assert(tree.size() == 1L) + assert(tree.getRootT()?.value == '+') + } + + @Test + fun `tree has no changes after deletion by non existed lesser key`() { + val tree = makeSize1Tree() + tree.remove('a') + assert(tree.size() == 1L) + assert(tree.getRootT()?.value == '+') + } + + private fun makeTreeForRemoveLeafWithoutBalancingTest(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex('r', "r", AVLVertex('n', "n"), AVLVertex('z', "z")), 3L) + } + + @Test + fun `right leaf deleted after remove with one's key was called`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + tree.remove('z') + val root = tree.getRootT() + if (root != null) { + assert(root.rightSon == null) + } else { + assert(false) + } + } + + @Test + fun `left leaf deleted after remove with one's key was called`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + tree.remove('n') + val root = tree.getRootT() + if (root != null) { + assert(root.leftSon == null) + } else { + assert(false) + } + } + + @Test + fun `remove by right leaf's key return due value`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + assert(tree.remove('z') == "z") + } + + @Test + fun `remove by left leaf's key return due value`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + assert(tree.remove('n') == "n") + } + + @Test + fun `vertex's sonsHeightDiff increased by 1 after one's right leaf was deleted`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + tree.remove('z') + val root = tree.getRootT() + assert(root?.sonsHeightDiff == 1) + } + + @Test + fun `vertex's sonsHeightDiff decreased by 1 after one's left leaf was deleted`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + tree.remove('n') + val root = tree.getRootT() + assert(root?.sonsHeightDiff == -1) + } + + @Test + fun `size decreased by 1 after right leaf was deleted`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + tree.remove('z') + assert(tree.size() == 2L) + } + + @Test + fun `size decreased by 1 after left leaf was deleted`() { + val tree = makeTreeForRemoveLeafWithoutBalancingTest() + tree.remove('n') + assert(tree.size() == 2L) + } + + private fun makeTreeForRemoveLeftSonWithOnlyLeftSonTest(): AVLTreeForTest { + val leftSon = AVLVertex('q', 9, AVLVertex('o', 0), null, 1) + val root = AVLVertex('s', 5, leftSon, AVLVertex('z', 2), 1) + return AVLTreeForTest(root, 4L) + } + + @Test + fun `remove be key of left son with only left son returns due value`() { + val tree = makeTreeForRemoveLeftSonWithOnlyRightSonTest() + assert(tree.remove('q') == 9) + } + + @Test + fun `content is correct after removed left son with only left son`() { + val tree = makeTreeForRemoveLeftSonWithOnlyLeftSonTest() + tree.remove('q') + assert(isTreeConsistsOf(setOf('o' to 0, 's' to 5, 'z' to 2), tree)) + } + + @Test + fun `size decreased by 1 after left son with only left son was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyLeftSonTest() + tree.remove('q') + assert(tree.size() == 3L) + } + + @Test + fun `structure is correct after left son with only left son was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyLeftSonTest() + tree.remove('q') + assert(isTreeSStructureThat(tree, arrayOf('s', 'o', 'z'), listOf(Triple(0, 1, 2)))) + } + + @Test + fun `sonsHeightDiff values are correct after LSon with only LSon was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyLeftSonTest() + tree.remove('q') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForRemoveLeftSonWithOnlyRightSonTest(): AVLTreeForTest { + val leftSon = AVLVertex('q', 9, null, AVLVertex('r', 7), -1) + val root = AVLVertex('s', 5, leftSon, AVLVertex('z', 2), 1) + return AVLTreeForTest(root, 4L) + } + + @Test + fun `remove by key of left son with only right son returns due value`() { + val tree = makeTreeForRemoveLeftSonWithOnlyRightSonTest() + assert(tree.remove('q') == 9) + } + + @Test + fun `content is correct after left son with only right son was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyRightSonTest() + tree.remove('q') + assert(isTreeConsistsOf(setOf('r' to 7, 's' to 5, 'z' to 2), tree)) + } + + @Test + fun `size decreased by 1 after left son with only right son was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyRightSonTest() + tree.remove('q') + assert(tree.size() == 3L) + } + + @Test + fun `structure is correct after left son with only right son was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyRightSonTest() + tree.remove('q') + assert(isTreeSStructureThat(tree, arrayOf('s', 'r', 'z'), listOf(Triple(0, 1, 2)))) + } + + @Test + fun `sonsHeightDiff values are correct after LSon with only RSon was deleted`() { + val tree = makeTreeForRemoveLeftSonWithOnlyRightSonTest() + tree.remove('q') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForRemoveRightSonWithOnlyLeftSonTest(): AVLTreeForTest { + val rightSon = AVLVertex('q', 9, AVLVertex('o', 0), null, 1) + val root = AVLVertex('i', 1, AVLVertex('b', 6), rightSon, -1) + return AVLTreeForTest(root, 4L) + } + + @Test + fun `remove by key of right son with only left son returns due value`() { + val tree = makeTreeForRemoveRightSonWithOnlyLeftSonTest() + assert(tree.remove('q') == 9) + } + + @Test + fun `content is correct after right son with only left son was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyLeftSonTest() + tree.remove('q') + assert(isTreeConsistsOf(setOf('o' to 0, 'b' to 6, 'i' to 1), tree)) + } + + @Test + fun `size decreased by 1 after right son with only left son was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyLeftSonTest() + tree.remove('q') + assert(tree.size() == 3L) + } + + @Test + fun `structure is correct after right son with only left son was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyLeftSonTest() + tree.remove('q') + assert(isTreeSStructureThat(tree, arrayOf('i', 'b', 'o'), listOf(Triple(0, 1, 2)))) + } + + @Test + fun `sonsHeightDiff values are correct after RSon with only LSon was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyLeftSonTest() + tree.remove('q') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForRemoveRightSonWithOnlyRightSonTest(): AVLTreeForTest { + val rightSon = AVLVertex('q', 9, null, AVLVertex('r', 7), -1) + val root = AVLVertex('i', 1, AVLVertex('b', 6), rightSon, -1) + return AVLTreeForTest(root, 4L) + } + + @Test + fun `remove by key of right son with only right son returns due value`() { + val tree = makeTreeForRemoveRightSonWithOnlyRightSonTest() + assert(tree.remove('q') == 9) + } + + @Test + fun `content is correct after right son with only right son was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyRightSonTest() + tree.remove('q') + assert(isTreeConsistsOf(setOf('r' to 7, 'b' to 6, 'i' to 1), tree)) + } + + @Test + fun `size decreased by 1 after right son with only right son was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyRightSonTest() + tree.remove('q') + assert(tree.size() == 3L) + } + + @Test + fun `structure is correct after right son with only right son was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyRightSonTest() + tree.remove('q') + assert(isTreeSStructureThat(tree, arrayOf('i', 'b', 'r'), listOf(Triple(0, 1, 2)))) + } + + @Test + fun `sonsHeightDiff values are correct after RSon with only RSon was deleted`() { + val tree = makeTreeForRemoveRightSonWithOnlyRightSonTest() + tree.remove('q') + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForRemoveRootWithOnlyLeftSon(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex("be", "es", (AVLVertex("and", "et")), null, 1), 2L) + } + + @Test + fun `remove by key of root with only left son returns due value`() { + val tree = makeTreeForRemoveRootWithOnlyLeftSon() + assert(tree.remove("be") == "es") + } + + @Test + fun `content is correct after root with only left son was deleted`() { + val tree = makeTreeForRemoveRootWithOnlyLeftSon() + tree.remove("be") + assert(isTreeConsistsOf(setOf("and" to "et"), tree)) + } + + @Test + fun `sonsHeightDiff values are correct after root with only LSon was deleted`() { + val tree = makeTreeForRemoveRootWithOnlyLeftSon() + tree.remove("and") + assert(isSonsHeightDiffCorrect(tree)) + } + + private fun makeTreeForRemoveRootWithOnlyRightSon(): AVLTreeForTest { + return AVLTreeForTest(AVLVertex("and", "et", null, (AVLVertex("be", "es")), -1), 2L) + } + + @Test + fun `remove by key of root with only right son returns due value`() { + val tree = makeTreeForRemoveRootWithOnlyRightSon() + assert(tree.remove("and") == "et") + } + + @Test + fun `content is correct after root with only right son was deleted`() { + val tree = makeTreeForRemoveRootWithOnlyRightSon() + tree.remove("and") + assert(isTreeConsistsOf(setOf("be" to "es"), tree)) + } + + @Test + fun `sonsHeightDiff values are correct after root with only RSon was deleted`() { + val tree = makeTreeForRemoveRootWithOnlyRightSon() + tree.remove("and") + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/avlTree/removeTest/withoutBalancing/VertexHadTwoSons.kt b/lib/src/test/kotlin/trees/avlTree/removeTest/withoutBalancing/VertexHadTwoSons.kt new file mode 100644 index 0000000..e793753 --- /dev/null +++ b/lib/src/test/kotlin/trees/avlTree/removeTest/withoutBalancing/VertexHadTwoSons.kt @@ -0,0 +1,189 @@ +package trees.avlTree.removeTest.withoutBalancing + +import org.junit.jupiter.api.Test +import trees.avlTree.AVLTreeForTest +import trees.avlTree.AuxiliaryFunctions.isSonsHeightDiffCorrect +import trees.avlTree.AuxiliaryFunctions.isTreeConsistsOf +import trees.avlTree.AuxiliaryFunctions.isTreeSStructureThat +import vertexes.AVLVertex + +class VertexHadTwoSons { + private fun makeTreeForRemoveSonWithBothSons(): AVLTreeForTest { + val eVrt = AVLVertex('e', 'E', AVLVertex('c', 'C'), null, 1) + val bVrt = AVLVertex('b', 'B', AVLVertex('a', 'A'), eVrt, -1) + val leftSon = AVLVertex('f', 'F', bVrt, AVLVertex('i', 'I', null, AVLVertex('k', 'K'), -1), 1) + val qVrt = AVLVertex('q', 'Q', null, AVLVertex('s', 'S'), -1) + val wVrt = AVLVertex('w', 'W', null, AVLVertex('z', 'Z'), -1) + val root = AVLVertex('n', 'N', leftSon, AVLVertex('u', 'U', qVrt, wVrt, 0), 1) + return AVLTreeForTest(root, 13L) + } + + @Test + fun `remove by key of left son with both sons returns due value`() { + val tree = makeTreeForRemoveSonWithBothSons() + assert(tree.remove('f') == 'F') + } + + @Test + fun `content is correct after left son with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('f') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'e' to 'E', + 'i' to 'I', + 'z' to 'Z', + 'k' to 'K', + 'n' to 'N', + 'u' to 'U', + 'q' to 'Q', + 's' to 'S', + 'w' to 'W', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after left son with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('f') + assert(tree.size() == 12L) + } + + @Test + fun `structure is correct after left son with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('f') + val expectedDependencies = + listOf( + Triple(0, 1, 7), + Triple(1, 2, 5), + Triple(2, 3, 4), + Triple(5, null, 6), + Triple(7, 8, 10), + Triple(10, null, 11), + ) + val expectedOrder = arrayOf('n', 'e', 'b', 'a', 'c', 'i', 'k', 'u', 'q', 's', 'w', 'z') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after LSon with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('f') + assert(isSonsHeightDiffCorrect(tree)) + } + + @Test + fun `remove by key of right son with both sons returns due value`() { + val tree = makeTreeForRemoveSonWithBothSons() + assert(tree.remove('u') == 'U') + } + + @Test + fun `content is correct after right son with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('u') + val expectedContent = + setOf( + 'a' to 'A', + 'b' to 'B', + 'c' to 'C', + 'e' to 'E', + 'i' to 'I', + 'z' to 'Z', + 'k' to 'K', + 'n' to 'N', + 'f' to 'F', + 'q' to 'Q', + 's' to 'S', + 'w' to 'W', + ) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after right son with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('u') + assert(tree.size() == 12L) + } + + @Test + fun `structure is correct after right son with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('u') + val expectedDependencies = + listOf( + Triple(0, 1, 8), + Triple(1, 2, 6), + Triple(2, 3, 4), + Triple(6, null, 7), + Triple(4, 5, null), + Triple(8, 9, 10), + Triple(10, null, 11), + ) + val expectedOrder = arrayOf('n', 'f', 'b', 'a', 'e', 'c', 'i', 'k', 's', 'q', 'w', 'z') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after RSon with both sons was deleted`() { + val tree = makeTreeForRemoveSonWithBothSons() + tree.remove('u') + assert(isSonsHeightDiffCorrect(tree)) + } + + @Test + fun `remove fun return null if entry's key isn't exists(2)`() { + val tree = makeTreeForRemoveSonWithBothSons() + assert(tree.remove('x') == null) + } + + private fun makeTreeForRemoveRootWithBothSonsTest(): AVLTreeForTest { + val leftSon = AVLVertex('b', 2, AVLVertex('a', 1), AVLVertex('e', 5), 0) + val rightSon = AVLVertex('i', 9, null, AVLVertex('k', 11), -1) + val root = AVLVertex('f', 6, leftSon, rightSon, 0) + return AVLTreeForTest(root, 6L) + } + + @Test + fun `remove by key of root with both sons returns due value`() { + val tree = makeTreeForRemoveRootWithBothSonsTest() + assert(tree.remove('f') == 6) + } + + @Test + fun `content is correct after root with both sons was deleted`() { + val tree = makeTreeForRemoveRootWithBothSonsTest() + tree.remove('f') + val expectedContent = setOf('a' to 1, 'b' to 2, 'e' to 5, 'i' to 9, 'k' to 11) + assert(isTreeConsistsOf(expectedContent, tree)) + } + + @Test + fun `size decreased by 1 after root with both sons was deleted`() { + val tree = makeTreeForRemoveRootWithBothSonsTest() + tree.remove('f') + assert(tree.size() == 5L) + } + + @Test + fun `structure is correct after root with both sons was deleted`() { + val tree = makeTreeForRemoveRootWithBothSonsTest() + tree.remove('f') + val expectedDependencies = listOf(Triple(0, 1, 3), Triple(1, 2, null), Triple(3, null, 4)) + val expectedOrder = arrayOf('e', 'b', 'a', 'i', 'k') + assert(isTreeSStructureThat(tree, expectedOrder, expectedDependencies)) + } + + @Test + fun `sonsHeightDiff values are correct after root with both sons was deleted`() { + val tree = makeTreeForRemoveRootWithBothSonsTest() + tree.remove('f') + assert(isSonsHeightDiffCorrect(tree)) + } +} diff --git a/lib/src/test/kotlin/trees/rbTree/RBSearchTreeTest.kt b/lib/src/test/kotlin/trees/rbTree/RBSearchTreeTest.kt new file mode 100644 index 0000000..cd3b5a0 --- /dev/null +++ b/lib/src/test/kotlin/trees/rbTree/RBSearchTreeTest.kt @@ -0,0 +1,383 @@ +package trees.rbTree + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import trees.RBSearchTree +import kotlin.test.assertEquals + +class RBSearchTreeTest { + private lateinit var rbTree: RBSearchTree + private lateinit var expectedResult: List> + private var result: MutableList> = mutableListOf() + + @BeforeEach + fun setup() { + rbTree = RBSearchTree() + } + + @AfterEach + fun end() { + for (el in rbTree) result.add(el) + assertEquals(expectedResult, result) + } + + @Test + fun `put a root to the tree`() { + rbTree.put(7, "hi") + expectedResult = listOf(Pair(7, "hi")) + } + + @Test + fun `put new vertex to the left`() { + rbTree.put(7, "hi") + rbTree.put(5, "my") + expectedResult = listOf(Pair(7, "hi"), Pair(5, "my")) + } + + @Test + fun `put new vertex to the right`() { + rbTree.put(7, "hi") + rbTree.put(9, "name") + expectedResult = listOf(Pair(7, "hi"), Pair(9, "name")) + } + + @Test + fun `put existing vertex without replace`() { + rbTree.put(7, "hi") + rbTree.put(7, "is", false) + expectedResult = listOf(Pair(7, "hi")) + } + + @Test + fun `put existing vertex with replace`() { + rbTree.put(7, "hi") + rbTree.put(7, "Slim") + expectedResult = listOf(Pair(7, "Slim")) + } + + @Test + fun `balance after put - uncle is rightSon and red`() { + rbTree.put(7, "hi") + rbTree.put(5, "my") + rbTree.put(9, "name") + rbTree.put(6, "is") + expectedResult = listOf(Pair(7, "hi"), Pair(9, "name"), Pair(5, "my"), Pair(6, "is")) + } + + @Test + fun `balance after put - uncle is rightSon and black + newVertex is leftSon`() { + rbTree.put(7, "hi") + rbTree.put(5, "my") + rbTree.put(3, "name") + expectedResult = listOf(Pair(5, "my"), Pair(7, "hi"), Pair(3, "name")) + } + + @Test + fun `balance after put - uncle is rightSon and black + newVertex is rightSon`() { + rbTree.put(7, "hi") + rbTree.put(5, "my") + rbTree.put(6, "name") + expectedResult = listOf(Pair(6, "name"), Pair(7, "hi"), Pair(5, "my")) + } + + @Test + fun `balance after put - uncle is leftSon and red`() { + rbTree.put(7, "hi") + rbTree.put(5, "my") + rbTree.put(9, "name") + rbTree.put(8, "is") + expectedResult = listOf(Pair(7, "hi"), Pair(9, "name"), Pair(8, "is"), Pair(5, "my")) + } + + @Test + fun `balance after put - uncle is leftSon and black + newVertex is rightSon`() { + rbTree.put(7, "hi") + rbTree.put(9, "my") + rbTree.put(10, "name") + expectedResult = listOf(Pair(9, "my"), Pair(10, "name"), Pair(7, "hi")) + } + + @Test + fun `balance after put - uncle is leftSon and black + newVertex is leftSon`() { + rbTree.put(7, "hi") + rbTree.put(9, "my") + rbTree.put(8, "name") + expectedResult = listOf(Pair(8, "name"), Pair(9, "my"), Pair(7, "hi")) + } + + @Test + fun `remove non-existent vertex`() { + rbTree.put(7, "hi") + rbTree.remove(9) + expectedResult = listOf(Pair(7, "hi")) + } + + @Test + fun `remove root`() { + rbTree.put(7, "hi") + rbTree.remove(7) + expectedResult = listOf() + } + + @Test + fun `delete red vertex with 0 children`() { + rbTree.put(7, "hi") + rbTree.put(9, "my") + rbTree.remove(9) + expectedResult = listOf(Pair(7, "hi")) + } + + @Test + fun `delete black vertex with 1 rightSon`() { + rbTree.put(7, "hi") + rbTree.put(9, "my") + rbTree.remove(7) + expectedResult = listOf(Pair(9, "my")) + } + + @Test + fun `delete black vertex with 1 leftSon`() { + rbTree.put(7, "hi") + rbTree.put(5, "my") + rbTree.remove(7) + expectedResult = listOf(Pair(5, "my")) + } + + @Test + fun `delete black vertex with 2 children`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.remove(7) + expectedResult = listOf(Pair(10, "name"), Pair(4, "my")) + } + + @Test + fun `delete red vertex with 2 children`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(3, "is") + rbTree.put(5, "chka") + rbTree.put(9, "chka") + rbTree.put(12, "chka") + rbTree.put(11, "Slim") + rbTree.put(13, "Shady") + rbTree.remove(10) + expectedResult = + listOf( + Pair(7, "hi"), + Pair(11, "Slim"), + Pair(12, "chka"), + Pair(13, "Shady"), + Pair(9, "chka"), + Pair(4, "my"), + Pair(5, "chka"), + Pair(3, "is"), + ) + } + + @Test + fun `balance after remove - brother is right and black, brother's rightSon - red`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(3, "is") + rbTree.put(5, "chka") + rbTree.put(9, "chka") + rbTree.put(12, "chka") + rbTree.put(11, "Slim") + rbTree.put(13, "Shady") + rbTree.remove(9) + expectedResult = + listOf( + Pair(7, "hi"), + Pair(12, "chka"), + Pair(13, "Shady"), + Pair(10, "name"), + Pair(11, "Slim"), + Pair(4, "my"), + Pair(5, "chka"), + Pair(3, "is"), + ) + } + + @Test + fun `balance after remove - brother is right and black, brother's leftSon - red (rightSon - black)`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(3, "is") + rbTree.put(5, "chka") + rbTree.put(9, "chka") + rbTree.put(12, "Slim") + rbTree.put(11, "Shady") + rbTree.remove(9) + expectedResult = + listOf( + Pair(7, "hi"), + Pair(11, "Shady"), + Pair(12, "Slim"), + Pair(10, "name"), + Pair(4, "my"), + Pair(5, "chka"), + Pair(3, "is"), + ) + } + + @Test + fun `balance after remove - brother is right and black, both sons - black`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(9, "is") + rbTree.put(12, "Slim") + rbTree.put(11, "Shady") + rbTree.remove(11) + + // now vertexes with keys 9 and 12 are black + rbTree.remove(9) + + expectedResult = listOf(Pair(7, "hi"), Pair(10, "name"), Pair(12, "Slim"), Pair(4, "my")) + } + + @Test + fun `balance after remove - right brother is red`() { + rbTree.put(60, "sixty") + rbTree.put(33, "thirty-three") + rbTree.put(84, "eighty-four") + rbTree.put(15, "fifteen") + rbTree.put(51, "fifty-one") + rbTree.put(65, "sixty-five") + rbTree.put(96, "ninety-six") + rbTree.put(34, "thirty-four") + rbTree.put(52, "Alblack") + rbTree.put(94, "ninety-four") + rbTree.put(97, "ninety-seven") + rbTree.put(95, "ninety-five") + + // now vertex with key 96 is red, with key 65 - black + rbTree.remove(65) + + expectedResult = + listOf( + Pair(60, "sixty"), + Pair(96, "ninety-six"), + Pair(97, "ninety-seven"), + Pair(94, "ninety-four"), + Pair(95, "ninety-five"), + Pair(84, "eighty-four"), + Pair(33, "thirty-three"), + Pair(51, "fifty-one"), + Pair(52, "Alblack"), + Pair(34, "thirty-four"), + Pair(15, "fifteen"), + ) + } + + @Test + fun `balance after remove - brother is left and black, brother's rightSon - red`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(2, "is") + rbTree.put(5, "chka") + rbTree.put(9, "chka") + rbTree.put(12, "chka") + rbTree.put(1, "Slim") + rbTree.put(3, "Shady") + rbTree.remove(5) + expectedResult = + listOf( + Pair(7, "hi"), + Pair(10, "name"), + Pair(12, "chka"), + Pair(9, "chka"), + Pair(2, "is"), + Pair(4, "my"), + Pair(3, "Shady"), + Pair(1, "Slim"), + ) + } + + @Test + fun `balance after remove - brother is left and black, brother's rightSon - red (leftSon - black)`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(2, "is") + rbTree.put(5, "chka") + rbTree.put(9, "chka") + rbTree.put(12, "Slim") + rbTree.put(3, "Shady") + rbTree.remove(5) + expectedResult = + listOf( + Pair(7, "hi"), + Pair(10, "name"), + Pair(12, "Slim"), + Pair(9, "chka"), + Pair(3, "Shady"), + Pair(4, "my"), + Pair(2, "is"), + ) + } + + @Test + fun `balance after remove - brother is left and black, both sons - black`() { + rbTree.put(7, "hi") + rbTree.put(4, "my") + rbTree.put(10, "name") + rbTree.put(2, "is") + rbTree.put(5, "Slim") + rbTree.put(3, "Shady") + rbTree.remove(3) + + // now vertexes with keys 2 and 5 are black + rbTree.remove(5) + + expectedResult = listOf(Pair(7, "hi"), Pair(10, "name"), Pair(4, "my"), Pair(2, "is")) + } + + @Test + fun `balance after remove - left brother is red`() { + rbTree.put(60, "sixty") + rbTree.put(33, "thirty-three") + rbTree.put(84, "eighty-four") + rbTree.put(15, "fifteen") + rbTree.put(51, "fifty-one") + rbTree.put(65, "sixty-five") + rbTree.put(96, "ninety-six") + rbTree.put(5, "five") + rbTree.put(27, "twenty-seven") + rbTree.put(61, "sixty-one") + rbTree.put(69, "sixty-nine") + rbTree.put(17, "seventeen") + + // now vertex with key 15 is red, with key 51 - black + rbTree.remove(51) + + expectedResult = + listOf( + Pair(60, "sixty"), + Pair(84, "eighty-four"), + Pair(96, "ninety-six"), + Pair(65, "sixty-five"), + Pair(69, "sixty-nine"), + Pair(61, "sixty-one"), + Pair(15, "fifteen"), + Pair(27, "twenty-seven"), + Pair(33, "thirty-three"), + Pair(17, "seventeen"), + Pair(5, "five"), + ) + } + + @Test + fun `test secondary constructor`() { + val testMap = mapOf(3 to "three", 1 to "one", 2 to "two") + rbTree = RBSearchTree(testMap) + expectedResult = listOf(Pair(2, "two"), Pair(3, "three"), Pair(1, "one")) + } +} diff --git a/lib/src/test/kotlin/trees/simpleBSTree/SimpleBSTForTest.kt b/lib/src/test/kotlin/trees/simpleBSTree/SimpleBSTForTest.kt new file mode 100644 index 0000000..2161bc6 --- /dev/null +++ b/lib/src/test/kotlin/trees/simpleBSTree/SimpleBSTForTest.kt @@ -0,0 +1,366 @@ +package trees.simpleBSTree + +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class SimpleBSTForTest { + private lateinit var tree: TestSimpleBST + + @BeforeEach + fun setup() { + tree = TestSimpleBST() + } + + @Test + fun `create tree from map`() { + val map = mapOf(Pair(1, "cat"), Pair(2, "rabbit")) + val tree = TestSimpleBST(map, true) + + assertEquals("cat", tree.getTreeRoot()?.value) + assertEquals("rabbit", tree.getTreeRoot()?.rightSon?.value) + assertEquals(2L, tree.size()) + } + + // put + @Test + fun `put key-value pair to empty tree`() { + tree.put(1, "meow") + + assertEquals("meow", tree.getTreeRoot()?.value) + assertEquals(1L, tree.size()) + } + + @Test + fun `put existing key and do not replace it`() { + tree.put(1, "old") + tree.put(1, "new", false) + + assertEquals("old", tree.getTreeRoot()?.value) + assertEquals(1L, tree.size()) + } + + @Test + fun `put existing key and replace it`() { + tree.put(1, "old") + tree.put(1, "new", true) + + assertEquals("new", tree.getTreeRoot()?.value) + assertEquals(1L, tree.size()) + } + + @Test + fun `put key smaller than root key`() { + tree.put(1, "one") + tree.put(0, "zero") + +// 1 +// ^ +// 0 null + + assertEquals("zero", tree.getTreeRoot()?.leftSon?.value) + assertEquals(2L, tree.size()) + } + + @Test + fun `put key smaller than root key, leftSon != null and replace existing key`() { + tree.put(3, "three", true) + tree.put(2, "two dogs", true) + tree.put(2, "two cats", true) + tree.put(1, "one", true) + +// 3 +// ^ +// 2 null +// ^ +// 1 null + + assertEquals("three", tree.getTreeRoot()?.value) + assertEquals("two cats", tree.getTreeRoot()?.leftSon?.value) + assertEquals("one", tree.getTreeRoot()?.leftSon?.leftSon?.value) + assertEquals(3L, tree.size()) + } + + @Test + fun `put key smaller than root key and leftSon != null and do not replace existing key`() { + tree.put(3, "three", false) + tree.put(2, "two dogs", false) + tree.put(2, "two cats", false) + tree.put(1, "one", false) + +// 3 +// ^ +// 2 null +// ^ +// 1 null + + assertEquals("three", tree.getTreeRoot()?.value) + assertEquals("two dogs", tree.getTreeRoot()?.leftSon?.value) + assertEquals("one", tree.getTreeRoot()?.leftSon?.leftSon?.value) + assertEquals(3, tree.size()) + } + + @Test + fun `put key more than root key`() { + tree.put(1, "one") + tree.put(2, "two") + + assertEquals("two", tree.getTreeRoot()?.rightSon?.value) + assertEquals(2, tree.size()) + } + + @Test + fun `put key more than root key, rightSon != null and replace existing key`() { + tree.put(1, "one", true) + tree.put(2, "two parrots", true) + tree.put(2, "two rabbits", true) + tree.put(3, "three", true) + +// 1 +// ^ +// null 2 +// ^ +// null 3 + + assertEquals("one", tree.getTreeRoot()?.value) + assertEquals("two rabbits", tree.getTreeRoot()?.rightSon?.value) + assertEquals("three", tree.getTreeRoot()?.rightSon?.rightSon?.value) + assertEquals(3, tree.size()) + } + + @Test + fun `put key more than root key, rightSon != null and do not replace existing key`() { + tree.put(1, "one", false) + tree.put(2, "two parrots", false) + tree.put(2, "two rabbits", false) + tree.put(3, "three", false) + +// 1 +// ^ +// null 2 +// ^ +// null 3 + + assertEquals("one", tree.getTreeRoot()?.value) + assertEquals("two parrots", tree.getTreeRoot()?.rightSon?.value) + assertEquals("three", tree.getTreeRoot()?.rightSon?.rightSon?.value) + assertEquals(3, tree.size()) + } + + // remove + @Test + fun `remove vertex with null key from empty tree`() { + val tree: TestSimpleBST = TestSimpleBST() + val returned = tree.remove(null) + assertEquals(null, tree.getTreeRoot()) + assertEquals(null, returned) + assertEquals(0, tree.size()) + } + + @Test + fun `remove root vertex without sons`() { + tree.put(5, "five") + val returned = tree.remove(5) + assertEquals(null, tree.getTreeRoot()) + assertEquals("five", returned) + assertEquals(0, tree.size()) + } + + @Test + fun `remove root vertex with one left son`() { + tree.putAll(mapOf(Pair(5, "five"), Pair(0, "zero"))) + +// 5 +// ^ +// 0 null + + val returned = tree.remove(5) + assertEquals("zero", tree.getTreeRoot()?.value) + assertEquals("five", returned) + assertEquals(1, tree.size()) + } + + @Test + fun `remove root vertex with one right son`() { + tree.putAll(mapOf(Pair(5, "five"), Pair(6, "six"))) + +// 5 +// ^ +// null 6 + + val returned = tree.remove(5) + assertEquals("six", tree.getTreeRoot()?.value) + assertEquals("five", returned) + assertEquals(1, tree.size()) + } + + @Test + fun `remove vertex with smaller key, vertex has not sons`() { + tree.putAll(mapOf(Pair(5, "five"), Pair(0, "zero"))) + +// 5 +// ^ +// 0 null + + val returned = tree.remove(0) + assertEquals("five", tree.getTreeRoot()?.value) + assertEquals("zero", returned) + assertEquals(1, tree.size()) + } + + @Test + fun `remove vertex with smaller key, vertex has one right son`() { + tree.putAll( + mapOf( + Pair(5, "five"), + Pair(0, "zero"), + Pair(4, "four"), + ), + ) + +// 5 +// ^ +// 0 null +// ^ +// null 4 + + val returned = tree.remove(0) + assertEquals("five", tree.getTreeRoot()?.value) + assertEquals("four", tree.getTreeRoot()?.leftSon?.value) + assertEquals("zero", returned) + assertEquals(2, tree.size()) + } + + @Test + fun `remove vertex with smaller key, vertex has one left son`() { + tree.putAll( + mapOf( + Pair(5, "five"), + Pair(0, "zero"), + Pair(-1, "minus_one"), + ), + ) + +// 5 +// ^ +// 0 null +// ^ +// -1 null + + val returned = tree.remove(0) + assertEquals("five", tree.getTreeRoot()?.value) + assertEquals("minus_one", tree.getTreeRoot()?.leftSon?.value) + assertEquals("zero", returned) + assertEquals(2, tree.size()) + } + + @Test + fun `remove vertex with smaller key, vertex has two sons`() { + tree.putAll( + mapOf( + Pair(5, "five"), + Pair(0, "zero"), + Pair(-1, "minus_one"), + Pair(4, "four"), + Pair(3, "three"), + ), + ) + +// 5 +// ^ +// 0 null +// ^ +// -1 4 +// ^ +// 3 null + + val returned = tree.remove(0) + assertEquals("five", tree.getTreeRoot()?.value) + assertEquals("three", tree.getTreeRoot()?.leftSon?.value) + assertEquals("zero", returned) + assertEquals(4, tree.size()) + } + + @Test + fun `remove vertex with more key, vertex has not sons`() { + tree.putAll(mapOf(Pair(1, "one"), Pair(5, "five"))) + +// 1 +// ^ +// null 5 + + val returned = tree.remove(5) + assertEquals("one", tree.getTreeRoot()?.value) + assertEquals("five", returned) + assertEquals(1, tree.size()) + } + + @Test + fun `remove vertex with more key, vertex has one left son`() { + tree.putAll(mapOf(Pair(1, "one"), Pair(5, "five"), Pair(2, "two"))) + +// 1 +// ^ +// null 5 +// ^ +// 2 null + + val returned = tree.remove(5) + assertEquals("one", tree.getTreeRoot()?.value) + assertEquals("two", tree.getTreeRoot()?.rightSon?.value) + assertEquals("five", returned) + assertEquals(2L, tree.size()) + } + + @Test + fun `remove vertex with more key, vertex has one right son`() { + tree.putAll(mapOf(Pair(1, "one"), Pair(5, "five"), Pair(10, "ten"))) + +// 1 +// ^ +// null 5 +// ^ +// null 10 + + val returned = tree.remove(5) + assertEquals("one", tree.getTreeRoot()?.value) + assertEquals("ten", tree.getTreeRoot()?.rightSon?.value) + assertEquals("five", returned) + assertEquals(2L, tree.size()) + } + + @Test + fun `remove vertex with more key, vertex has two sons`() { + tree.putAll( + mapOf( + Pair(1, "one"), + Pair(5, "five"), + Pair(10, "ten"), + Pair(2, "two"), + Pair(6, "six"), + ), + ) + +// 1 +// ^ +// null 5 +// ^ +// 2 10 +// ^ +// 6 null + + val returned = tree.remove(5) + assertEquals("one", tree.getTreeRoot()?.value) + assertEquals("six", tree.getTreeRoot()?.rightSon?.value) + assertEquals("five", returned) + assertEquals(4L, tree.size()) + } + + @Test + fun `remove vertex with non-existing key`() { + tree.put(1, "cat") + tree.remove(6) + + assertEquals(1L, tree.size()) + } +} diff --git a/lib/src/test/kotlin/trees/simpleBSTree/TestSimpleBST.kt b/lib/src/test/kotlin/trees/simpleBSTree/TestSimpleBST.kt new file mode 100644 index 0000000..249accb --- /dev/null +++ b/lib/src/test/kotlin/trees/simpleBSTree/TestSimpleBST.kt @@ -0,0 +1,14 @@ +package trees.simpleBSTree + +import trees.SimpleBinarySearchTree +import vertexes.SimpleBSTVertex + +class TestSimpleBST : SimpleBinarySearchTree { + fun getTreeRoot(): SimpleBSTVertex? { + return root + } + + constructor(comparator: Comparator? = null) : super(comparator) + constructor(map: Map, replaceIfExists: Boolean = true, comparator: Comparator? = null) : + super(map, replaceIfExists, comparator) +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..5073bcb --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,14 @@ +/* + * 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 = "trees-9" +include("lib")