From 0de1819a13f532e1a19ffdd8b51c2b1c59441474 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:43:21 +0000 Subject: [PATCH 01/90] Setting up GitHub Classroom Feedback From 3cbfea09288b1477c066ae8b1e0dcb137bb886a8 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Thu, 23 Mar 2023 23:19:04 +0300 Subject: [PATCH 02/90] first commit, add .gitignore, README.md, init Gradle --- .gitignore | 41 ++++ .idea/.gitignore | 8 + LICENSE.txt | 235 +++++++++++++++++++++++ README.md | 49 +++++ build.gradle.kts | 27 +++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 ++++++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle.kts | 3 + src/main/kotlin/Main.kt | 8 + 12 files changed, 700 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/Main.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34d75bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..97401ee --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,235 @@ +Home / Licenses +Apache License 2.0 +A permissive license whose main conditions require preservation of copyright and license notices. Contributors provide an express grant of patent rights. Licensed works, modifications, and larger works may be distributed under different terms and without source code. + +Permissions Conditions Limitations + Commercial use + Distribution + Modification + Patent use + Private use + License and copyright notice + State changes + Liability + Trademark use + Warranty + 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. +Copy license text to clipboard +Suggest this license +Make a pull request to suggest this license for a project that is not licensed. Please be polite: see if a license has already been suggested, try to suggest a license fitting for the project’s community, and keep your communication with project maintainers friendly. + +Enter GitHub repository URL +How to apply this license +Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file. + +Optional steps +The Apache Software Foundation recommends taking the additional step of adding a boilerplate notice to the header of each source file. You can find the notice in the appendix at the very end of the license text. + +Add Apache-2.0 to your project’s package description, if applicable (e.g., Node.js, Ruby, and Rust). This will ensure the license is displayed in package directories. + + Source +Who’s using this license? +Kubernetes +PDF.js +Swift +About Terms of Service Help improve this pageThe content of this site is licensed under the Creative Commons Attribution 3.0 Unported License.Curated with ❤️ by GitHub, Inc. and You! \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa5d444 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ + +# ABOBA + +A brief description of what this project does and who it's for + + +## Badges + +Add badges from somewhere like: [shields.io](https://shields.io/) + + +[![Apache License](https://img.shields.io/badge/license-Apache%202.0-black.svg)](https://www.apache.org/licenses/LICENSE-2.0) + + +## Authors (ABOBAS) + +- [absolute](https://steamcommunity.com/groups/absoluteplayer) [@roketflame](https://github.com/RoketFlame) +- [отдел продаж](https://steamcommunity.com/groups/Otedel_Prodaj) [@wokuparalyzed](https://www.github.com/wokuparalyzed) +- [and their fans](https://steamcommunity.com/groups/kazakhstansgaminggirls) [@Lesh79](https://www.github.com/Lesh79) + + +## Features + +- DEploy +- DEploy +- DEploy +- Cross platform + + +## Feedback + +If you have any feedback, please reach out to us at fake@fake.com + + +## About Me +I'm a full stack developer... \ +Help please...((( depre??ed + +# Hi, I'm ABOBA! + + +## 🛠 Skills +Javascript, HTML, CSS, fisting... + + +## 🔗 Links (Источники вдохновления) +[![gradle](https://img.shields.io/badge/gradle-FFFFFF?style=for-the-badge&logo=gradle&logoColor=black&)](https://gradle.org/) \ +[](https://www.youtube.com/watch?v=dQw4w9WgXcQ) + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..766f636 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("jvm") version "1.8.0" + application +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(8) +} + +application { + mainClass.set("MainKt") +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..9cbe239 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ + +rootProject.name = "Trees" + diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..fb4f2bf --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,8 @@ +fun main(args: Array) { + println("Hello World!") + println() + + // Try adding program arguments via Run/Debug configuration. + // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. + println("Program arguments: ${args.joinToString()}") +} \ No newline at end of file From c008b4a4700d6ff6ae787bd09c46758b7ce9cbfd Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 24 Mar 2023 01:13:24 +0300 Subject: [PATCH 03/90] feat: add Deployment paragraph in README.md, add basic structure for trees and nodes --- README.md | 9 +++++++++ src/main/kotlin/ABSTree.kt | 16 ++++++++++++++++ src/main/kotlin/Main.kt | 10 ++++------ src/main/kotlin/Node.kt | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/ABSTree.kt create mode 100644 src/main/kotlin/Node.kt diff --git a/README.md b/README.md index aa5d444..e85082a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,15 @@ Add badges from somewhere like: [shields.io](https://shields.io/) - DEploy - Cross platform +## Deployment + +To deploy this project run + +```bash + gradle build +``` + + ## Feedback diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt new file mode 100644 index 0000000..45f4168 --- /dev/null +++ b/src/main/kotlin/ABSTree.kt @@ -0,0 +1,16 @@ +import Node +interface Tree { + fun add() + fun contain() + fun delete() +} + +abstract class ABSTree, V : Any>: Tree { + var root: BSNode? = null + fun simple_add(key: K, value: V) { + val node = BSNode(KeyValue(key, value)) + while (true) { + } + } + fun simple_delete() {} +} \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index fb4f2bf..97f405e 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,8 +1,6 @@ +import ABSTree +import Node fun main(args: Array) { - println("Hello World!") - println() - - // Try adding program arguments via Run/Debug configuration. - // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. - println("Program arguments: ${args.joinToString()}") + val sds = KeyValue(50, 70) + println(sds.getKey()) } \ No newline at end of file diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt new file mode 100644 index 0000000..f323457 --- /dev/null +++ b/src/main/kotlin/Node.kt @@ -0,0 +1,33 @@ +interface Node, Subtype : Node> { + var data: T + var left: Subtype? + var right: Subtype? + fun getKey() : Any + fun getValue() : Any +} + +class BSNode, V : Any>(override var data: KeyValue) : Node, BSNode> { + override var left: BSNode? = null + override var right: BSNode? = null + + override fun getKey(): K { + return data.getKey() + } + + override fun getValue(): V { + return data.getValue() + } +} + +class KeyValue, V>(private val key: K, private var value: V) : Comparable> { + fun getKey(): K { + return key + } + fun getValue(): V { + return value + } + override fun compareTo(other: KeyValue): Int { + return key.compareTo(other.key) + } + +} \ No newline at end of file From 117df44e463f5a2f4c167e4a06ca50795b5c1354 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 24 Mar 2023 22:31:15 +0300 Subject: [PATCH 04/90] feat: add CONTRIBUTING.md fix: fix text in LICENSE.txt --- CONTRIBUTING.md | 40 ++++++++++++++++++++++++++++++++++++++++ LICENSE.txt | 45 +++++++++++++-------------------------------- 2 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf4ddac --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Внесение правок + +## Основные советы + +1. Не используйте merge, только rebase (для сохранения линейной истории коммитов) +2. Не менять чужие ветки без крайней необходимости +3. Перепроверьте историю коммитов перед созданием пулл реквеста +4. **Перепроверьте, что вы в правильной ветке**, никогда не коммитьте напрямую в main + +## Правила добавления коммитов + +Коммиты добавляются в соответствии с conventional commits. Т.е +`(): `. + +Поле `` должно принимать одно из этих значений: + +* `feat` для добавления новой функциональности +* `fix` для исправления бага в программе +* `refactor` для рефакторинга кода, например, переименования переменной +* `test` для добавления тестов, их рефакторинга +* `struct` для изменений связанных с изменением структуры проекта (НО НЕ КОДА), например изменение + расположения папок +* `ci` для различных задач ci/cd + +Поле `` содержит суть изменений в повелительном наклонении настоящего времени на английском языке без точки в +конце, первое слово - глагол с маленькой буквы. +Примеры: + +* Хорошо: "feat: Add module for future BST implementations" +* Плохо: "Added module for future BST implementations." + +## Правила для пулл реквестов + +**НЕ ТЫКАТЬ НА ЗЕЛЕНУЮ КНОПОЧКУ `REBASE AND MERGE` БЕЗ РЕВЬЮ** + +**Запрещено** сливать свой пулл реквест в ветку самостоятельно. + +Если тыкаете на зеленую кнопочку, то **убедитесь**, что на ней написано `REBASE AND MERGE` + +Ревью происходит в виде комметариев к пулл реквестам, обсуждения в чате команды и личном общении. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index 97401ee..f3a0e72 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,18 +1,18 @@ -Home / Licenses Apache License 2.0 A permissive license whose main conditions require preservation of copyright and license notices. Contributors provide an express grant of patent rights. Licensed works, modifications, and larger works may be distributed under different terms and without source code. -Permissions Conditions Limitations - Commercial use - Distribution - Modification - Patent use - Private use - License and copyright notice - State changes - Liability - Trademark use - Warranty +Permissions Conditions +Limitations +Commercial use +Distribution +Modification +Patent use +Private use +License and copyright notice +State changes +Liability +Trademark use +Warranty Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -213,23 +213,4 @@ Permissions Conditions Limitations 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. -Copy license text to clipboard -Suggest this license -Make a pull request to suggest this license for a project that is not licensed. Please be polite: see if a license has already been suggested, try to suggest a license fitting for the project’s community, and keep your communication with project maintainers friendly. - -Enter GitHub repository URL -How to apply this license -Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file. - -Optional steps -The Apache Software Foundation recommends taking the additional step of adding a boilerplate notice to the header of each source file. You can find the notice in the appendix at the very end of the license text. - -Add Apache-2.0 to your project’s package description, if applicable (e.g., Node.js, Ruby, and Rust). This will ensure the license is displayed in package directories. - - Source -Who’s using this license? -Kubernetes -PDF.js -Swift -About Terms of Service Help improve this pageThe content of this site is licensed under the Creative Commons Attribution 3.0 Unported License.Curated with ❤️ by GitHub, Inc. and You! \ No newline at end of file + limitations under the License. \ No newline at end of file From f7a83f8921dfc31ccb01e1578b1961745090506b Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 24 Mar 2023 22:33:16 +0300 Subject: [PATCH 05/90] fix: fix generics in interface Tree feat: release simple_add function --- src/main/kotlin/ABSTree.kt | 39 +++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt index 45f4168..9424a5f 100644 --- a/src/main/kotlin/ABSTree.kt +++ b/src/main/kotlin/ABSTree.kt @@ -1,16 +1,37 @@ -import Node -interface Tree { - fun add() - fun contain() - fun delete() +interface Tree> { + fun add(data: T) + fun contain(data: T) + fun delete(data: T) } -abstract class ABSTree, V : Any>: Tree { +abstract class ABSTree, V : Any> : Tree> { var root: BSNode? = null - fun simple_add(key: K, value: V) { - val node = BSNode(KeyValue(key, value)) + fun simple_add(data: KeyValue) { + val node = BSNode(data) + if (root == null) { + root = node + return + } + var curNode = root!! while (true) { + + if (node < curNode) { + if (curNode.left == null) { + curNode.left = node + break + } else { + curNode = curNode.left!! + } + + } else if (node > curNode) { + if (curNode.right == null) { + curNode.right = node + break + } else { + curNode = curNode.right!! + } + } + } } - fun simple_delete() {} } \ No newline at end of file From d5751e57280033dd2774cfd06d82fbcd407e7979 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 24 Mar 2023 22:34:41 +0300 Subject: [PATCH 06/90] fix: fix interface Node feat: add fun compareTo for BSNode --- src/main/kotlin/Node.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt index f323457..de768a1 100644 --- a/src/main/kotlin/Node.kt +++ b/src/main/kotlin/Node.kt @@ -2,21 +2,25 @@ interface Node, Subtype : Node> { var data: T var left: Subtype? var right: Subtype? - fun getKey() : Any - fun getValue() : Any } class BSNode, V : Any>(override var data: KeyValue) : Node, BSNode> { override var left: BSNode? = null override var right: BSNode? = null - override fun getKey(): K { + fun getKey(): K { return data.getKey() } - override fun getValue(): V { + fun getValue(): V { return data.getValue() } + operator fun compareTo(other: BSNode?): Int { + if (other != null) { + return data.getKey().compareTo(other.getKey()) + } + return 0 + } } class KeyValue, V>(private val key: K, private var value: V) : Comparable> { From f17f3b800ec57f14086a7f93e53defcf7bd714bd Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 24 Mar 2023 22:35:51 +0300 Subject: [PATCH 07/90] fix: add .idea files (trash) --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 34d75bc..812ebeb 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,9 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store +/.idea/.name +/.idea/gradle.xml +/.idea/kotlinc.xml +/.idea/misc.xml +/.idea/vcs.xml From fe3d13485ea9a6cef122040b76112f2123be6da6 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 24 Mar 2023 23:01:56 +0300 Subject: [PATCH 08/90] feat: change generics in Tree, now it has more clear structure --- src/main/kotlin/ABSTree.kt | 17 ++++++++--------- src/main/kotlin/Node.kt | 11 +++++++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt index 9424a5f..ff9ede9 100644 --- a/src/main/kotlin/ABSTree.kt +++ b/src/main/kotlin/ABSTree.kt @@ -4,28 +4,27 @@ interface Tree> { fun delete(data: T) } -abstract class ABSTree, V : Any> : Tree> { - var root: BSNode? = null - fun simple_add(data: KeyValue) { - val node = BSNode(data) +abstract class ABSTree, NodeType : Node> : Tree { + var root: NodeType? = null + fun simple_add(data: NodeType) { if (root == null) { - root = node + root = data return } var curNode = root!! while (true) { - if (node < curNode) { + if (data < curNode) { if (curNode.left == null) { - curNode.left = node + curNode.left = data break } else { curNode = curNode.left!! } - } else if (node > curNode) { + } else if (data > curNode) { if (curNode.right == null) { - curNode.right = node + curNode.right = data break } else { curNode = curNode.right!! diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt index de768a1..1d8a8ba 100644 --- a/src/main/kotlin/Node.kt +++ b/src/main/kotlin/Node.kt @@ -1,10 +1,14 @@ -interface Node, Subtype : Node> { +interface Node, Subtype : Node> : Comparable> { + override operator fun compareTo(other: Node): Int { + return data.compareTo(other.data) + } + var data: T var left: Subtype? var right: Subtype? } -class BSNode, V : Any>(override var data: KeyValue) : Node, BSNode> { +class BSNode, V : Any>(override var data: KeyValue) : Node, BSNode> { override var left: BSNode? = null override var right: BSNode? = null @@ -15,6 +19,7 @@ class BSNode, V : Any>(override var data: KeyValue) : N fun getValue(): V { return data.getValue() } + operator fun compareTo(other: BSNode?): Int { if (other != null) { return data.getKey().compareTo(other.getKey()) @@ -27,9 +32,11 @@ class KeyValue, V>(private val key: K, private var value: V) : fun getKey(): K { return key } + fun getValue(): V { return value } + override fun compareTo(other: KeyValue): Int { return key.compareTo(other.key) } From 6898fb502f14335293f6e68a127556989a41a2fe Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 07:48:57 +0300 Subject: [PATCH 09/90] fix: change interface Tree, root in ABSTree is protected refactor: rename simple_add to simpleAdd --- src/main/kotlin/ABSTree.kt | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt index ff9ede9..a32fa43 100644 --- a/src/main/kotlin/ABSTree.kt +++ b/src/main/kotlin/ABSTree.kt @@ -1,12 +1,12 @@ interface Tree> { fun add(data: T) - fun contain(data: T) + fun contain(data: T) : Boolean fun delete(data: T) } abstract class ABSTree, NodeType : Node> : Tree { - var root: NodeType? = null - fun simple_add(data: NodeType) { + protected var root: NodeType? = null + fun simpleAdd(data: NodeType) { if (root == null) { root = data return @@ -33,4 +33,32 @@ abstract class ABSTree, NodeType : Node> : Tree curNode) { + if (curNode.right == null) { + break + } else { + curNode = curNode.right!! + } + } else if (node == curNode) { + return true + } + } + + return false + } } \ No newline at end of file From 1ca3445512311385ed400e56c24fb9cd25e4ad6b Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 07:50:27 +0300 Subject: [PATCH 10/90] fix: fix compareTo for Node and BSNode feat: add fun equals for KeyValue and BSNode --- src/main/kotlin/Node.kt | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt index 1d8a8ba..3652b0b 100644 --- a/src/main/kotlin/Node.kt +++ b/src/main/kotlin/Node.kt @@ -1,16 +1,23 @@ interface Node, Subtype : Node> : Comparable> { - override operator fun compareTo(other: Node): Int { - return data.compareTo(other.data) - } var data: T var left: Subtype? var right: Subtype? } -class BSNode, V : Any>(override var data: KeyValue) : Node, BSNode> { +class BSNode, V>(override var data: KeyValue) : Node, BSNode> { override var left: BSNode? = null override var right: BSNode? = null + override fun compareTo(other: Node, BSNode>): Int { + return data.getKey().compareTo(other.data.getKey()) + } + + override fun equals(other: Any?): Boolean { + if (other is BSNode<*, *>) { + return data.equals(other.data) + } + return false + } fun getKey(): K { return data.getKey() @@ -19,13 +26,6 @@ class BSNode, V : Any>(override var data: KeyValue) : No fun getValue(): V { return data.getValue() } - - operator fun compareTo(other: BSNode?): Int { - if (other != null) { - return data.getKey().compareTo(other.getKey()) - } - return 0 - } } class KeyValue, V>(private val key: K, private var value: V) : Comparable> { @@ -41,4 +41,11 @@ class KeyValue, V>(private val key: K, private var value: V) : return key.compareTo(other.key) } + override fun equals(other: Any?): Boolean { + if (other is KeyValue<*, *>) { + return key.equals(other.getKey()) + } + return false + } + } \ No newline at end of file From c7ee8b5a1f35464677120170ebe82fc528bfaccd Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 07:51:38 +0300 Subject: [PATCH 11/90] feat: add early BSTree --- src/main/kotlin/BSTree.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/kotlin/BSTree.kt diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt new file mode 100644 index 0000000..e7367a7 --- /dev/null +++ b/src/main/kotlin/BSTree.kt @@ -0,0 +1,17 @@ +class BSTree, V> : ABSTree, BSNode>() { + + + override fun add(data: KeyValue) { + simpleAdd(BSNode(data)) + } + + override fun contain(data: KeyValue): Boolean { + return simpleContains(BSNode(data)) + } + + override fun delete(data: KeyValue) { + TODO("Not yet implemented") + } + + +} \ No newline at end of file From 86a611ee613e2f8b831876913f728b63d3f8caef Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 22:38:40 +0300 Subject: [PATCH 12/90] fix: delete .idea --- .idea/.gitignore | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .idea/.gitignore diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml From 3749a5f914d07a0f7813cd3027c8ccd33bb07fa7 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 22:39:40 +0300 Subject: [PATCH 13/90] fix: add .idea to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 812ebeb..f75d2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ bin/ /.idea/kotlinc.xml /.idea/misc.xml /.idea/vcs.xml +/.idea/ From caa7bb2aaa261414bd3d24a9d02d1b3caf98194d Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 23:42:00 +0300 Subject: [PATCH 14/90] feat: add fun get for BSTree --- src/main/kotlin/BSTree.kt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index e7367a7..427ffd9 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -9,6 +9,34 @@ class BSTree, V> : ABSTree, BSNode>() { return simpleContains(BSNode(data)) } + fun get(key: K): V? { + if (root == null) { + return null + } + var curNode = root!! + + while (true) { + + if (key < curNode.data.getKey()) { + if (curNode.left == null) { + break + } else { + curNode = curNode.left!! + } + + } else if (key > curNode.data.getKey()) { + if (curNode.right == null) { + break + } else { + curNode = curNode.right!! + } + } else if (key == curNode.data.getKey()) { + return curNode.data.getValue() + } + } + return null + } + override fun delete(data: KeyValue) { TODO("Not yet implemented") } From 064af09aa9168d819c174d17180efbe9ce7a84bc Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 23:42:36 +0300 Subject: [PATCH 15/90] feat: add filed parent for interface Node --- src/main/kotlin/Node.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt index 3652b0b..d5b2240 100644 --- a/src/main/kotlin/Node.kt +++ b/src/main/kotlin/Node.kt @@ -1,13 +1,15 @@ interface Node, Subtype : Node> : Comparable> { - var data: T var left: Subtype? var right: Subtype? + var parent: Subtype? } class BSNode, V>(override var data: KeyValue) : Node, BSNode> { override var left: BSNode? = null override var right: BSNode? = null + override var parent: BSNode? = null + override fun compareTo(other: Node, BSNode>): Int { return data.getKey().compareTo(other.data.getKey()) } From c5c003bacf96340bbca57386b8c366ca4ca7f7b4 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sat, 25 Mar 2023 23:42:55 +0300 Subject: [PATCH 16/90] fix: fix version of gradle --- gradle/wrapper/gradle-wrapper.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 60c76b3..2235886 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Sat Mar 25 23:27:28 MSK 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists From 0a844502faf0cf8b0c5d0ad67c996003ef90a9ab Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sun, 26 Mar 2023 05:46:39 +0300 Subject: [PATCH 17/90] feat: add funs simpleDelete, getMinimal and getMaximal fix: change returned type of simpleContains to NodeType fix: field parent is correct now (in simpleAdd) --- src/main/kotlin/ABSTree.kt | 127 +++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt index a32fa43..8445e9b 100644 --- a/src/main/kotlin/ABSTree.kt +++ b/src/main/kotlin/ABSTree.kt @@ -16,6 +16,7 @@ abstract class ABSTree, NodeType : Node> : Tree, NodeType : Node> : Tree curNode) { if (curNode.right == null) { + data.parent = curNode curNode.right = data break } else { curNode = curNode.right!! } } - } } - fun simpleContains(node: NodeType): Boolean { + fun simpleContains(node: NodeType): NodeType? { if (root == null) { - return false + return null } var curNode = root!! @@ -55,10 +56,126 @@ abstract class ABSTree, NodeType : Node> : Tree Date: Sun, 26 Mar 2023 05:48:38 +0300 Subject: [PATCH 18/90] fix: change contains function to be compatible with simpleContains --- src/main/kotlin/BSTree.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 427ffd9..374e1bb 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -6,7 +6,7 @@ class BSTree, V> : ABSTree, BSNode>() { } override fun contain(data: KeyValue): Boolean { - return simpleContains(BSNode(data)) + return (simpleContains(BSNode(data)) != null) } fun get(key: K): V? { @@ -38,7 +38,8 @@ class BSTree, V> : ABSTree, BSNode>() { } override fun delete(data: KeyValue) { - TODO("Not yet implemented") + val curNode = simpleContains(BSNode(data)) ?: return + simpleDelete(curNode) } From 3a81f3d181e0cfa7ef0dbb2ee2e8100ea561eff1 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 06:48:33 +0300 Subject: [PATCH 19/90] feat: add copyright header for source files refactor: reformat source files --- src/main/kotlin/ABSTree.kt | 12 +++++++++--- src/main/kotlin/BSTree.kt | 5 +++++ src/main/kotlin/Node.kt | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt index 8445e9b..954a405 100644 --- a/src/main/kotlin/ABSTree.kt +++ b/src/main/kotlin/ABSTree.kt @@ -1,6 +1,11 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + interface Tree> { fun add(data: T) - fun contain(data: T) : Boolean + fun contain(data: T): Boolean fun delete(data: T) } @@ -34,6 +39,7 @@ abstract class ABSTree, NodeType : Node> : Tree, NodeType : Node> : Tree, NodeType : Node> : Tree, V> : ABSTree, BSNode>() { diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt index d5b2240..2acaa02 100644 --- a/src/main/kotlin/Node.kt +++ b/src/main/kotlin/Node.kt @@ -1,3 +1,8 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + interface Node, Subtype : Node> : Comparable> { var data: T var left: Subtype? From 79de5488f480f1cdfb34285d9bcea64d37f81644 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 06:51:46 +0300 Subject: [PATCH 20/90] feat: add workflows for github, it has to check project build on creating pull request into main or release --- .github/mergeable.yml | 15 +++++++++++++++ .github/workflows/github-actions.yml | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .github/mergeable.yml create mode 100644 .github/workflows/github-actions.yml diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 0000000..a575717 --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,15 @@ +version: 2 +mergeable: + - when: pull_request.* + name: 'Check decoration' + validate: + - do: title + must_exclude: + regex: ^\[WIP\] + - do: label + must_exclude: + regex: 'wip' + - do: description + no_empty: + enabled: true + message: Description matter and should not be empty. Provide detail with **what** was changed, **why** it was changed, and **how** it was changed. \ No newline at end of file diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..de063b2 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,25 @@ +name: Build CI + +on: + pull_request: + branches: + - main + - release + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Build with Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: build \ No newline at end of file From e6f0ee6e98bbad7bc10ff1d943b2c9ba4f2ff0a9 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 06:58:33 +0300 Subject: [PATCH 21/90] fix: change platform for build and tests --- .github/workflows/github-actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index de063b2..9aa8fbc 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - uses: actions/checkout@v3 From 542a0925ee21e788026f347ac4109a364d0dbc75 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 21:12:31 +0300 Subject: [PATCH 22/90] struct: change struct project for gradle building --- src/main/kotlin/ABSTree.kt | 187 ------------------------------------- src/main/kotlin/BSTree.kt | 51 ---------- src/main/kotlin/Main.kt | 6 -- src/main/kotlin/Node.kt | 58 ------------ 4 files changed, 302 deletions(-) delete mode 100644 src/main/kotlin/ABSTree.kt delete mode 100644 src/main/kotlin/BSTree.kt delete mode 100644 src/main/kotlin/Main.kt delete mode 100644 src/main/kotlin/Node.kt diff --git a/src/main/kotlin/ABSTree.kt b/src/main/kotlin/ABSTree.kt deleted file mode 100644 index 954a405..0000000 --- a/src/main/kotlin/ABSTree.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -interface Tree> { - fun add(data: T) - fun contain(data: T): Boolean - fun delete(data: T) -} - -abstract class ABSTree, NodeType : Node> : Tree { - protected var root: NodeType? = null - fun simpleAdd(data: NodeType) { - if (root == null) { - root = data - return - } - var curNode = root!! - while (true) { - - if (data < curNode) { - if (curNode.left == null) { - data.parent = curNode - curNode.left = data - break - } else { - curNode = curNode.left!! - } - - } else if (data > curNode) { - if (curNode.right == null) { - data.parent = curNode - curNode.right = data - break - } else { - curNode = curNode.right!! - } - } - } - } - - fun simpleContains(node: NodeType): NodeType? { - if (root == null) { - return null - } - var curNode = root!! - - while (true) { - - if (node < curNode) { - if (curNode.left == null) { - break - } else { - curNode = curNode.left!! - } - - } else if (node > curNode) { - if (curNode.right == null) { - break - } else { - curNode = curNode.right!! - } - } else if (node == curNode) { - return curNode - } - } - - return null - } - - fun simpleDelete(node: NodeType) { - - if (node == root) { - if ((node.left == null) and (node.right != null)) { - root = node.right - } else if ((node.right == null) and (node.left != null)) { - root = node.left - } else if ((node.right == null) and (node.left == null)) { - root = null - } else { - val curNode = root?.right ?: error("Not reachable") - val newNode = getMinimal(curNode) - if (newNode.parent?.left == newNode) { - newNode.parent?.left = null - } else { - newNode.parent?.right = null - } - newNode.left = root?.left - newNode.right = root?.right - root = newNode - } - root?.parent = null - return - } - val parent = node.parent ?: error("Not reachable") - val isRight = (parent.right == node) - - if ((node.left == null) and (node.right != null)) { - if (isRight) { - parent.right = node.right - node.right?.parent = parent - } else { - parent.left = node.right - node.right?.parent = parent - } - - } else if ((node.right == null) and (node.left != null)) { - if (isRight) { - parent.right = node.left - node.left?.parent = parent - } else { - parent.left = node.left - node.left?.parent = parent - } - - } else if ((node.right == null) and (node.left == null)) { - if (isRight) { - parent.right = null - } else { - parent.left = null - } - } else { - val newNode = node.right?.let { getMinimal(it) } - if (newNode?.parent?.left == newNode) { - newNode?.parent?.left = null - } else { - newNode?.parent?.right = null - } - if (isRight) { - parent.right = newNode - } else { - parent.left = newNode - } - newNode?.parent = parent - } - - } - - fun getMinimal(node: NodeType): NodeType { - var minNode = node - - while (true) { - if ((minNode.left != null)) { - break - } else if (minNode.right != null) { - minNode = minNode.right ?: error("Mot reachable") - } else { - return minNode - } - } - - while (true) { - if (minNode.left == null) { - break - } else { - minNode = minNode.left ?: error("Not reachable") - } - } - - return minNode - } - - fun getMaximal(node: NodeType): NodeType { - var maxNode = node - - while (true) { - if ((maxNode.right != null)) { - break - } else if (maxNode.left != null) { - maxNode = maxNode.left ?: error("Mot reachable") - } else { - return maxNode - } - } - - while (true) { - if (maxNode.right == null) { - break - } else { - maxNode = maxNode.right ?: error("Not reachable") - } - } - - return maxNode - } -} \ No newline at end of file diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt deleted file mode 100644 index 337dcac..0000000 --- a/src/main/kotlin/BSTree.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -class BSTree, V> : ABSTree, BSNode>() { - - - override fun add(data: KeyValue) { - simpleAdd(BSNode(data)) - } - - override fun contain(data: KeyValue): Boolean { - return (simpleContains(BSNode(data)) != null) - } - - fun get(key: K): V? { - if (root == null) { - return null - } - var curNode = root!! - - while (true) { - - if (key < curNode.data.getKey()) { - if (curNode.left == null) { - break - } else { - curNode = curNode.left!! - } - - } else if (key > curNode.data.getKey()) { - if (curNode.right == null) { - break - } else { - curNode = curNode.right!! - } - } else if (key == curNode.data.getKey()) { - return curNode.data.getValue() - } - } - return null - } - - override fun delete(data: KeyValue) { - val curNode = simpleContains(BSNode(data)) ?: return - simpleDelete(curNode) - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt deleted file mode 100644 index 97f405e..0000000 --- a/src/main/kotlin/Main.kt +++ /dev/null @@ -1,6 +0,0 @@ -import ABSTree -import Node -fun main(args: Array) { - val sds = KeyValue(50, 70) - println(sds.getKey()) -} \ No newline at end of file diff --git a/src/main/kotlin/Node.kt b/src/main/kotlin/Node.kt deleted file mode 100644 index 2acaa02..0000000 --- a/src/main/kotlin/Node.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -interface Node, Subtype : Node> : Comparable> { - var data: T - var left: Subtype? - var right: Subtype? - var parent: Subtype? -} - -class BSNode, V>(override var data: KeyValue) : Node, BSNode> { - override var left: BSNode? = null - override var right: BSNode? = null - override var parent: BSNode? = null - - override fun compareTo(other: Node, BSNode>): Int { - return data.getKey().compareTo(other.data.getKey()) - } - - override fun equals(other: Any?): Boolean { - if (other is BSNode<*, *>) { - return data.equals(other.data) - } - return false - } - - fun getKey(): K { - return data.getKey() - } - - fun getValue(): V { - return data.getValue() - } -} - -class KeyValue, V>(private val key: K, private var value: V) : Comparable> { - fun getKey(): K { - return key - } - - fun getValue(): V { - return value - } - - override fun compareTo(other: KeyValue): Int { - return key.compareTo(other.key) - } - - override fun equals(other: Any?): Boolean { - if (other is KeyValue<*, *>) { - return key.equals(other.getKey()) - } - return false - } - -} \ No newline at end of file From a299c66c07276c51da6ba132845ad785794c3555 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 21:12:59 +0300 Subject: [PATCH 23/90] feat: init correct build project --- build.gradle.kts | 27 --- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 234 ----------------------- gradlew.bat | 89 --------- settings.gradle.kts | 3 - 7 files changed, 360 deletions(-) delete mode 100644 build.gradle.kts delete mode 100644 gradle.properties delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100644 gradlew delete mode 100644 gradlew.bat delete mode 100644 settings.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 766f636..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - kotlin("jvm") version "1.8.0" - application -} - -group = "org.example" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() -} - -dependencies { - testImplementation(kotlin("test")) -} - -tasks.test { - useJUnitPlatform() -} - -kotlin { - jvmToolchain(8) -} - -application { - mainClass.set("MainKt") -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 7fc6f1f..0000000 --- a/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 249e5832f090a2944b7473328c07c9755baa3196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60756 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 107acd3..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 9cbe239..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ - -rootProject.name = "Trees" - From 740e2205750b60e0f91249235648f1f17c1a8499 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 21:13:31 +0300 Subject: [PATCH 24/90] feat: add build directory --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index f75d2ef..d1fbeba 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,7 @@ bin/ /.idea/misc.xml /.idea/vcs.xml /.idea/ + +# Ignore Gradle build output directory +build +/app/build \ No newline at end of file From c0f356155c10bf9da209c6013681c0d21d7bc420 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 21:15:07 +0300 Subject: [PATCH 25/90] feat: init correct gradle project struct: change struct of project for gradle building --- .gitattributes | 9 + app/build.gradle.kts | 41 ++++ app/src/main/kotlin/trees/ABSTree.kt | 187 +++++++++++++++++ app/src/main/kotlin/trees/App.kt | 17 ++ app/src/main/kotlin/trees/BSTree.kt | 51 +++++ app/src/main/kotlin/trees/Main.kt | 9 + app/src/main/kotlin/trees/Node.kt | 58 ++++++ app/src/test/kotlin/trees/AppTest.kt | 14 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 +++++++++++++++++++++++ gradlew.bat | 92 +++++++++ settings.gradle.kts | 11 + 13 files changed, 739 insertions(+) create mode 100644 .gitattributes create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/kotlin/trees/ABSTree.kt create mode 100644 app/src/main/kotlin/trees/App.kt create mode 100644 app/src/main/kotlin/trees/BSTree.kt create mode 100644 app/src/main/kotlin/trees/Main.kt create mode 100644 app/src/main/kotlin/trees/Node.kt create mode 100644 app/src/test/kotlin/trees/AppTest.kt create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..e0db6de --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,41 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Kotlin application project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + id("org.jetbrains.kotlin.jvm") version "1.8.10" + + // Apply the application plugin to add support for building a CLI application in Java. + application +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use the Kotlin JUnit 5 integration. + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + + // Use the JUnit 5 integration. + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.1") + + // This dependency is used by the application. + implementation("com.google.guava:guava:31.1-jre") +} + +application { + // Define the main class for the application. + mainClass.set("trees.AppKt") +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt new file mode 100644 index 0000000..5afd59a --- /dev/null +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +interface Tree> { + fun add(data: T) + fun contain(data: T): Boolean + fun delete(data: T) +} + +abstract class ABSTree, NodeType : Node> : Tree { + protected var root: NodeType? = null + fun simpleAdd(data: NodeType) { + if (root == null) { + root = data + return + } + var curNode = root!! + while (true) { + + if (data < curNode) { + if (curNode.left == null) { + data.parent = curNode + curNode.left = data + break + } else { + curNode = curNode.left!! + } + + } else if (data > curNode) { + if (curNode.right == null) { + data.parent = curNode + curNode.right = data + break + } else { + curNode = curNode.right!! + } + } + } + } + + fun simpleContains(node: NodeType): NodeType? { + if (root == null) { + return null + } + var curNode = root!! + + while (true) { + + if (node < curNode) { + if (curNode.left == null) { + break + } else { + curNode = curNode.left!! + } + + } else if (node > curNode) { + if (curNode.right == null) { + break + } else { + curNode = curNode.right!! + } + } else if (node == curNode) { + return curNode + } + } + + return null + } + + fun simpleDelete(node: NodeType) { + + if (node == root) { + if ((node.left == null) and (node.right != null)) { + root = node.right + } else if ((node.right == null) and (node.left != null)) { + root = node.left + } else if ((node.right == null) and (node.left == null)) { + root = null + } else { + val curNode = root?.right ?: error("Not reachable") + val newNode = getMinimal(curNode) + if (newNode.parent?.left == newNode) { + newNode.parent?.left = null + } else { + newNode.parent?.right = null + } + newNode.left = root?.left + newNode.right = root?.right + root = newNode + } + root?.parent = null + return + } + val parent = node.parent ?: error("Not reachable") + val isRight = (parent.right == node) + + if ((node.left == null) and (node.right != null)) { + if (isRight) { + parent.right = node.right + node.right?.parent = parent + } else { + parent.left = node.right + node.right?.parent = parent + } + + } else if ((node.right == null) and (node.left != null)) { + if (isRight) { + parent.right = node.left + node.left?.parent = parent + } else { + parent.left = node.left + node.left?.parent = parent + } + + } else if ((node.right == null) and (node.left == null)) { + if (isRight) { + parent.right = null + } else { + parent.left = null + } + } else { + val newNode = node.right?.let { getMinimal(it) } + if (newNode?.parent?.left == newNode) { + newNode?.parent?.left = null + } else { + newNode?.parent?.right = null + } + if (isRight) { + parent.right = newNode + } else { + parent.left = newNode + } + newNode?.parent = parent + } + + } + + fun getMinimal(node: NodeType): NodeType { + var minNode = node + + while (true) { + if ((minNode.left != null)) { + break + } else if (minNode.right != null) { + minNode = minNode.right ?: error("Mot reachable") + } else { + return minNode + } + } + + while (true) { + if (minNode.left == null) { + break + } else { + minNode = minNode.left ?: error("Not reachable") + } + } + + return minNode + } + + fun getMaximal(node: NodeType): NodeType { + var maxNode = node + + while (true) { + if ((maxNode.right != null)) { + break + } else if (maxNode.left != null) { + maxNode = maxNode.left ?: error("Mot reachable") + } else { + return maxNode + } + } + + while (true) { + if (maxNode.right == null) { + break + } else { + maxNode = maxNode.right ?: error("Not reachable") + } + } + + return maxNode + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/App.kt b/app/src/main/kotlin/trees/App.kt new file mode 100644 index 0000000..97c582b --- /dev/null +++ b/app/src/main/kotlin/trees/App.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ +package trees +import KeyValue + + +class App { + val greeting: String + get() { + return "Hello World!" + } +} + +fun main(args: Array) { +} diff --git a/app/src/main/kotlin/trees/BSTree.kt b/app/src/main/kotlin/trees/BSTree.kt new file mode 100644 index 0000000..a230b8b --- /dev/null +++ b/app/src/main/kotlin/trees/BSTree.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +class BSTree, V> : ABSTree, BSNode>() { + + + override fun add(data: KeyValue) { + simpleAdd(BSNode(data)) + } + + override fun contain(data: KeyValue): Boolean { + return (simpleContains(BSNode(data)) != null) + } + + fun get(key: K): V? { + if (root == null) { + return null + } + var curNode = root!! + + while (true) { + + if (key < curNode.data.getKey()) { + if (curNode.left == null) { + break + } else { + curNode = curNode.left!! + } + + } else if (key > curNode.data.getKey()) { + if (curNode.right == null) { + break + } else { + curNode = curNode.right!! + } + } else if (key == curNode.data.getKey()) { + return curNode.data.getValue() + } + } + return null + } + + override fun delete(data: KeyValue) { + val curNode = simpleContains(BSNode(data)) ?: return + simpleDelete(curNode) + } + + +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/Main.kt b/app/src/main/kotlin/trees/Main.kt new file mode 100644 index 0000000..7524ce2 --- /dev/null +++ b/app/src/main/kotlin/trees/Main.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import ABSTree +import Node +fun main(args: Array) { +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/Node.kt b/app/src/main/kotlin/trees/Node.kt new file mode 100644 index 0000000..82e733e --- /dev/null +++ b/app/src/main/kotlin/trees/Node.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +interface Node, Subtype : Node> : Comparable> { + var data: T + var left: Subtype? + var right: Subtype? + var parent: Subtype? +} + +class BSNode, V>(override var data: KeyValue) : Node, BSNode> { + override var left: BSNode? = null + override var right: BSNode? = null + override var parent: BSNode? = null + + override fun compareTo(other: Node, BSNode>): Int { + return data.getKey().compareTo(other.data.getKey()) + } + + override fun equals(other: Any?): Boolean { + if (other is BSNode<*, *>) { + return data.equals(other.data) + } + return false + } + + fun getKey(): K { + return data.getKey() + } + + fun getValue(): V { + return data.getValue() + } +} + +class KeyValue, V>(private val key: K, private var value: V) : Comparable> { + fun getKey(): K { + return key + } + + fun getValue(): V { + return value + } + + override fun compareTo(other: KeyValue): Int { + return key.compareTo(other.key) + } + + override fun equals(other: Any?): Boolean { + if (other is KeyValue<*, *>) { + return key.equals(other.getKey()) + } + return false + } + +} \ No newline at end of file diff --git a/app/src/test/kotlin/trees/AppTest.kt b/app/src/test/kotlin/trees/AppTest.kt new file mode 100644 index 0000000..93fb359 --- /dev/null +++ b/app/src/test/kotlin/trees/AppTest.kt @@ -0,0 +1,14 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package trees + +import kotlin.test.Test +import kotlin.test.assertNotNull + +class AppTest { + @Test fun appHasAGreeting() { + val classUnderTest = App() + assertNotNull(classUnderTest.greeting, "app should have a greeting") + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bdc9a83 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..79a61d4 --- /dev/null +++ b/gradlew @@ -0,0 +1,244 @@ +#!/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##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=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=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 + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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..93e3f59 --- /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. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% 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/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e26452f --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html + */ + +rootProject.name = "trees-8" +include("app") From c135272634c3fd3b33fe4fd2c5c4fd484e766b33 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 27 Mar 2023 21:57:58 +0300 Subject: [PATCH 26/90] fix: refactor fun get, now it uses simpleContains from ABSTree fix: change main class in build.gradle.kts struct: delete App.kt (useless) --- app/build.gradle.kts | 10 +++++----- app/src/main/kotlin/trees/App.kt | 17 ----------------- app/src/main/kotlin/trees/BSTree.kt | 26 +------------------------- app/src/main/kotlin/trees/Main.kt | 5 +++-- app/src/main/kotlin/trees/Node.kt | 6 +++--- app/src/test/kotlin/trees/AppTest.kt | 5 ++--- 6 files changed, 14 insertions(+), 55 deletions(-) delete mode 100644 app/src/main/kotlin/trees/App.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e0db6de..eeb2b36 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,10 +32,10 @@ dependencies { application { // Define the main class for the application. - mainClass.set("trees.AppKt") + mainClass.set("MainKt") } -tasks.named("test") { - // Use JUnit Platform for unit tests. - useJUnitPlatform() -} +//tasks.named("test") { +// // Use JUnit Platform for unit tests. +// useJUnitPlatform() +//} diff --git a/app/src/main/kotlin/trees/App.kt b/app/src/main/kotlin/trees/App.kt deleted file mode 100644 index 97c582b..0000000 --- a/app/src/main/kotlin/trees/App.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ -package trees -import KeyValue - - -class App { - val greeting: String - get() { - return "Hello World!" - } -} - -fun main(args: Array) { -} diff --git a/app/src/main/kotlin/trees/BSTree.kt b/app/src/main/kotlin/trees/BSTree.kt index a230b8b..95d7860 100644 --- a/app/src/main/kotlin/trees/BSTree.kt +++ b/app/src/main/kotlin/trees/BSTree.kt @@ -15,31 +15,7 @@ class BSTree, V> : ABSTree, BSNode>() { } fun get(key: K): V? { - if (root == null) { - return null - } - var curNode = root!! - - while (true) { - - if (key < curNode.data.getKey()) { - if (curNode.left == null) { - break - } else { - curNode = curNode.left!! - } - - } else if (key > curNode.data.getKey()) { - if (curNode.right == null) { - break - } else { - curNode = curNode.right!! - } - } else if (key == curNode.data.getKey()) { - return curNode.data.getValue() - } - } - return null + return simpleContains(BSNode(KeyValue(key, null)))?.getValue() } override fun delete(data: KeyValue) { diff --git a/app/src/main/kotlin/trees/Main.kt b/app/src/main/kotlin/trees/Main.kt index 7524ce2..abdd84c 100644 --- a/app/src/main/kotlin/trees/Main.kt +++ b/app/src/main/kotlin/trees/Main.kt @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import ABSTree -import Node fun main(args: Array) { + val tree = BSTree() + tree.add(KeyValue(10, 100)) + println(tree.get(10)) } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/Node.kt b/app/src/main/kotlin/trees/Node.kt index 82e733e..080a1da 100644 --- a/app/src/main/kotlin/trees/Node.kt +++ b/app/src/main/kotlin/trees/Node.kt @@ -30,17 +30,17 @@ class BSNode, V>(override var data: KeyValue) : Node, V>(private val key: K, private var value: V) : Comparable> { +class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { fun getKey(): K { return key } - fun getValue(): V { + fun getValue(): V? { return value } diff --git a/app/src/test/kotlin/trees/AppTest.kt b/app/src/test/kotlin/trees/AppTest.kt index 93fb359..8acbe2c 100644 --- a/app/src/test/kotlin/trees/AppTest.kt +++ b/app/src/test/kotlin/trees/AppTest.kt @@ -4,11 +4,10 @@ package trees import kotlin.test.Test -import kotlin.test.assertNotNull class AppTest { @Test fun appHasAGreeting() { - val classUnderTest = App() - assertNotNull(classUnderTest.greeting, "app should have a greeting") +// val classUnderTest = App() +// assertNotNull(classUnderTest.greeting, "app should have a greeting") } } From b4e7919949545095f89906e3c51d0f69d9f42514 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Wed, 29 Mar 2023 05:01:21 +0300 Subject: [PATCH 27/90] fix: fix funs getMinimal nad getMaximal --- app/src/main/kotlin/trees/ABSTree.kt | 36 ++-------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 5afd59a..7ba4a4c 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -139,49 +139,17 @@ abstract class ABSTree, NodeType : Node> : Tree Date: Wed, 29 Mar 2023 07:38:43 +0300 Subject: [PATCH 28/90] fix: huge refactoring of methods, now it are more simple and clear --- app/src/main/kotlin/trees/ABSTree.kt | 139 ++++++--------------------- 1 file changed, 31 insertions(+), 108 deletions(-) diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 7ba4a4c..1a69605 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -11,130 +11,53 @@ interface Tree> { abstract class ABSTree, NodeType : Node> : Tree { protected var root: NodeType? = null - fun simpleAdd(data: NodeType) { - if (root == null) { - root = data - return - } - var curNode = root!! - while (true) { - if (data < curNode) { - if (curNode.left == null) { - data.parent = curNode - curNode.left = data - break - } else { - curNode = curNode.left!! - } + fun simpleAdd(initNode: NodeType?, node: NodeType): NodeType { - } else if (data > curNode) { - if (curNode.right == null) { - data.parent = curNode - curNode.right = data - break - } else { - curNode = curNode.right!! - } - } + if (initNode == null) { + return node } + + if (initNode < node) { + initNode.right = simpleAdd(initNode.right, node) + } else { + initNode.left = simpleAdd(initNode.left, node) + } + return initNode } - fun simpleContains(node: NodeType): NodeType? { - if (root == null) { + fun simpleContains(initNode: NodeType?, node: NodeType): NodeType? { + if (initNode == null) { return null } - var curNode = root!! - - while (true) { - - if (node < curNode) { - if (curNode.left == null) { - break - } else { - curNode = curNode.left!! - } - } else if (node > curNode) { - if (curNode.right == null) { - break - } else { - curNode = curNode.right!! - } - } else if (node == curNode) { - return curNode - } + return if (initNode < node) { + simpleContains(initNode.right, node) + } else if (initNode > node) { + simpleContains(initNode.left, node) + } else { + initNode } - - return null } - fun simpleDelete(node: NodeType) { - - if (node == root) { - if ((node.left == null) and (node.right != null)) { - root = node.right - } else if ((node.right == null) and (node.left != null)) { - root = node.left - } else if ((node.right == null) and (node.left == null)) { - root = null - } else { - val curNode = root?.right ?: error("Not reachable") - val newNode = getMinimal(curNode) - if (newNode.parent?.left == newNode) { - newNode.parent?.left = null - } else { - newNode.parent?.right = null - } - newNode.left = root?.left - newNode.right = root?.right - root = newNode - } - root?.parent = null - return + fun simpleDelete(initNode: NodeType?, node: NodeType): NodeType? { + if (initNode == null) { + return null } - val parent = node.parent ?: error("Not reachable") - val isRight = (parent.right == node) - - if ((node.left == null) and (node.right != null)) { - if (isRight) { - parent.right = node.right - node.right?.parent = parent - } else { - parent.left = node.right - node.right?.parent = parent - } - - } else if ((node.right == null) and (node.left != null)) { - if (isRight) { - parent.right = node.left - node.left?.parent = parent - } else { - parent.left = node.left - node.left?.parent = parent - } - - } else if ((node.right == null) and (node.left == null)) { - if (isRight) { - parent.right = null - } else { - parent.left = null - } + if (initNode < node) { + initNode.right = simpleDelete(initNode.right, node) + } else if (initNode > node) { + initNode.left = simpleDelete(initNode.left, node) } else { - val newNode = node.right?.let { getMinimal(it) } - if (newNode?.parent?.left == newNode) { - newNode?.parent?.left = null + if ((initNode.left == null) or (initNode.right == null)) { + return initNode.left ?: initNode.right } else { - newNode?.parent?.right = null + val tmp = getMinimal(initNode.right ?: error("Not reachable")) + initNode.data = tmp.data + initNode.right = simpleDelete(initNode.right, tmp) } - if (isRight) { - parent.right = newNode - } else { - parent.left = newNode - } - newNode?.parent = parent } - + return initNode } fun getMinimal(node: NodeType): NodeType { From ab537d2349fa846d7a18435224aa95f75f6e3fca Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Wed, 29 Mar 2023 07:39:43 +0300 Subject: [PATCH 29/90] fix: minor changes in the use of ABSTree functions --- app/src/main/kotlin/trees/BSTree.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/trees/BSTree.kt b/app/src/main/kotlin/trees/BSTree.kt index 95d7860..5f62e1e 100644 --- a/app/src/main/kotlin/trees/BSTree.kt +++ b/app/src/main/kotlin/trees/BSTree.kt @@ -7,21 +7,22 @@ class BSTree, V> : ABSTree, BSNode>() { override fun add(data: KeyValue) { - simpleAdd(BSNode(data)) + root = simpleAdd(root, BSNode(data)) } override fun contain(data: KeyValue): Boolean { - return (simpleContains(BSNode(data)) != null) + return (simpleContains(root, BSNode(data)) != null) } fun get(key: K): V? { - return simpleContains(BSNode(KeyValue(key, null)))?.getValue() + return simpleContains(root, BSNode(KeyValue(key, null)))?.getValue() } override fun delete(data: KeyValue) { - val curNode = simpleContains(BSNode(data)) ?: return - simpleDelete(curNode) + root = simpleDelete(root, BSNode(data)) } - + fun min(): KeyValue? { + return root?.let { getMinimal(it).data } + } } \ No newline at end of file From c5f45a3373b299f36b2b366ea6a4e1c0b1d747ea Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Wed, 29 Mar 2023 07:40:52 +0300 Subject: [PATCH 30/90] feat: add conversion KeyValue to string --- app/src/main/kotlin/trees/Node.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/trees/Node.kt b/app/src/main/kotlin/trees/Node.kt index 080a1da..d1391e1 100644 --- a/app/src/main/kotlin/trees/Node.kt +++ b/app/src/main/kotlin/trees/Node.kt @@ -55,4 +55,7 @@ class KeyValue, V>(private val key: K, private var value: V?) return false } + override fun toString(): String { + return "$key: $value" + } } \ No newline at end of file From ac10c0be88de03ce51178b4c012d5880977dd5fa Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Wed, 29 Mar 2023 08:08:46 +0300 Subject: [PATCH 31/90] feat: add functions rotateLeft and rotateRight --- app/src/main/kotlin/trees/ABSTree.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 1a69605..adeab8b 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -75,4 +75,20 @@ abstract class ABSTree, NodeType : Node> : Tree Date: Wed, 29 Mar 2023 08:25:27 +0300 Subject: [PATCH 32/90] fix: simple fix avoid null exception feat: add package --- app/src/main/kotlin/trees/ABSTree.kt | 12 +++++++++--- app/src/main/kotlin/trees/BSTree.kt | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index adeab8b..22a0c50 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -1,3 +1,7 @@ +package trees + +import Node + /* * Copyright 2023 teemEight * SPDX-License-Identifier: Apache-2.0 @@ -52,9 +56,11 @@ abstract class ABSTree, NodeType : Node> : Tree Date: Wed, 29 Mar 2023 09:07:34 +0300 Subject: [PATCH 33/90] fix: minor class BSTree initialization fix --- app/src/main/kotlin/trees/BSTree.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/trees/BSTree.kt b/app/src/main/kotlin/trees/BSTree.kt index 508f626..7f80c6f 100644 --- a/app/src/main/kotlin/trees/BSTree.kt +++ b/app/src/main/kotlin/trees/BSTree.kt @@ -1,11 +1,14 @@ -import trees.ABSTree +package trees + +import BSNode +import KeyValue /* * Copyright 2023 teemEight * SPDX-License-Identifier: Apache-2.0 */ -class BSTree, V> : ABSTree, BSNode>() { +class BSTree, V, T : KeyValue> : ABSTree, BSNode>() { override fun add(data: KeyValue) { From d41ad0d36cec3de72afddcba10db7775362264cc Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sun, 2 Apr 2023 19:24:21 +0300 Subject: [PATCH 34/90] struct: split all files into files for each class/interface fix: fix parent field feat: change generics, now it doesn't depend on KeyValue --- app/src/main/kotlin/trees/ABSTree.kt | 50 ++++++++++++++++----------- app/src/main/kotlin/trees/BSNode.kt | 20 +++++++++++ app/src/main/kotlin/trees/BSTree.kt | 17 ++++----- app/src/main/kotlin/trees/KeyValue.kt | 26 ++++++++++++++ app/src/main/kotlin/trees/Node.kt | 50 --------------------------- app/src/main/kotlin/trees/Tree.kt | 7 ++++ 6 files changed, 89 insertions(+), 81 deletions(-) create mode 100644 app/src/main/kotlin/trees/BSNode.kt create mode 100644 app/src/main/kotlin/trees/KeyValue.kt create mode 100644 app/src/main/kotlin/trees/Tree.kt diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 22a0c50..33846db 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -7,15 +7,14 @@ import Node * SPDX-License-Identifier: Apache-2.0 */ -interface Tree> { - fun add(data: T) - fun contain(data: T): Boolean - fun delete(data: T) -} abstract class ABSTree, NodeType : Node> : Tree { protected var root: NodeType? = null + fun rebalance(initNode: NodeType): NodeType { + return initNode + } + fun simpleAdd(initNode: NodeType?, node: NodeType): NodeType { if (initNode == null) { @@ -24,24 +23,12 @@ abstract class ABSTree, NodeType : Node> : Tree node) { - simpleContains(initNode.left, node) - } else { - initNode - } + return rebalance(initNode) } fun simpleDelete(initNode: NodeType?, node: NodeType): NodeType? { @@ -50,8 +37,10 @@ abstract class ABSTree, NodeType : Node> : Tree node) { initNode.left = simpleDelete(initNode.left, node) + initNode.left?.parent = initNode } else { if ((initNode.left == null) or (initNode.right == null)) { return initNode.left ?: initNode.right @@ -60,10 +49,25 @@ abstract class ABSTree, NodeType : Node> : Tree node) { + simpleContains(initNode.left, node) + } else { + initNode + } } fun getMinimal(node: NodeType): NodeType { @@ -86,7 +90,9 @@ abstract class ABSTree, NodeType : Node> : Tree, NodeType : Node> : Tree>(override var data: T) : Node> { + override var left: BSNode? = null + override var right: BSNode? = null + override var parent: BSNode? = null + + override fun compareTo(other: Node>): Int { + return data.compareTo(other.data) + } + + override fun equals(other: Any?): Boolean { + if (other is BSNode<*>) { + return data.equals(other.data) + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/BSTree.kt b/app/src/main/kotlin/trees/BSTree.kt index 7f80c6f..4a68cec 100644 --- a/app/src/main/kotlin/trees/BSTree.kt +++ b/app/src/main/kotlin/trees/BSTree.kt @@ -1,33 +1,30 @@ package trees -import BSNode -import KeyValue - /* * Copyright 2023 teemEight * SPDX-License-Identifier: Apache-2.0 */ -class BSTree, V, T : KeyValue> : ABSTree, BSNode>() { +class BSTree> : ABSTree>() { - override fun add(data: KeyValue) { + override fun add(data:T) { root = simpleAdd(root, BSNode(data)) } - override fun contain(data: KeyValue): Boolean { + override fun contain(data: T): Boolean { return (simpleContains(root, BSNode(data)) != null) } - fun get(key: K): V? { - return simpleContains(root, BSNode(KeyValue(key, null)))?.getValue() + fun get(data: T): T? { + return simpleContains(root, BSNode(data))?.data } - override fun delete(data: KeyValue) { + override fun delete(data: T) { root = simpleDelete(root, BSNode(data)) } - fun min(): KeyValue? { + fun min(): T? { return root?.let { getMinimal(it).data } } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/KeyValue.kt b/app/src/main/kotlin/trees/KeyValue.kt new file mode 100644 index 0000000..0c5ed1f --- /dev/null +++ b/app/src/main/kotlin/trees/KeyValue.kt @@ -0,0 +1,26 @@ +package trees + +class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { + fun getKey(): K { + return key + } + + fun getValue(): V? { + return value + } + + override fun compareTo(other: KeyValue): Int { + return key.compareTo(other.key) + } + + override fun equals(other: Any?): Boolean { + if (other is KeyValue<*, *>) { + return key.equals(other.getKey()) + } + return false + } + + override fun toString(): String { + return "$key: $value" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/Node.kt b/app/src/main/kotlin/trees/Node.kt index d1391e1..3ac5635 100644 --- a/app/src/main/kotlin/trees/Node.kt +++ b/app/src/main/kotlin/trees/Node.kt @@ -9,53 +9,3 @@ interface Node, Subtype : Node> : Comparable, V>(override var data: KeyValue) : Node, BSNode> { - override var left: BSNode? = null - override var right: BSNode? = null - override var parent: BSNode? = null - - override fun compareTo(other: Node, BSNode>): Int { - return data.getKey().compareTo(other.data.getKey()) - } - - override fun equals(other: Any?): Boolean { - if (other is BSNode<*, *>) { - return data.equals(other.data) - } - return false - } - - fun getKey(): K { - return data.getKey() - } - - fun getValue(): V? { - return data.getValue() - } -} - -class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { - fun getKey(): K { - return key - } - - fun getValue(): V? { - return value - } - - override fun compareTo(other: KeyValue): Int { - return key.compareTo(other.key) - } - - override fun equals(other: Any?): Boolean { - if (other is KeyValue<*, *>) { - return key.equals(other.getKey()) - } - return false - } - - override fun toString(): String { - return "$key: $value" - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/Tree.kt b/app/src/main/kotlin/trees/Tree.kt new file mode 100644 index 0000000..b1aa7d8 --- /dev/null +++ b/app/src/main/kotlin/trees/Tree.kt @@ -0,0 +1,7 @@ +package trees + +interface Tree> { + fun add(data: T) + fun contain(data: T): Boolean + fun delete(data: T) +} \ No newline at end of file From 32aaf55451e2200a52a3fd58a4a8a31bd60bab3a Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sun, 2 Apr 2023 19:39:26 +0300 Subject: [PATCH 35/90] struct: add modules for trees and nodes refactor: update Copyright header --- app/src/main/kotlin/trees/ABSTree.kt | 10 +++++----- app/src/main/kotlin/trees/KeyValue.kt | 5 +++++ app/src/main/kotlin/trees/{ => interfaces}/Node.kt | 4 +++- app/src/main/kotlin/trees/{ => interfaces}/Tree.kt | 7 ++++++- app/src/main/kotlin/trees/nodes/AVLNode.kt | 9 +++++++++ app/src/main/kotlin/trees/{ => nodes}/BSNode.kt | 11 ++++++++--- app/src/main/kotlin/trees/trees/AVLTree.kt | 9 +++++++++ app/src/main/kotlin/trees/{ => trees}/BSTree.kt | 11 +++++++---- 8 files changed, 52 insertions(+), 14 deletions(-) rename app/src/main/kotlin/trees/{ => interfaces}/Node.kt (80%) rename app/src/main/kotlin/trees/{ => interfaces}/Tree.kt (52%) create mode 100644 app/src/main/kotlin/trees/nodes/AVLNode.kt rename app/src/main/kotlin/trees/{ => nodes}/BSNode.kt (66%) create mode 100644 app/src/main/kotlin/trees/trees/AVLTree.kt rename app/src/main/kotlin/trees/{ => trees}/BSTree.kt (80%) diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 33846db..70c43b8 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -1,12 +1,12 @@ -package trees - -import Node - /* - * Copyright 2023 teemEight + * Copyright (c) 2023 teemEight * SPDX-License-Identifier: Apache-2.0 */ +package trees + +import trees.interfaces.Node +import trees.interfaces.Tree abstract class ABSTree, NodeType : Node> : Tree { protected var root: NodeType? = null diff --git a/app/src/main/kotlin/trees/KeyValue.kt b/app/src/main/kotlin/trees/KeyValue.kt index 0c5ed1f..deca452 100644 --- a/app/src/main/kotlin/trees/KeyValue.kt +++ b/app/src/main/kotlin/trees/KeyValue.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + package trees class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { diff --git a/app/src/main/kotlin/trees/Node.kt b/app/src/main/kotlin/trees/interfaces/Node.kt similarity index 80% rename from app/src/main/kotlin/trees/Node.kt rename to app/src/main/kotlin/trees/interfaces/Node.kt index 3ac5635..44c7a61 100644 --- a/app/src/main/kotlin/trees/Node.kt +++ b/app/src/main/kotlin/trees/interfaces/Node.kt @@ -1,8 +1,10 @@ /* - * Copyright 2023 teemEight + * Copyright (c) 2023 teemEight * SPDX-License-Identifier: Apache-2.0 */ +package trees.interfaces + interface Node, Subtype : Node> : Comparable> { var data: T var left: Subtype? diff --git a/app/src/main/kotlin/trees/Tree.kt b/app/src/main/kotlin/trees/interfaces/Tree.kt similarity index 52% rename from app/src/main/kotlin/trees/Tree.kt rename to app/src/main/kotlin/trees/interfaces/Tree.kt index b1aa7d8..e2a8738 100644 --- a/app/src/main/kotlin/trees/Tree.kt +++ b/app/src/main/kotlin/trees/interfaces/Tree.kt @@ -1,4 +1,9 @@ -package trees +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.interfaces interface Tree> { fun add(data: T) diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/trees/nodes/AVLNode.kt new file mode 100644 index 0000000..680847a --- /dev/null +++ b/app/src/main/kotlin/trees/nodes/AVLNode.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.nodes + +class AVLNode { +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/BSNode.kt b/app/src/main/kotlin/trees/nodes/BSNode.kt similarity index 66% rename from app/src/main/kotlin/trees/BSNode.kt rename to app/src/main/kotlin/trees/nodes/BSNode.kt index 650a0eb..1721f75 100644 --- a/app/src/main/kotlin/trees/BSNode.kt +++ b/app/src/main/kotlin/trees/nodes/BSNode.kt @@ -1,8 +1,13 @@ -package trees +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ -import Node +package trees.nodes -class BSNode>(override var data: T) : Node> { +import trees.interfaces.Node + +class BSNode>(override var data: T) : Node> { override var left: BSNode? = null override var right: BSNode? = null override var parent: BSNode? = null diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/trees/trees/AVLTree.kt new file mode 100644 index 0000000..2020c7f --- /dev/null +++ b/app/src/main/kotlin/trees/trees/AVLTree.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.trees + +class AVLTree { +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/BSTree.kt b/app/src/main/kotlin/trees/trees/BSTree.kt similarity index 80% rename from app/src/main/kotlin/trees/BSTree.kt rename to app/src/main/kotlin/trees/trees/BSTree.kt index 4a68cec..bb1850d 100644 --- a/app/src/main/kotlin/trees/BSTree.kt +++ b/app/src/main/kotlin/trees/trees/BSTree.kt @@ -1,14 +1,17 @@ -package trees - /* - * Copyright 2023 teemEight + * Copyright (c) 2023 teemEight * SPDX-License-Identifier: Apache-2.0 */ +package trees.trees + +import trees.ABSTree +import trees.nodes.BSNode + class BSTree> : ABSTree>() { - override fun add(data:T) { + override fun add(data: T) { root = simpleAdd(root, BSNode(data)) } From 5ebeade04c2094133bdf70c4bc9c24df86336ee8 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Tue, 4 Apr 2023 22:33:58 +0300 Subject: [PATCH 36/90] refactor: sort funs in BSTree, rename fun "rebalance" to "balance" --- app/src/main/kotlin/trees/ABSTree.kt | 10 +++++----- app/src/main/kotlin/trees/trees/BSTree.kt | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 70c43b8..832ab70 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -11,14 +11,14 @@ import trees.interfaces.Tree abstract class ABSTree, NodeType : Node> : Tree { protected var root: NodeType? = null - fun rebalance(initNode: NodeType): NodeType { + open fun balance(initNode: NodeType?): NodeType? { return initNode } - fun simpleAdd(initNode: NodeType?, node: NodeType): NodeType { + fun simpleAdd(initNode: NodeType?, node: NodeType): NodeType? { if (initNode == null) { - return node + return balance(node) } if (initNode < node) { @@ -28,7 +28,7 @@ abstract class ABSTree, NodeType : Node> : Tree, NodeType : Node> : Tree> : ABSTree>() { return (simpleContains(root, BSNode(data)) != null) } - fun get(data: T): T? { - return simpleContains(root, BSNode(data))?.data - } - override fun delete(data: T) { root = simpleDelete(root, BSNode(data)) } + fun get(data: T): T? { + return simpleContains(root, BSNode(data))?.data + } + fun min(): T? { return root?.let { getMinimal(it).data } } From 373a15f08cba11e8f1ebc64f708decc44e6dca58 Mon Sep 17 00:00:00 2001 From: Lesh79 Date: Tue, 28 Mar 2023 20:08:03 +0300 Subject: [PATCH 37/90] feat(AVLNode): add AVLNode with methods "compareTo", "equals", "getKey", "getValue", --- src/main/AVL/AVLNode.kt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/AVL/AVLNode.kt diff --git a/src/main/AVL/AVLNode.kt b/src/main/AVL/AVLNode.kt new file mode 100644 index 0000000..3528751 --- /dev/null +++ b/src/main/AVL/AVLNode.kt @@ -0,0 +1,33 @@ +class AVLNodee, V>(override var data: KeyValue) : Node, AVLNodee> { + var height: Int? + override var left: AVLNode? = null + override var right: AVLNode? = null + override var parent: AVLNode? = null + + fun updateHeight() + fun rotateLeft(): AVLNode + fun rotateRight(): AVLNode + fun balanceFactor(): Int + fun rebalance() + fun add(data: KeyValue) + fun delete(data: KeyValue) + + fun getKey(): K { + return data.getKey() + } + fun getValue(): V { + return data.getValue() + } + + + override fun compareTo(other: Node, AVLNode>): Int { + return data.compareTo(other.data.getKey()) + } + + override fun equals(other: Any?): Boolean { + if (other is BSNode<*, *>) { + return data.equals(other.data) + } + return false + } +} \ No newline at end of file From 97fc2eeced07b76a1efd7de3b0f36d11e0e3f17c Mon Sep 17 00:00:00 2001 From: Lesh79 Date: Tue, 28 Mar 2023 20:09:47 +0300 Subject: [PATCH 38/90] feat(AVLTree): add AVLTree implementing methods "balaceFactor", "updateHeight", "rotateLeft", "rotateRight", "rebalance" --- src/main/AVL/AVLTree.kt | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/main/AVL/AVLTree.kt diff --git a/src/main/AVL/AVLTree.kt b/src/main/AVL/AVLTree.kt new file mode 100644 index 0000000..8fe68cf --- /dev/null +++ b/src/main/AVL/AVLTree.kt @@ -0,0 +1,66 @@ +class AVLTree, V> : ABSTree, AVLNode>() { + + var root: AVLNode? = null + + + + override fun contain(data: KeyValue): Boolean { + return (simpleContains(BSNode(data)) != null) + } + + override fun add(data: KeyValue) { + simpleAdd(BSNode(data)) + root?.rebalance() + } + + override fun delete(data: KeyValue) { + val curNode = simpleContains(BSNode(data)) ?: return + simpleDelete(curNode) + root?.rebalance() + } + + fun get(key: K): V? { + return simpleContains(BSNode(KeyValue(key, null)))?.getValue() + } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + override fun balanceFactor(): Int { + return (right?.height ?: 0) - (left?.height ?: 0) + } + + override fun updateHeight() { + val leftHeight = left?.height ?: 0 + val rightHeight = right?.height ?: 0 + height = 1 + maxOf(leftHeight, rightHeight) + } + + override fun rotateLeft(): AVLNode { + val newRoot = right + right = newRoot?.left + newRoot?.left = this + return newRoot ?: this + } + + override fun rotateRight(): AVLNode { + val newRoot = left + left = newRoot?.right + newRoot?.right = this + return newRoot ?: this + } + + override fun rebalance() { + val balanceFactor = balanceFactor() + + if (balanceFactor > 1) { + if (right?.balanceFactor() ?: 0 < 0) { + right = right?.rotateRight() + } + root = rotateLeft() + } else if (balanceFactor < -1) { + if (left?.balanceFactor() ?: 0 > 0) { + left = left?.rotateLeft() + } + root = rotateRight() + } + } + +} From 669e0df1d029b8e27de5dd38eff9296919e2a7da Mon Sep 17 00:00:00 2001 From: Lesh79 Date: Tue, 4 Apr 2023 22:43:50 +0300 Subject: [PATCH 39/90] feat(AVLTree): add AVLTree implementing methods "balaceFactor", "updateHeight", "rotateLeft", "rotateRight", "rebalance" --- app/src/main/kotlin/trees/ABSTree.kt | 1 + app/src/main/kotlin/trees/nodes/AVLNode.kt | 33 ++++++++++- app/src/main/kotlin/trees/trees/AVLTree.kt | 60 +++++++++++++++++++- src/main/AVL/AVLNode.kt | 33 ----------- src/main/AVL/AVLTree.kt | 66 ---------------------- 5 files changed, 92 insertions(+), 101 deletions(-) delete mode 100644 src/main/AVL/AVLNode.kt delete mode 100644 src/main/AVL/AVLTree.kt diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 832ab70..04ad8cf 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -105,4 +105,5 @@ abstract class ABSTree, NodeType : Node> : Tree>(override var data: T) : Node> { + var height: Int? = null + override var left: AVLNode? = null + override var right: AVLNode? = null + override var parent: AVLNode? = null + + + fun updateHeight() { + val leftNode = left + val rightNode = right + val leftHeight = if (leftNode != null) leftNode.height ?: 0 else 0 + val rightHeight = if (rightNode != null) rightNode.height ?: 0 else 0 + height = 1 + maxOf(leftHeight, rightHeight) + } + + fun balanceFactor(): Int { + val leftNode = left + val rightNode = right + return (rightNode?.height ?: 0) - (leftNode?.height ?: 0) + } + + override fun compareTo(other: Node>): Int { + return data.compareTo(other.data) + } + + override fun equals(other: Any?): Boolean { + if (other is AVLNode<*>) { + return data.equals(other.data) + } + return false + } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/trees/trees/AVLTree.kt index 2020c7f..10c8a6c 100644 --- a/app/src/main/kotlin/trees/trees/AVLTree.kt +++ b/app/src/main/kotlin/trees/trees/AVLTree.kt @@ -5,5 +5,63 @@ package trees.trees -class AVLTree { +import trees.ABSTree +import trees.nodes.AVLNode + +class AVLTree> : ABSTree>() { + + override fun add(data: T) { + root = simpleAdd(root, AVLNode(data)) + root?.updateHeight() + } + + override fun contain(data: T): Boolean { + return (simpleContains(root, AVLNode(data)) != null) + } + + override fun delete(data: T) { + root = simpleDelete(root, AVLNode(data)) // simpleDelete(curNode, root) + } + + override fun balance(initNode: AVLNode?): AVLNode? { + if (initNode == null) { + return null + } + initNode.updateHeight() + val balanceFactor = initNode.balanceFactor() + + if (balanceFactor > 1) { + if ((initNode.right?.balanceFactor() ?: 0) < 0) { + initNode.right = initNode.right?.let { rotateRight(it) } // right = right?.rotateRight() + initNode.right?.updateHeight() + } + val node = rotateLeft(initNode) + updateChildrenHeight(node) + node?.updateHeight() + return node + } else if (balanceFactor < -1) { + if ((initNode.left?.balanceFactor() ?: 0) > 0) { + initNode.left = initNode.left?.let { rotateLeft(it) } + initNode.left?.updateHeight() + } + val node = rotateRight(initNode) + updateChildrenHeight(node) + node?.updateHeight() + return node + } + return initNode + } + + fun get(data: T): T? { + return simpleContains(root, AVLNode(data))?.data + } + + fun min(): T? { + return root?.let { getMinimal(it).data } + } + + fun updateChildrenHeight(node: AVLNode?) { + node?.left?.updateHeight() + node?.right?.updateHeight() + } } \ No newline at end of file diff --git a/src/main/AVL/AVLNode.kt b/src/main/AVL/AVLNode.kt deleted file mode 100644 index 3528751..0000000 --- a/src/main/AVL/AVLNode.kt +++ /dev/null @@ -1,33 +0,0 @@ -class AVLNodee, V>(override var data: KeyValue) : Node, AVLNodee> { - var height: Int? - override var left: AVLNode? = null - override var right: AVLNode? = null - override var parent: AVLNode? = null - - fun updateHeight() - fun rotateLeft(): AVLNode - fun rotateRight(): AVLNode - fun balanceFactor(): Int - fun rebalance() - fun add(data: KeyValue) - fun delete(data: KeyValue) - - fun getKey(): K { - return data.getKey() - } - fun getValue(): V { - return data.getValue() - } - - - override fun compareTo(other: Node, AVLNode>): Int { - return data.compareTo(other.data.getKey()) - } - - override fun equals(other: Any?): Boolean { - if (other is BSNode<*, *>) { - return data.equals(other.data) - } - return false - } -} \ No newline at end of file diff --git a/src/main/AVL/AVLTree.kt b/src/main/AVL/AVLTree.kt deleted file mode 100644 index 8fe68cf..0000000 --- a/src/main/AVL/AVLTree.kt +++ /dev/null @@ -1,66 +0,0 @@ -class AVLTree, V> : ABSTree, AVLNode>() { - - var root: AVLNode? = null - - - - override fun contain(data: KeyValue): Boolean { - return (simpleContains(BSNode(data)) != null) - } - - override fun add(data: KeyValue) { - simpleAdd(BSNode(data)) - root?.rebalance() - } - - override fun delete(data: KeyValue) { - val curNode = simpleContains(BSNode(data)) ?: return - simpleDelete(curNode) - root?.rebalance() - } - - fun get(key: K): V? { - return simpleContains(BSNode(KeyValue(key, null)))?.getValue() - } -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - override fun balanceFactor(): Int { - return (right?.height ?: 0) - (left?.height ?: 0) - } - - override fun updateHeight() { - val leftHeight = left?.height ?: 0 - val rightHeight = right?.height ?: 0 - height = 1 + maxOf(leftHeight, rightHeight) - } - - override fun rotateLeft(): AVLNode { - val newRoot = right - right = newRoot?.left - newRoot?.left = this - return newRoot ?: this - } - - override fun rotateRight(): AVLNode { - val newRoot = left - left = newRoot?.right - newRoot?.right = this - return newRoot ?: this - } - - override fun rebalance() { - val balanceFactor = balanceFactor() - - if (balanceFactor > 1) { - if (right?.balanceFactor() ?: 0 < 0) { - right = right?.rotateRight() - } - root = rotateLeft() - } else if (balanceFactor < -1) { - if (left?.balanceFactor() ?: 0 > 0) { - left = left?.rotateLeft() - } - root = rotateRight() - } - } - -} From ce39186e09b7f2e10d5a1cb80a3bf6ce0bd5931f Mon Sep 17 00:00:00 2001 From: wokuparalyzed Date: Wed, 5 Apr 2023 20:48:24 +0300 Subject: [PATCH 40/90] feat: add early RBTree and RBNode fix: add parameter 'type' in balance function for separation of balance usage in RBTree while inserting or deleting nodes. --- app/src/main/kotlin/trees/ABSTree.kt | 6 +- app/src/main/kotlin/trees/nodes/RBNode.kt | 31 +++++ app/src/main/kotlin/trees/trees/AVLTree.kt | 2 +- app/src/main/kotlin/trees/trees/RBTree.kt | 126 +++++++++++++++++++++ 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 app/src/main/kotlin/trees/nodes/RBNode.kt create mode 100644 app/src/main/kotlin/trees/trees/RBTree.kt diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt index 04ad8cf..8dfd78e 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -11,7 +11,7 @@ import trees.interfaces.Tree abstract class ABSTree, NodeType : Node> : Tree { protected var root: NodeType? = null - open fun balance(initNode: NodeType?): NodeType? { + open fun balance(initNode: NodeType?, type: Boolean=true): NodeType? { return initNode } @@ -28,7 +28,7 @@ abstract class ABSTree, NodeType : Node> : Tree, NodeType : Node> : Tree>(override var data: T) : Node> { + var color: Color = Color.RED + override var left: RBNode? = null + override var right: RBNode? = null + override var parent: RBNode? = null + + override fun compareTo(other: Node>): Int { + return data.compareTo(other.data) + } + + override fun equals(other: Any?): Boolean { + if (other is RBNode<*>) { + return data.equals(other.data) + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/trees/trees/AVLTree.kt index 10c8a6c..7039f83 100644 --- a/app/src/main/kotlin/trees/trees/AVLTree.kt +++ b/app/src/main/kotlin/trees/trees/AVLTree.kt @@ -23,7 +23,7 @@ class AVLTree> : ABSTree>() { root = simpleDelete(root, AVLNode(data)) // simpleDelete(curNode, root) } - override fun balance(initNode: AVLNode?): AVLNode? { + override fun balance(initNode: AVLNode?, type: Boolean): AVLNode? { if (initNode == null) { return null } diff --git a/app/src/main/kotlin/trees/trees/RBTree.kt b/app/src/main/kotlin/trees/trees/RBTree.kt new file mode 100644 index 0000000..025871c --- /dev/null +++ b/app/src/main/kotlin/trees/trees/RBTree.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.trees + +import trees.ABSTree +import trees.nodes.Color +import trees.nodes.RBNode +import kotlin.coroutines.CoroutineContext + +class RBTree> : ABSTree>() { + override fun add(data: T) { + root = simpleAdd(root, RBNode(data)) + + } + + override fun contain(data: T): Boolean { + return (simpleContains(root, RBNode(data)) != null) + } + + fun get(data: T): T? { + return simpleContains(root, RBNode(data))?.data + } + + override fun delete(data: T) { + root = simpleDelete(root, RBNode(data)) + } + + override fun balance(initNode: RBNode?, type: Boolean): RBNode? { + if (!type) { //for balancing add + var current = initNode + if (current == root) { + current?.color = Color.BLACK + return balance(initNode) + } + while (current != root && current?.parent?.color == Color.RED) { + if (current.parent == current.parent?.parent?.left) { + if (current.parent?.parent?.right?.color == Color.RED) { + current.parent?.color = Color.BLACK + current.parent?.parent?.right?.color = Color.BLACK + current.parent?.parent?.color = Color.RED + current = current.parent?.parent + } else { + if (current == current.parent?.right) { + current = current.parent!! + rotateLeft(current) + current.parent?.color = Color.BLACK + current.parent?.parent?.color = Color.RED + rotateRight(current.parent?.parent!!) + } + } + } else { + if (current.parent?.parent?.left?.color == Color.RED) { + current.parent?.color = Color.BLACK + current.parent?.parent?.left?.color = Color.BLACK + current.parent?.parent?.color = Color.RED + current = current.parent?.parent + } else { + if (current == current.parent?.left) { + current = current.parent!! + rotateRight(current) + current.parent?.color = Color.BLACK + current.parent?.parent?.color = Color.RED + rotateLeft(current.parent!!.parent!!) + } + } + } + } + root?.color = Color.BLACK + } + if (type) { //for balancing delete + var current = initNode + while ((current != root) && (current?.color == Color.BLACK)) { + if (current == current.parent?.left) { + if (current.parent?.right?.color == Color.RED) { + current.parent?.right?.color == Color.BLACK + current.parent?.color = Color.RED + rotateLeft(current.parent!!) + } + if ((current.parent?.right?.right?.color == Color.BLACK) && (current.parent?.right?.left?.color == Color.BLACK)) { + current.parent?.right?.color = Color.RED + } + else { + if (current.parent?.right?.right?.color == Color.BLACK) { + current.parent?.right?.left?.color = Color.BLACK + current.parent?.right?.color = Color.RED + rotateRight(current.parent?.right!!) + } + current.parent?.right?.color = current.parent!!.color + current.parent?.color = Color.BLACK + current.parent?.right?.right?.color = Color.BLACK + rotateLeft(current.parent!!) + current = root + } + } + else { + if (current.parent?.left?.color == Color.RED) { + current.parent?.left?.color == Color.BLACK + current.parent?.color = Color.RED + rotateRight(current.parent!!) + } + if ((current.parent?.left?.right?.color == Color.BLACK) && (current.parent?.left?.left?.color == Color.BLACK)) { + current.parent?.left?.color = Color.RED + } + else { + if (current.parent?.left?.left?.color == Color.BLACK) { + current.parent?.left?.right?.color = Color.BLACK + current.parent?.left?.color = Color.RED + rotateLeft(current.parent?.left!!) + } + current.parent?.left=current.parent + current.parent?.color=Color.BLACK + current.parent?.left?.left?.color=Color.BLACK + rotateRight(current.parent!!) + current = root + } + } + } + current?.color=Color.BLACK + root?.color=Color.BLACK + } + return balance(initNode) + } +} \ No newline at end of file From 77e56b0cce12b06e7df8deaf90c51d9355ca5903 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sun, 9 Apr 2023 17:45:27 +0300 Subject: [PATCH 41/90] Realized all types of trees (#10) feat: add all types of trees --------- Co-authored-by: Dmitry Shestakov <89578470+wokuparalyzed@users.noreply.github.com> Co-authored-by: Lesh79 Co-authored-by: wokuparalyzed --- .gitattributes | 9 + .github/mergeable.yml | 30 +++ .github/workflows/github-actions.yml | 25 ++ .gitignore | 51 ++++ CONTRIBUTING.md | 40 +++ LICENSE.txt | 216 ++++++++++++++++ README.md | 52 ++++ app/build.gradle.kts | 41 ++++ app/src/main/kotlin/trees/ABSTree.kt | 124 ++++++++++ app/src/main/kotlin/trees/KeyValue.kt | 31 +++ app/src/main/kotlin/trees/Main.kt | 18 ++ app/src/main/kotlin/trees/interfaces/Node.kt | 13 + app/src/main/kotlin/trees/interfaces/Tree.kt | 12 + app/src/main/kotlin/trees/nodes/AVLNode.kt | 45 ++++ app/src/main/kotlin/trees/nodes/BSNode.kt | 29 +++ app/src/main/kotlin/trees/nodes/MyNode.kt | 26 ++ app/src/main/kotlin/trees/nodes/RBNode.kt | 35 +++ app/src/main/kotlin/trees/trees/AVLTree.kt | 66 +++++ app/src/main/kotlin/trees/trees/BSTree.kt | 30 +++ app/src/main/kotlin/trees/trees/RBTree.kt | 220 +++++++++++++++++ app/src/test/kotlin/trees/AppTest.kt | 13 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 +++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle.kts | 11 + 26 files changed, 1479 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/mergeable.yml create mode 100644 .github/workflows/github-actions.yml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/kotlin/trees/ABSTree.kt create mode 100644 app/src/main/kotlin/trees/KeyValue.kt create mode 100644 app/src/main/kotlin/trees/Main.kt create mode 100644 app/src/main/kotlin/trees/interfaces/Node.kt create mode 100644 app/src/main/kotlin/trees/interfaces/Tree.kt create mode 100644 app/src/main/kotlin/trees/nodes/AVLNode.kt create mode 100644 app/src/main/kotlin/trees/nodes/BSNode.kt create mode 100644 app/src/main/kotlin/trees/nodes/MyNode.kt create mode 100644 app/src/main/kotlin/trees/nodes/RBNode.kt create mode 100644 app/src/main/kotlin/trees/trees/AVLTree.kt create mode 100644 app/src/main/kotlin/trees/trees/BSTree.kt create mode 100644 app/src/main/kotlin/trees/trees/RBTree.kt create mode 100644 app/src/test/kotlin/trees/AppTest.kt create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 0000000..47086cb --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,30 @@ +version: 2 +mergeable: + - when: pull_request.*, pull_request_review.* + filter: + # ignore 'Feedback' PR + - do: payload + pull_request: + title: + must_exclude: + regex: ^Feedback$ + regex_flag: none + validate: + - do: description + no_empty: + enabled: true + message: "Description matter and should not be empty. Provide detail with **what** was changed, **why** it was changed, and **how** it was changed." + - do: approvals + min: + count: 1 + required: + assignees: true + + - when: pull_request.opened + name: "Remind about contributing guidelines" + validate: [ ] + pass: + - do: comment + payload: + body: > + Thanks for creating a pull request! Please, check that your pull request meets the [CONTRIBUTING](./CONTRIBUTING.md) requirements. \ No newline at end of file diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..9aa8fbc --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,25 @@ +name: Build CI + +on: + pull_request: + branches: + - main + - release + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Build with Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: build \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1fbeba --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +/.idea/.name +/.idea/gradle.xml +/.idea/kotlinc.xml +/.idea/misc.xml +/.idea/vcs.xml +/.idea/ + +# Ignore Gradle build output directory +build +/app/build \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf4ddac --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Внесение правок + +## Основные советы + +1. Не используйте merge, только rebase (для сохранения линейной истории коммитов) +2. Не менять чужие ветки без крайней необходимости +3. Перепроверьте историю коммитов перед созданием пулл реквеста +4. **Перепроверьте, что вы в правильной ветке**, никогда не коммитьте напрямую в main + +## Правила добавления коммитов + +Коммиты добавляются в соответствии с conventional commits. Т.е +`(): `. + +Поле `` должно принимать одно из этих значений: + +* `feat` для добавления новой функциональности +* `fix` для исправления бага в программе +* `refactor` для рефакторинга кода, например, переименования переменной +* `test` для добавления тестов, их рефакторинга +* `struct` для изменений связанных с изменением структуры проекта (НО НЕ КОДА), например изменение + расположения папок +* `ci` для различных задач ci/cd + +Поле `` содержит суть изменений в повелительном наклонении настоящего времени на английском языке без точки в +конце, первое слово - глагол с маленькой буквы. +Примеры: + +* Хорошо: "feat: Add module for future BST implementations" +* Плохо: "Added module for future BST implementations." + +## Правила для пулл реквестов + +**НЕ ТЫКАТЬ НА ЗЕЛЕНУЮ КНОПОЧКУ `REBASE AND MERGE` БЕЗ РЕВЬЮ** + +**Запрещено** сливать свой пулл реквест в ветку самостоятельно. + +Если тыкаете на зеленую кнопочку, то **убедитесь**, что на ней написано `REBASE AND MERGE` + +Ревью происходит в виде комметариев к пулл реквестам, обсуждения в чате команды и личном общении. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f3a0e72 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,216 @@ +Apache License 2.0 +A permissive license whose main conditions require preservation of copyright and license notices. Contributors provide an express grant of patent rights. Licensed works, modifications, and larger works may be distributed under different terms and without source code. + +Permissions Conditions +Limitations +Commercial use +Distribution +Modification +Patent use +Private use +License and copyright notice +State changes +Liability +Trademark use +Warranty + 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..164878e --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# ABOBA + +teemEight's project of implementation of three trees: AVL, red-black, binary + +[![Apache License](https://img.shields.io/badge/license-Apache%202.0-black.svg)](https://www.apache.org/licenses/LICENSE-2.0) + +## Authors (teemEight) + +- [absolute](https://steamcommunity.com/groups/absoluteplayer) [@roketflame](https://github.com/RoketFlame) +- [отдел продаж](https://steamcommunity.com/groups/Otedel_Prodaj) [@wokuparalyzed](https://www.github.com/wokuparalyzed) +- [and their fans](https://steamcommunity.com/groups/kazakhstansgaminggirls) [@Lesh79](https://www.github.com/Lesh79) + +## Features + +- Add +- Delete +- Contains +- Cross platform +- Deploy +- Bugs + +## Building + +To build this project run + +```bash + gradle build +``` + +## Feedback + +If you have any feedback, please reach out to us at Issues + +## About Me + +I'm a full stack developer... \ +Help please...(( \ +18-20 y.o SPBU SE + +## 🛠 Skills + +2006 ELO Faciet, 1300 MMR, 1400 ELO CHESS, +21k TROPHIES BrAWL STARs, 5BC \ +[](https://i.imgur.com/TFDL3rB.jpeg) + +## 🔗 Links (Источники вдохновления) + +[![gradle](https://img.shields.io/badge/gradle-FFFFFF?style=for-the-badge&logo=gradle&logoColor=black&)](https://gradle.org/) \ +[](https://youtu.be/6Cv2kmgX0So?t=30) \ +[](https://kotlinlang.org/) \ +[](https://kotlinlang.org/) + diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..eeb2b36 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,41 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Kotlin application project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + id("org.jetbrains.kotlin.jvm") version "1.8.10" + + // Apply the application plugin to add support for building a CLI application in Java. + application +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // Use the Kotlin JUnit 5 integration. + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + + // Use the JUnit 5 integration. + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.1") + + // This dependency is used by the application. + implementation("com.google.guava:guava:31.1-jre") +} + +application { + // Define the main class for the application. + mainClass.set("MainKt") +} + +//tasks.named("test") { +// // Use JUnit Platform for unit tests. +// useJUnitPlatform() +//} diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/trees/ABSTree.kt new file mode 100644 index 0000000..c8b0a3f --- /dev/null +++ b/app/src/main/kotlin/trees/ABSTree.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees + +import trees.interfaces.Tree +import trees.nodes.MyNode + +abstract class ABSTree, NodeType : MyNode> : Tree { + var root: NodeType? = null + internal set + + protected open fun balance(initNode: NodeType?): NodeType? { + return initNode + } + + protected fun simpleAdd(initNode: NodeType?, node: NodeType): NodeType? { + + if (initNode == null) { + return node + } + + if (initNode < node) { + initNode.right = simpleAdd(initNode.right, node) + initNode.right?.parent = initNode + } else if (initNode > node) { + initNode.left = simpleAdd(initNode.left, node) + initNode.left?.parent = initNode + } + return balance(initNode) + } + + protected fun simpleDelete(initNode: NodeType?, node: NodeType): NodeType? { + if (initNode == null) { + return null + } + if (initNode < node) { + initNode.right = simpleDelete(initNode.right, node) + initNode.right?.parent = initNode + } else if (initNode > node) { + initNode.left = simpleDelete(initNode.left, node) + initNode.left?.parent = initNode + } else { + if ((initNode.left == null) || (initNode.right == null)) { + return initNode.left ?: initNode.right + } else { + initNode.right?.let { + val tmp = getMinimal(it) + initNode.data = tmp.data + initNode.right = simpleDelete(initNode.right, tmp) + initNode.right?.parent = initNode + } + } + } + return balance(initNode) + } + + protected fun simpleContains(initNode: NodeType?, node: NodeType): NodeType? { + if (initNode == null) { + return null + } + + return if (initNode < node) { + simpleContains(initNode.right, node) + } else if (initNode > node) { + simpleContains(initNode.left, node) + } else { + initNode + } + } + + protected fun getMinimal(node: NodeType): NodeType { + var minNode = node + while (true) { + minNode = minNode.left ?: break + } + return minNode + } + + protected fun getMaximal(node: NodeType): NodeType { + var maxNode = node + while (true) { + maxNode = maxNode.left ?: break + } + return maxNode + } + + protected fun rotateLeft(node: NodeType): NodeType? { + val rightChild = node.right + val secondSubtree = rightChild?.left + rightChild?.left = node + rightChild?.left?.parent = rightChild + node.right = secondSubtree + node.right?.parent = node + return rightChild + } + + protected fun rotateRight(node: NodeType): NodeType? { + val leftChild = node.left + val secondSubtree = leftChild?.right + leftChild?.right = node + leftChild?.right?.parent = leftChild + node.left = secondSubtree + node.left?.parent = node + return leftChild + } + + protected fun replaceChild(child: NodeType, newChild: NodeType?): NodeType? { + if (child == root) { + root = newChild + newChild?.parent = null + return newChild + } + if (child.parent?.left == child) { + child.parent?.left = newChild + } else if (child.parent?.right == child) { + child.parent?.right = newChild + } + newChild?.parent = child.parent + return newChild + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/KeyValue.kt b/app/src/main/kotlin/trees/KeyValue.kt new file mode 100644 index 0000000..deca452 --- /dev/null +++ b/app/src/main/kotlin/trees/KeyValue.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees + +class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { + fun getKey(): K { + return key + } + + fun getValue(): V? { + return value + } + + override fun compareTo(other: KeyValue): Int { + return key.compareTo(other.key) + } + + override fun equals(other: Any?): Boolean { + if (other is KeyValue<*, *>) { + return key.equals(other.getKey()) + } + return false + } + + override fun toString(): String { + return "$key: $value" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/Main.kt b/app/src/main/kotlin/trees/Main.kt new file mode 100644 index 0000000..557c489 --- /dev/null +++ b/app/src/main/kotlin/trees/Main.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import trees.KeyValue +import trees.trees.BSTree + +/* + * Copyright 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +fun main(args: Array) { + val tree = BSTree>() + tree.add(KeyValue(10, 100)) + println(tree.get(KeyValue(10, null))) +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/interfaces/Node.kt b/app/src/main/kotlin/trees/interfaces/Node.kt new file mode 100644 index 0000000..bd2bf00 --- /dev/null +++ b/app/src/main/kotlin/trees/interfaces/Node.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.interfaces + +interface Node, Subtype : Node> : Comparable> { + val data: T + val left: Subtype? + val right: Subtype? + val parent: Subtype? +} diff --git a/app/src/main/kotlin/trees/interfaces/Tree.kt b/app/src/main/kotlin/trees/interfaces/Tree.kt new file mode 100644 index 0000000..e2a8738 --- /dev/null +++ b/app/src/main/kotlin/trees/interfaces/Tree.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.interfaces + +interface Tree> { + fun add(data: T) + fun contain(data: T): Boolean + fun delete(data: T) +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/trees/nodes/AVLNode.kt new file mode 100644 index 0000000..152bb01 --- /dev/null +++ b/app/src/main/kotlin/trees/nodes/AVLNode.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.nodes + +import trees.interfaces.Node + +class AVLNode>(override var data: T) : MyNode>() { + private var height: Int = 1 + override var left: AVLNode? = null + override var right: AVLNode? = null + override var parent: AVLNode? = null + + + internal fun updateHeight() { + val leftNode = left + val rightNode = right + val leftHeight = leftNode?.height ?: 0 + val rightHeight = rightNode?.height ?: 0 + height = 1 + maxOf(leftHeight, rightHeight) + } + + internal fun balanceFactor(): Int { + val leftNode = left + val rightNode = right + return (rightNode?.height ?: 0) - (leftNode?.height ?: 0) + } + + override fun compareTo(other: Node>): Int { + return data.compareTo(other.data) + } + + override fun equals(other: Any?): Boolean { + if (other is AVLNode<*>) { + return data.equals(other.data) + } + return false + } + + override fun toString(): String { + return "$data" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/BSNode.kt b/app/src/main/kotlin/trees/nodes/BSNode.kt new file mode 100644 index 0000000..a6bc5f5 --- /dev/null +++ b/app/src/main/kotlin/trees/nodes/BSNode.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.nodes + +import trees.interfaces.Node + +class BSNode>(override var data: T) : MyNode>() { + override var left: BSNode? = null + override var right: BSNode? = null + override var parent: BSNode? = null + + override fun compareTo(other: Node>): Int { + return data.compareTo(other.data) + } + + override fun equals(other: Any?): Boolean { + if (other is BSNode<*>) { + return data.equals(other.data) + } + return false + } + + override fun toString(): String { + return "$data" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/MyNode.kt b/app/src/main/kotlin/trees/nodes/MyNode.kt new file mode 100644 index 0000000..bb851ad --- /dev/null +++ b/app/src/main/kotlin/trees/nodes/MyNode.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.nodes/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import trees.interfaces.Node + +abstract class MyNode, Subtype : MyNode> : Node { + abstract override var data: T + internal set + abstract override var left: Subtype? + internal set + abstract override var right: Subtype? + internal set + abstract override var parent: Subtype? + internal set + + override fun compareTo(other: Node): Int { + return data.compareTo(other.data) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/RBNode.kt b/app/src/main/kotlin/trees/nodes/RBNode.kt new file mode 100644 index 0000000..686073b --- /dev/null +++ b/app/src/main/kotlin/trees/nodes/RBNode.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.nodes + +import trees.interfaces.Node + +enum class Color { + RED, + BLACK +} + +class RBNode>(override var data: T) : MyNode>() { + var color: Color = Color.RED + override var left: RBNode? = null + override var right: RBNode? = null + override var parent: RBNode? = null + + override fun compareTo(other: Node>): Int { + return data.compareTo(other.data) + } + + override fun equals(other: Any?): Boolean { + if (other is RBNode<*>) { + return data.equals(other.data) + } + return false + } + + override fun toString(): String { + return "$color - $data" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/trees/trees/AVLTree.kt new file mode 100644 index 0000000..2e78892 --- /dev/null +++ b/app/src/main/kotlin/trees/trees/AVLTree.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.trees + +import trees.ABSTree +import trees.nodes.AVLNode + +class AVLTree> : ABSTree>() { + + override fun add(data: T) { + root = simpleAdd(root, AVLNode(data)) + root?.updateHeight() + root?.parent = null + } + + override fun contain(data: T): Boolean { + return (simpleContains(root, AVLNode(data)) != null) + } + + override fun delete(data: T) { + root = simpleDelete(root, AVLNode(data)) + root?.updateHeight() + root?.parent = null + } + + override fun balance(initNode: AVLNode?): AVLNode? { + if (initNode == null) { + return null + } + initNode.updateHeight() + val balanceFactor = initNode.balanceFactor() + + if (balanceFactor > 1) { + if ((initNode.right?.balanceFactor() ?: 0) < 0) { + initNode.right = initNode.right?.let { rotateRight(it) } // right = right?.rotateRight() + initNode.right?.updateHeight() + } + val node = rotateLeft(initNode) + updateChildrenHeight(node) + node?.updateHeight() + return node + } else if (balanceFactor < -1) { + if ((initNode.left?.balanceFactor() ?: 0) > 0) { + initNode.left = initNode.left?.let { rotateLeft(it) } + initNode.left?.updateHeight() + } + val node = rotateRight(initNode) + updateChildrenHeight(node) + node?.updateHeight() + return node + } + return initNode + } + + fun get(data: T): T? { + return simpleContains(root, AVLNode(data))?.data + } + + private fun updateChildrenHeight(node: AVLNode?) { + node?.left?.updateHeight() + node?.right?.updateHeight() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/trees/BSTree.kt b/app/src/main/kotlin/trees/trees/BSTree.kt new file mode 100644 index 0000000..6702ccf --- /dev/null +++ b/app/src/main/kotlin/trees/trees/BSTree.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.trees + +import trees.ABSTree +import trees.nodes.BSNode + +class BSTree> : ABSTree>() { + + + override fun add(data: T) { + root = simpleAdd(root, BSNode(data)) + } + + override fun contain(data: T): Boolean { + return (simpleContains(root, BSNode(data)) != null) + } + + override fun delete(data: T) { + root = simpleDelete(root, BSNode(data)) + root?.parent = null + } + + fun get(data: T): T? { + return simpleContains(root, BSNode(data))?.data + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/trees/RBTree.kt b/app/src/main/kotlin/trees/trees/RBTree.kt new file mode 100644 index 0000000..b9465fc --- /dev/null +++ b/app/src/main/kotlin/trees/trees/RBTree.kt @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees.trees + +import trees.ABSTree +import trees.nodes.Color +import trees.nodes.RBNode + +class RBTree> : ABSTree>() { + override fun add(data: T) { + val node = RBNode(data) + root = simpleAdd(root, node) + root = balanceAdd(node, root) + root?.parent = null + root?.color = Color.BLACK + } + + override fun delete(data: T) { + + val node = simpleContains(root, RBNode(data)) ?: return + val next: RBNode + + if ((node.left == null) && (node.right == null)) { + if (node == root) { + root = null + } else { + if (node.color == Color.RED) { + // delete red node without child + replaceChild(node, null) + } else { + // delete black node without children + root = balanceDelete(node) + replaceChild(node, null) + } + } + return + } + + if ((node.left == null) || (node.right == null)) { + // delete for black node with one red child + replaceChild(node, node.right ?: node.left)?.color = Color.BLACK + } else { + // delete node with two children + next = getMinimal(node.right ?: error("Node must have right child")) + node.data = next.data.also { next.data = node.data } + if (next.color == Color.RED) { + // delete red node without child + replaceChild(next, null) + } else { + if (next.right == null) { + root = balanceDelete(next) + replaceChild(next, null) + } else { + // delete for black node with one child + replaceChild(next, next.right)?.color = Color.BLACK // next.right must be red and not null + } + } + } + } + + override fun contain(data: T): Boolean { + return (simpleContains(root, RBNode(data)) != null) + } + + private fun balanceDelete(node: RBNode?): RBNode? { + var newRoot = root + var current = node + + while ((current != newRoot) && (current?.color == Color.BLACK)) { + if (current == current.parent?.left) { + var brother = current.parent?.right + if (isRed(brother)) { + brother?.color = Color.BLACK + current.parent?.color = Color.RED + newRoot = clearRotateLeft(current.parent, newRoot) + brother = current.parent?.right + } + if (isBlack(brother?.left) && (isBlack(brother?.right))) { + brother?.color = Color.RED + current = current.parent + } else { + if (isBlack(brother?.right)) { + brother?.left?.color = Color.BLACK + brother?.color = Color.RED + newRoot = clearRotateRight(brother, newRoot) + brother = current.parent?.right + } + brother?.color = current.parent?.color ?: Color.BLACK + current.parent?.color = Color.BLACK + brother?.right?.color = Color.BLACK + newRoot = clearRotateLeft(current.parent, newRoot) + current = newRoot + } + } else { + var brother = current.parent?.left + if (isRed(brother)) { + brother?.color = Color.BLACK + current.parent?.color = Color.RED + newRoot = clearRotateRight(current.parent, newRoot) + brother = current.parent?.left + } + if ((isBlack(brother?.right)) && (isBlack(brother?.left))) { + brother?.color = Color.RED + current = current.parent + } else { + if (isBlack(brother?.left)) { + brother?.right?.color = Color.BLACK + brother?.color = Color.RED + newRoot = clearRotateLeft(brother, newRoot) + brother = current.parent?.left + } + brother?.color = current.parent?.color ?: Color.BLACK + current.parent?.color = Color.BLACK + brother?.left?.color = Color.BLACK + newRoot = clearRotateRight(current.parent, newRoot) + current = newRoot + } + } + } + current?.color = Color.BLACK + return newRoot + } + + private fun balanceAdd(initNode: RBNode?, subRoot: RBNode?): RBNode? { + if (initNode?.parent == null) return subRoot + var newRoot = subRoot + var current = initNode + while (current?.parent?.color == Color.RED) { + if (current.parent == current.parent?.parent?.left) { + if (current.parent?.parent?.right?.color == Color.RED) { + current.parent?.color = Color.BLACK + current.parent?.parent?.right?.color = Color.BLACK + current.parent?.parent?.color = Color.RED + current = current.parent?.parent + } else { + if (current == current.parent?.right) { + current = current.parent + newRoot = current?.let { clearRotateLeft(it, newRoot) } + } + current?.parent?.color = Color.BLACK + current?.parent?.parent?.color = Color.RED + newRoot = current?.parent?.parent?.let { clearRotateRight(it, newRoot) } + + } + } else { + if (current.parent?.parent?.left?.color == Color.RED) { + current.parent?.color = Color.BLACK + current.parent?.parent?.left?.color = Color.BLACK + current.parent?.parent?.color = Color.RED + current = current.parent?.parent + } else { + if (current == current.parent?.left) { + current = current.parent + newRoot = current?.let { clearRotateRight(it, newRoot) } + } + current?.parent?.color = Color.BLACK + current?.parent?.parent?.color = Color.RED + newRoot = current?.parent?.parent?.let { clearRotateLeft(it, newRoot) } + + } + } + } + newRoot?.color = Color.BLACK + return newRoot ?: root + } + + fun get(data: T): T? { + return simpleContains(root, RBNode(data))?.data + } + + private fun clearRotateLeft(node: RBNode?, initRoot: RBNode?): RBNode? { + if (node?.right == null) return null + + var newRoot = initRoot + val parent = node.parent + val subTree = rotateLeft(node) + + if (parent == null) { + subTree?.parent = null + newRoot = subTree + } else if (parent.left == node) { + parent.left = subTree + subTree?.parent = parent + } else { + parent.right = subTree + subTree?.parent = parent + } + + return newRoot + } + + private fun clearRotateRight(node: RBNode?, root: RBNode?): RBNode? { + if (node?.left == null) return null + + var newRoot = root + val parent = node.parent + val subTree = rotateRight(node) + + if (parent == null) { + newRoot = subTree + } else if (parent.left == node) { + parent.left = subTree + } else { + parent.right = subTree + } + subTree?.parent = parent + return newRoot + } + + private fun isBlack(node: RBNode?): Boolean { + return ((node == null) || (node.color == Color.BLACK)) + } + + private fun isRed(node: RBNode?): Boolean { + return node?.color == Color.RED + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/trees/AppTest.kt b/app/src/test/kotlin/trees/AppTest.kt new file mode 100644 index 0000000..8acbe2c --- /dev/null +++ b/app/src/test/kotlin/trees/AppTest.kt @@ -0,0 +1,13 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package trees + +import kotlin.test.Test + +class AppTest { + @Test fun appHasAGreeting() { +// val classUnderTest = App() +// assertNotNull(classUnderTest.greeting, "app should have a greeting") + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bdc9a83 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..79a61d4 --- /dev/null +++ b/gradlew @@ -0,0 +1,244 @@ +#!/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##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=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=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 + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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..93e3f59 --- /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. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% 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/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e26452f --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html + */ + +rootProject.name = "trees-8" +include("app") From a455232add760625853bc673b6320ebf0cdab5f0 Mon Sep 17 00:00:00 2001 From: wokuparalyzed Date: Wed, 12 Apr 2023 00:07:33 +0300 Subject: [PATCH 42/90] feat: add early BSTreeTest --- app/build.gradle.kts | 5 +++- app/src/test/kotlin/trees/AppTest.kt | 3 ++- app/src/test/kotlin/trees/BSTreeTest.kt | 35 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 app/src/test/kotlin/trees/BSTreeTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eeb2b36..64cb2cc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,10 +24,13 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") // Use the JUnit 5 integration. - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.1") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") // This dependency is used by the application. implementation("com.google.guava:guava:31.1-jre") + testImplementation("org.testng:testng:7.7.0") + testImplementation("org.testng:testng:7.7.0") + testImplementation("org.testng:testng:7.7.0") } application { diff --git a/app/src/test/kotlin/trees/AppTest.kt b/app/src/test/kotlin/trees/AppTest.kt index 8acbe2c..456f24a 100644 --- a/app/src/test/kotlin/trees/AppTest.kt +++ b/app/src/test/kotlin/trees/AppTest.kt @@ -6,7 +6,8 @@ package trees import kotlin.test.Test class AppTest { - @Test fun appHasAGreeting() { + @Test + fun appHasAGreeting() { // val classUnderTest = App() // assertNotNull(classUnderTest.greeting, "app should have a greeting") } diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt new file mode 100644 index 0000000..e9c9797 --- /dev/null +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -0,0 +1,35 @@ +package trees + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import trees.nodes.BSNode +import trees.trees.BSTree + +class BSTreeTest { + + @Test + fun testInvariant() { + val bst = BSTree>() + bst.add(KeyValue(10, 10)) + bst.add(KeyValue(10, 1)) + bst.add(KeyValue(10, 9)) + bst.add(KeyValue(10, 2)) + bst.add(KeyValue(10, 3)) + bst.add(KeyValue(10, 4)) + bst.add(KeyValue(10, 5)) + + assertTrue(checkInvariant(bst.root)) + + } + + private fun checkInvariant(node: BSNode>?): Boolean { + if (node == null) return true + if (node.left != null && node.left!!.data > node.data) { + return false + } + if (node.right != null && node.right!!.data < node.data) { + return false + } + return checkInvariant(node.left) && checkInvariant(node.right) + } +} \ No newline at end of file From 0480a429d224d51aea3dca72a34aaefddf53d826 Mon Sep 17 00:00:00 2001 From: wokuparalyzed Date: Wed, 12 Apr 2023 23:53:07 +0300 Subject: [PATCH 43/90] fix: small fix BSTreeTest. Add list of random integers for test binary search tree --- app/src/test/kotlin/trees/BSTreeTest.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index e9c9797..41af052 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -4,25 +4,23 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import trees.nodes.BSNode import trees.trees.BSTree +import java.util.* class BSTreeTest { @Test fun testInvariant() { - val bst = BSTree>() - bst.add(KeyValue(10, 10)) - bst.add(KeyValue(10, 1)) - bst.add(KeyValue(10, 9)) - bst.add(KeyValue(10, 2)) - bst.add(KeyValue(10, 3)) - bst.add(KeyValue(10, 4)) - bst.add(KeyValue(10, 5)) + val bst = BSTree() + val lst = List(100) { Random(42).nextInt() } + for (num in lst) { + bst.add(num) + } assertTrue(checkInvariant(bst.root)) } - private fun checkInvariant(node: BSNode>?): Boolean { + private fun checkInvariant(node: BSNode?): Boolean { if (node == null) return true if (node.left != null && node.left!!.data > node.data) { return false From 5fb1fda2efca6fb9ba743a15d5dbbad71de35cb2 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Wed, 5 Apr 2023 22:12:18 +0300 Subject: [PATCH 44/90] feat: row commit, change struct of balance and refactor code --- app/src/main/kotlin/trees/trees/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/trees/trees/AVLTree.kt index 2e78892..c2daad4 100644 --- a/app/src/main/kotlin/trees/trees/AVLTree.kt +++ b/app/src/main/kotlin/trees/trees/AVLTree.kt @@ -26,7 +26,7 @@ class AVLTree> : ABSTree>() { root?.parent = null } - override fun balance(initNode: AVLNode?): AVLNode? { + override fun balance(initNode: AVLNode?, type: Boolean): AVLNode? { if (initNode == null) { return null } From 42ba4a59a384b73c01be4711d51c0ddc93a4a09b Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Fri, 7 Apr 2023 21:24:06 +0300 Subject: [PATCH 45/90] fix: fix adding already existing value refactor: delete type in balance --- app/src/main/kotlin/trees/trees/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/trees/trees/AVLTree.kt index c2daad4..2e78892 100644 --- a/app/src/main/kotlin/trees/trees/AVLTree.kt +++ b/app/src/main/kotlin/trees/trees/AVLTree.kt @@ -26,7 +26,7 @@ class AVLTree> : ABSTree>() { root?.parent = null } - override fun balance(initNode: AVLNode?, type: Boolean): AVLNode? { + override fun balance(initNode: AVLNode?): AVLNode? { if (initNode == null) { return null } From 2e29d1c0818a98e259e359391a43359560779f96 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sun, 9 Apr 2023 03:34:11 +0300 Subject: [PATCH 46/90] fix: hu-u-uge refactor, change access modifier most of the methods feat: add abstract class for Nodes, now all fields in any Node read-only outside module --- .../main/kotlin/trees/interfaces/MyNode.kt | 21 +++++++++++++++++++ app/src/main/kotlin/trees/nodes/AVLNode.kt | 1 + app/src/main/kotlin/trees/nodes/BSNode.kt | 1 + app/src/main/kotlin/trees/nodes/RBNode.kt | 1 + 4 files changed, 24 insertions(+) create mode 100644 app/src/main/kotlin/trees/interfaces/MyNode.kt diff --git a/app/src/main/kotlin/trees/interfaces/MyNode.kt b/app/src/main/kotlin/trees/interfaces/MyNode.kt new file mode 100644 index 0000000..e44f600 --- /dev/null +++ b/app/src/main/kotlin/trees/interfaces/MyNode.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import trees.interfaces.Node + +abstract class MyNode, Subtype : MyNode> : Node { + abstract override var data: T + internal set + abstract override var left: Subtype? + internal set + abstract override var right: Subtype? + internal set + abstract override var parent: Subtype? + internal set + + override fun compareTo(other: Node): Int { + return data.compareTo(other.data) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/trees/nodes/AVLNode.kt index 152bb01..1d3d303 100644 --- a/app/src/main/kotlin/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/trees/nodes/AVLNode.kt @@ -5,6 +5,7 @@ package trees.nodes +import MyNode import trees.interfaces.Node class AVLNode>(override var data: T) : MyNode>() { diff --git a/app/src/main/kotlin/trees/nodes/BSNode.kt b/app/src/main/kotlin/trees/nodes/BSNode.kt index a6bc5f5..36ba392 100644 --- a/app/src/main/kotlin/trees/nodes/BSNode.kt +++ b/app/src/main/kotlin/trees/nodes/BSNode.kt @@ -5,6 +5,7 @@ package trees.nodes +import MyNode import trees.interfaces.Node class BSNode>(override var data: T) : MyNode>() { diff --git a/app/src/main/kotlin/trees/nodes/RBNode.kt b/app/src/main/kotlin/trees/nodes/RBNode.kt index 686073b..ef35901 100644 --- a/app/src/main/kotlin/trees/nodes/RBNode.kt +++ b/app/src/main/kotlin/trees/nodes/RBNode.kt @@ -5,6 +5,7 @@ package trees.nodes +import MyNode import trees.interfaces.Node enum class Color { From 9650516f543cb9211e6c518aa0722aa262755399 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Sun, 9 Apr 2023 03:55:04 +0300 Subject: [PATCH 47/90] struct: move abstract class MyNode into Nodes folder --- .../main/kotlin/trees/interfaces/MyNode.kt | 21 ------------------- app/src/main/kotlin/trees/nodes/AVLNode.kt | 1 - app/src/main/kotlin/trees/nodes/BSNode.kt | 1 - app/src/main/kotlin/trees/nodes/RBNode.kt | 1 - 4 files changed, 24 deletions(-) delete mode 100644 app/src/main/kotlin/trees/interfaces/MyNode.kt diff --git a/app/src/main/kotlin/trees/interfaces/MyNode.kt b/app/src/main/kotlin/trees/interfaces/MyNode.kt deleted file mode 100644 index e44f600..0000000 --- a/app/src/main/kotlin/trees/interfaces/MyNode.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -import trees.interfaces.Node - -abstract class MyNode, Subtype : MyNode> : Node { - abstract override var data: T - internal set - abstract override var left: Subtype? - internal set - abstract override var right: Subtype? - internal set - abstract override var parent: Subtype? - internal set - - override fun compareTo(other: Node): Int { - return data.compareTo(other.data) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/trees/nodes/AVLNode.kt index 1d3d303..152bb01 100644 --- a/app/src/main/kotlin/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/trees/nodes/AVLNode.kt @@ -5,7 +5,6 @@ package trees.nodes -import MyNode import trees.interfaces.Node class AVLNode>(override var data: T) : MyNode>() { diff --git a/app/src/main/kotlin/trees/nodes/BSNode.kt b/app/src/main/kotlin/trees/nodes/BSNode.kt index 36ba392..a6bc5f5 100644 --- a/app/src/main/kotlin/trees/nodes/BSNode.kt +++ b/app/src/main/kotlin/trees/nodes/BSNode.kt @@ -5,7 +5,6 @@ package trees.nodes -import MyNode import trees.interfaces.Node class BSNode>(override var data: T) : MyNode>() { diff --git a/app/src/main/kotlin/trees/nodes/RBNode.kt b/app/src/main/kotlin/trees/nodes/RBNode.kt index ef35901..686073b 100644 --- a/app/src/main/kotlin/trees/nodes/RBNode.kt +++ b/app/src/main/kotlin/trees/nodes/RBNode.kt @@ -5,7 +5,6 @@ package trees.nodes -import MyNode import trees.interfaces.Node enum class Color { From 47b17c807ae6cdde3e4897ab3c5bae6c7d4951a2 Mon Sep 17 00:00:00 2001 From: Lesh79 Date: Wed, 12 Apr 2023 20:41:59 +0300 Subject: [PATCH 48/90] fix: change private access modifier on internal --- app/src/main/kotlin/trees/nodes/AVLNode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/trees/nodes/AVLNode.kt index 152bb01..87ae176 100644 --- a/app/src/main/kotlin/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/trees/nodes/AVLNode.kt @@ -8,7 +8,7 @@ package trees.nodes import trees.interfaces.Node class AVLNode>(override var data: T) : MyNode>() { - private var height: Int = 1 + internal var height: Int = 1 override var left: AVLNode? = null override var right: AVLNode? = null override var parent: AVLNode? = null From 7de5e53c1144aeb94d5da7bf6f2daaff6cbcf15e Mon Sep 17 00:00:00 2001 From: Lesh79 Date: Wed, 12 Apr 2023 20:43:15 +0300 Subject: [PATCH 49/90] feat: add early AVLTreeTest --- app/src/test/kotlin/trees/AVLTreeTest.kt | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/src/test/kotlin/trees/AVLTreeTest.kt diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt new file mode 100644 index 0000000..028c047 --- /dev/null +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -0,0 +1,46 @@ +package trees + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import trees.trees.AVLTree +import trees.nodes.AVLNode +import java.lang.Math.abs + + +class AVLTree{ + @Test + fun InvariantTest(){ + val AVL = AVLTree>() + assertTrue(AVL.root == null) + AVL.add(KeyValue(10, 1040)) + AVL.add(KeyValue(59, 1041)) + AVL.add(KeyValue(60, 1030)) + AVL.add(KeyValue(7, 1080)) + AVL.add(KeyValue(12, 1060)) + AVL.add(KeyValue(6, 1010)) + AVL.add(KeyValue(15, 1000)) + assertTrue(AVL.root != null) + + + assertTrue(checkerInvariant(AVL.root)) + } + private fun checkerInvariant(node: AVLNode>?): Boolean{ + if (node == null) return true + + if (node.left != null && node.left!!.data > node.data) return false + + if (node.right != null && node.right!!.data < node.data) return false + + if ((node.right != null) && (node.left != null) && (abs(node.balanceFactor()) > 1)) return false + + if (node.parent?.height != max(node.left?.height ?: 0, node.right?.height ?: 0) + 1) return false + + return checkerInvariant(node.left) && checkerInvariant(node.right) + } + + private fun max(i: Int, j: Int): Int { + if (i > j) return i + if (j > i) return j + return 0 + } +} \ No newline at end of file From fab3467a02e958aa791e4579ca8f5a2d3fe8623f Mon Sep 17 00:00:00 2001 From: Lesh79 Date: Wed, 12 Apr 2023 20:47:25 +0300 Subject: [PATCH 50/90] fix: one of the invariants --- app/src/test/kotlin/trees/AVLTreeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index 028c047..de42ea3 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -33,7 +33,7 @@ class AVLTree{ if ((node.right != null) && (node.left != null) && (abs(node.balanceFactor()) > 1)) return false - if (node.parent?.height != max(node.left?.height ?: 0, node.right?.height ?: 0) + 1) return false + if (node.height != max(node.left?.height ?: 0, node.right?.height ?: 0) + 1) return false return checkerInvariant(node.left) && checkerInvariant(node.right) } From a62958f6bdbd9434649c45c254afb3c535492eec Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 17 Apr 2023 00:29:36 +0300 Subject: [PATCH 51/90] feat: add tests for all types of trees with InvariantTest fix: change access modifier for functions isBlack, isRed for RBTree --- app/build.gradle.kts | 13 +++- app/src/main/kotlin/trees/trees/RBTree.kt | 4 +- app/src/test/kotlin/trees/AVLTreeTest.kt | 85 ++++++++++++++-------- app/src/test/kotlin/trees/AppTest.kt | 14 ---- app/src/test/kotlin/trees/BSTreeTest.kt | 65 ++++++++++++----- app/src/test/kotlin/trees/InvariantTest.kt | 79 ++++++++++++++++++++ app/src/test/kotlin/trees/RBTreeTest.kt | 71 ++++++++++++++++++ 7 files changed, 261 insertions(+), 70 deletions(-) delete mode 100644 app/src/test/kotlin/trees/AppTest.kt create mode 100644 app/src/test/kotlin/trees/InvariantTest.kt create mode 100644 app/src/test/kotlin/trees/RBTreeTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 64cb2cc..15b125f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + /* * This file was generated by the Gradle 'init' task. * @@ -38,7 +43,7 @@ application { mainClass.set("MainKt") } -//tasks.named("test") { -// // Use JUnit Platform for unit tests. -// useJUnitPlatform() -//} +tasks.test { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/app/src/main/kotlin/trees/trees/RBTree.kt b/app/src/main/kotlin/trees/trees/RBTree.kt index b9465fc..8736c51 100644 --- a/app/src/main/kotlin/trees/trees/RBTree.kt +++ b/app/src/main/kotlin/trees/trees/RBTree.kt @@ -210,11 +210,11 @@ class RBTree> : ABSTree>() { return newRoot } - private fun isBlack(node: RBNode?): Boolean { + internal fun isBlack(node: RBNode?): Boolean { return ((node == null) || (node.color == Color.BLACK)) } - private fun isRed(node: RBNode?): Boolean { + internal fun isRed(node: RBNode?): Boolean { return node?.color == Color.RED } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index de42ea3..054287f 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -1,46 +1,67 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + package trees -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import trees.trees.AVLTree import trees.nodes.AVLNode -import java.lang.Math.abs +import trees.trees.AVLTree +import kotlin.random.Random +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue -class AVLTree{ - @Test - fun InvariantTest(){ - val AVL = AVLTree>() - assertTrue(AVL.root == null) - AVL.add(KeyValue(10, 1040)) - AVL.add(KeyValue(59, 1041)) - AVL.add(KeyValue(60, 1030)) - AVL.add(KeyValue(7, 1080)) - AVL.add(KeyValue(12, 1060)) - AVL.add(KeyValue(6, 1010)) - AVL.add(KeyValue(15, 1000)) - assertTrue(AVL.root != null) - - - assertTrue(checkerInvariant(AVL.root)) +class AVLTreeTest { + companion object { + const val seed = 42 } - private fun checkerInvariant(node: AVLNode>?): Boolean{ - if (node == null) return true - - if (node.left != null && node.left!!.data > node.data) return false - if (node.right != null && node.right!!.data < node.data) return false + private lateinit var tree: AVLTree + private lateinit var values: Array + private val randomizer = Random(seed) - if ((node.right != null) && (node.left != null) && (abs(node.balanceFactor()) > 1)) return false + @BeforeTest + fun init() { + values = Array(1000) { randomizer.nextInt(10000) } + tree = AVLTree() + } - if (node.height != max(node.left?.height ?: 0, node.right?.height ?: 0) + 1) return false + @Test + fun `check invariant while adding`() { + for (value in values) { + tree.add(value) + assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + } + } - return checkerInvariant(node.left) && checkerInvariant(node.right) + @Test + fun `check invariant while deleting`() { + for (value in values) { + tree.add(value) + } + values.shuffle(randomizer) + for (value in values) { + tree.delete(value) + assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + } } - private fun max(i: Int, j: Int): Int { - if (i > j) return i - if (j > i) return j - return 0 + @Test + fun `special incorrect test`() { + tree.add(10) + tree.add(15) + tree.add(5) + tree.root?.left?.left = AVLNode(100) + tree.root?.left?.left?.left = AVLNode(150) + assertFalse(InvariantTest.checkHeightAVL(tree.root)) + assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/AppTest.kt b/app/src/test/kotlin/trees/AppTest.kt deleted file mode 100644 index 456f24a..0000000 --- a/app/src/test/kotlin/trees/AppTest.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package trees - -import kotlin.test.Test - -class AppTest { - @Test - fun appHasAGreeting() { -// val classUnderTest = App() -// assertNotNull(classUnderTest.greeting, "app should have a greeting") - } -} diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 41af052..708d2a9 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -1,33 +1,62 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + package trees -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test import trees.nodes.BSNode import trees.trees.BSTree -import java.util.* +import kotlin.random.Random +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + class BSTreeTest { + companion object { + const val seed = 42 + } - @Test - fun testInvariant() { - val bst = BSTree() - val lst = List(100) { Random(42).nextInt() } - for (num in lst) { - bst.add(num) - } + private lateinit var tree: BSTree + private lateinit var values: Array + private val randomizer = Random(seed) - assertTrue(checkInvariant(bst.root)) + @BeforeTest + fun init() { + values = Array(1000) { randomizer.nextInt(10000) } + tree = BSTree() + } + @Test + fun `check invariant while adding`() { + for (value in values) { + tree.add(value) + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + } } - private fun checkInvariant(node: BSNode?): Boolean { - if (node == null) return true - if (node.left != null && node.left!!.data > node.data) { - return false + @Test + fun `check invariant while deleting`() { + for (value in values) { + tree.add(value) } - if (node.right != null && node.right!!.data < node.data) { - return false + values.shuffle(randomizer) + for (value in values) { + tree.delete(value) + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } - return checkInvariant(node.left) && checkInvariant(node.right) + } + + @Test + fun `special incorrect test`() { + tree.add(10) + tree.root?.left = BSNode(15) + tree.root?.right = BSNode(5) + assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/InvariantTest.kt b/app/src/test/kotlin/trees/InvariantTest.kt new file mode 100644 index 0000000..3517953 --- /dev/null +++ b/app/src/test/kotlin/trees/InvariantTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package trees + +import trees.interfaces.Node +import trees.nodes.AVLNode +import trees.nodes.Color +import trees.nodes.RBNode +import trees.trees.RBTree +import kotlin.math.abs + +object InvariantTest { + fun , NodeType : Node> checkLinksToParent(node: Node?): Boolean { + if (node == null) return true + var result = true + result = result && ((node.left?.parent == node) || (node.left == null)) + result = result && ((node.right?.parent == node) || (node.right == null)) + if (result) { + return checkLinksToParent(node.left) && checkLinksToParent(node.right) + } + return false + } + + fun , NodeType : Node> checkDataInNodes(node: Node?): Boolean { + if (node == null) return true + var result = true + val leftChild = node.left + val rightChild = node.right + result = result && ((leftChild?.data == null) || (leftChild.data < node.data)) + result = result && ((rightChild?.data == null) || (rightChild.data > node.data)) + if (result) { + return checkDataInNodes(node.left) && checkDataInNodes(node.right) + } + return false + } + + fun > checkHeightAVL(node: AVLNode?): Boolean { + if (node == null) return true + var result = true + val rightHeight = node.right?.height ?: 0 + val leftHeight = node.left?.height ?: 0 + result = result && (maxOf(rightHeight, leftHeight) + 1 == node.height) + result = result && (abs(node.balanceFactor()) <= 1) + if (result) { + return checkHeightAVL(node.right) && checkHeightAVL(node.left) + } + return false + } + + fun > checkBlackHeight(node: RBNode?): Boolean { + fun > getBlackHeightRB(node: RBNode?): Pair { + if (node == null) return Pair(true, 1) + val leftHeight = getBlackHeightRB(node.left) + val rightHeight = getBlackHeightRB(node.right) + if (leftHeight.second != rightHeight.second || !leftHeight.first || !rightHeight.first) { + return Pair(false, rightHeight.second + (if (node.color == Color.BLACK) 1 else 0)) + } + return if (node.color == Color.BLACK) + Pair(true, rightHeight.second + 1) + else Pair(true, rightHeight.second) + } + return getBlackHeightRB(node).first + } + + fun > checkRedParent(tree: RBTree, node: RBNode?): Boolean { + if (node == null) return true + if (tree.isRed(node)) { + if ((tree.isRed(node.left)) or (tree.isRed(node.right))) + return false + } + return checkRedParent(tree, node.left) && checkRedParent(tree, node.right) + } +} diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt new file mode 100644 index 0000000..e4b41cc --- /dev/null +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package trees + + +import trees.nodes.Color +import trees.nodes.RBNode +import trees.trees.RBTree +import kotlin.random.Random +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class RBTreeTest { + companion object { + const val seed = 42 + } + + private lateinit var tree: RBTree + private lateinit var values: Array + private val randomizer = Random(seed) + + @BeforeTest + fun init() { + values = Array(1000) { randomizer.nextInt(10000) } + tree = RBTree() + } + + @Test + fun `check invariant while adding`() { + for (value in values) { + tree.add(value) + assertTrue(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") + assertTrue(InvariantTest.checkRedParent(tree, tree.root), "Failed invariant, incorrect color for node") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + } + } + + @Test + fun `check invariant while deleting`() { + for (value in values) { + tree.add(value) + } + values.shuffle(randomizer) + for (value in values) { + tree.delete(value) + assertTrue(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") + assertTrue(InvariantTest.checkRedParent(tree, tree.root), "Failed invariant, incorrect color for node") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + } + } + + @Test + fun `special incorrect test`() { + tree.add(10) + tree.root?.left = RBNode(15) + tree.root?.right = RBNode(5) + tree.root?.right?.color = Color.BLACK + tree.root?.color = Color.RED + assertFalse(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") + assertFalse(InvariantTest.checkRedParent(tree, tree.root), "Failed invariant, incorrect color for node") + assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + } +} \ No newline at end of file From 45fd11a9c0d7c6d5f9d92382590234f18291e047 Mon Sep 17 00:00:00 2001 From: Roket_Flame Date: Mon, 17 Apr 2023 10:42:02 +0300 Subject: [PATCH 52/90] feat: add tests for function "contains" fix: fix README.md --- README.md | 23 ++++++++++++++++------- app/src/test/kotlin/trees/AVLTreeTest.kt | 8 ++++++++ app/src/test/kotlin/trees/BSTreeTest.kt | 8 ++++++++ app/src/test/kotlin/trees/RBTreeTest.kt | 8 ++++++++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 164878e..ec5d4e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ABOBA +# Trees teemEight's project of implementation of three trees: AVL, red-black, binary @@ -10,11 +10,15 @@ teemEight's project of implementation of three trees: AVL, red-black, binary - [отдел продаж](https://steamcommunity.com/groups/Otedel_Prodaj) [@wokuparalyzed](https://www.github.com/wokuparalyzed) - [and their fans](https://steamcommunity.com/groups/kazakhstansgaminggirls) [@Lesh79](https://www.github.com/Lesh79) +## About + +This project provides access to three types of binary-search trees: red-black, AVL, binary + ## Features -- Add -- Delete -- Contains +- add +- delete +- contains - Cross platform - Deploy - Bugs @@ -33,9 +37,10 @@ If you have any feedback, please reach out to us at Issues ## About Me -I'm a full stack developer... \ -Help please...(( \ +I'm a full stack developer... (( \ 18-20 y.o SPBU SE +

+ ## 🛠 Skills @@ -48,5 +53,9 @@ Help please...(( \ [![gradle](https://img.shields.io/badge/gradle-FFFFFF?style=for-the-badge&logo=gradle&logoColor=black&)](https://gradle.org/) \ [](https://youtu.be/6Cv2kmgX0So?t=30) \ [](https://kotlinlang.org/) \ -[](https://kotlinlang.org/) +[](https://www.youtube.com/watch?v=_CTod1hk-bc) \ +[](https://i.imgur.com/rgGO1Oc.png) + + +
diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index 054287f..f83b4a4 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -64,4 +64,12 @@ class AVLTreeTest { assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } + + @Test + fun `check for the presence of elements`() { + for (value in values) { + tree.add(value) + assertTrue(tree.contain(value)) + } + } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 708d2a9..77c6e68 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -59,4 +59,12 @@ class BSTreeTest { assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } + + @Test + fun `check for the presence of elements`() { + for (value in values) { + tree.add(value) + assertTrue(tree.contain(value)) + } + } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt index e4b41cc..cdd2fef 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -68,4 +68,12 @@ class RBTreeTest { assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } + + @Test + fun `check for the presence of elements`() { + for (value in values) { + tree.add(value) + assertTrue(tree.contain(value)) + } + } } \ No newline at end of file From b209b00ce28fb1b66811fd231abe857d968a4a43 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Mon, 17 Apr 2023 16:33:59 +0300 Subject: [PATCH 53/90] fix: simple fix classes of nodes (data class now, add hashCode()) feat: update README.md --- README.md | 9 +++++++++ app/src/main/kotlin/trees/KeyValue.kt | 6 +++++- app/src/main/kotlin/trees/nodes/AVLNode.kt | 14 ++++++-------- app/src/main/kotlin/trees/nodes/BSNode.kt | 14 ++++++-------- app/src/main/kotlin/trees/nodes/MyNode.kt | 11 +++++++++++ app/src/main/kotlin/trees/nodes/RBNode.kt | 12 +++++------- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index ec5d4e8..c44d00f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,15 @@ teemEight's project of implementation of three trees: AVL, red-black, binary This project provides access to three types of binary-search trees: red-black, AVL, binary +## Roadmap + +- [x] License +- [x] CI +- [x] Realized all types of trees +- [x] Added tests +- [ ] Storing in databases +- [ ] GUI + ## Features - add diff --git a/app/src/main/kotlin/trees/KeyValue.kt b/app/src/main/kotlin/trees/KeyValue.kt index deca452..e9ce478 100644 --- a/app/src/main/kotlin/trees/KeyValue.kt +++ b/app/src/main/kotlin/trees/KeyValue.kt @@ -20,7 +20,7 @@ class KeyValue, V>(private val key: K, private var value: V?) override fun equals(other: Any?): Boolean { if (other is KeyValue<*, *>) { - return key.equals(other.getKey()) + return key == other.getKey() } return false } @@ -28,4 +28,8 @@ class KeyValue, V>(private val key: K, private var value: V?) override fun toString(): String { return "$key: $value" } + + override fun hashCode(): Int { + return key.hashCode() + } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/trees/nodes/AVLNode.kt index 87ae176..11061ed 100644 --- a/app/src/main/kotlin/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/trees/nodes/AVLNode.kt @@ -5,9 +5,7 @@ package trees.nodes -import trees.interfaces.Node - -class AVLNode>(override var data: T) : MyNode>() { +data class AVLNode>(override var data: T) : MyNode>() { internal var height: Int = 1 override var left: AVLNode? = null override var right: AVLNode? = null @@ -28,13 +26,9 @@ class AVLNode>(override var data: T) : MyNode>() return (rightNode?.height ?: 0) - (leftNode?.height ?: 0) } - override fun compareTo(other: Node>): Int { - return data.compareTo(other.data) - } - override fun equals(other: Any?): Boolean { if (other is AVLNode<*>) { - return data.equals(other.data) + return data == other.data } return false } @@ -42,4 +36,8 @@ class AVLNode>(override var data: T) : MyNode>() override fun toString(): String { return "$data" } + + override fun hashCode(): Int { + return data.hashCode() + } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/BSNode.kt b/app/src/main/kotlin/trees/nodes/BSNode.kt index a6bc5f5..663c70c 100644 --- a/app/src/main/kotlin/trees/nodes/BSNode.kt +++ b/app/src/main/kotlin/trees/nodes/BSNode.kt @@ -5,20 +5,14 @@ package trees.nodes -import trees.interfaces.Node - -class BSNode>(override var data: T) : MyNode>() { +data class BSNode>(override var data: T) : MyNode>() { override var left: BSNode? = null override var right: BSNode? = null override var parent: BSNode? = null - override fun compareTo(other: Node>): Int { - return data.compareTo(other.data) - } - override fun equals(other: Any?): Boolean { if (other is BSNode<*>) { - return data.equals(other.data) + return data == other.data } return false } @@ -26,4 +20,8 @@ class BSNode>(override var data: T) : MyNode>() { override fun toString(): String { return "$data" } + + override fun hashCode(): Int { + return data.hashCode() + } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/MyNode.kt b/app/src/main/kotlin/trees/nodes/MyNode.kt index bb851ad..885b28b 100644 --- a/app/src/main/kotlin/trees/nodes/MyNode.kt +++ b/app/src/main/kotlin/trees/nodes/MyNode.kt @@ -23,4 +23,15 @@ abstract class MyNode, Subtype : MyNode> : Node): Int { return data.compareTo(other.data) } + + override fun hashCode(): Int { + return data.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other is MyNode<*, *>) { + return data == other.data + } + return false + } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/nodes/RBNode.kt b/app/src/main/kotlin/trees/nodes/RBNode.kt index 686073b..664d68a 100644 --- a/app/src/main/kotlin/trees/nodes/RBNode.kt +++ b/app/src/main/kotlin/trees/nodes/RBNode.kt @@ -5,8 +5,6 @@ package trees.nodes -import trees.interfaces.Node - enum class Color { RED, BLACK @@ -18,13 +16,9 @@ class RBNode>(override var data: T) : MyNode>() { override var right: RBNode? = null override var parent: RBNode? = null - override fun compareTo(other: Node>): Int { - return data.compareTo(other.data) - } - override fun equals(other: Any?): Boolean { if (other is RBNode<*>) { - return data.equals(other.data) + return data == other.data } return false } @@ -32,4 +26,8 @@ class RBNode>(override var data: T) : MyNode>() { override fun toString(): String { return "$color - $data" } + + override fun hashCode(): Int { + return data.hashCode() + } } \ No newline at end of file From 3080f2f6a826b0e102bf986287db1a056fd5387d Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Mon, 17 Apr 2023 22:12:50 +0300 Subject: [PATCH 54/90] fix: add information about value in tests when test failed feat: change isBlack(), isRed() to static methods --- app/src/main/kotlin/trees/trees/RBTree.kt | 20 +++++++----- app/src/test/kotlin/trees/AVLTreeTest.kt | 18 +++++++---- app/src/test/kotlin/trees/BSTreeTest.kt | 14 ++++++--- app/src/test/kotlin/trees/InvariantTest.kt | 8 ++--- app/src/test/kotlin/trees/RBTreeTest.kt | 36 ++++++++++++++++------ 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/trees/trees/RBTree.kt b/app/src/main/kotlin/trees/trees/RBTree.kt index 8736c51..8e422fc 100644 --- a/app/src/main/kotlin/trees/trees/RBTree.kt +++ b/app/src/main/kotlin/trees/trees/RBTree.kt @@ -10,6 +10,18 @@ import trees.nodes.Color import trees.nodes.RBNode class RBTree> : ABSTree>() { + companion object { + @JvmStatic + internal fun > isBlack(node: RBNode?): Boolean { + return ((node == null) || (node.color == Color.BLACK)) + } + + @JvmStatic + internal fun > isRed(node: RBNode?): Boolean { + return node?.color == Color.RED + } + } + override fun add(data: T) { val node = RBNode(data) root = simpleAdd(root, node) @@ -209,12 +221,4 @@ class RBTree> : ABSTree>() { subTree?.parent = parent return newRoot } - - internal fun isBlack(node: RBNode?): Boolean { - return ((node == null) || (node.color == Color.BLACK)) - } - - internal fun isRed(node: RBNode?): Boolean { - return node?.color == Color.RED - } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index f83b4a4..532ae30 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -33,9 +33,12 @@ class AVLTreeTest { fun `check invariant while adding`() { for (value in values) { tree.add(value) - assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height") - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height, value: $value") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue( + InvariantTest.checkLinksToParent(tree.root), + "Failed invariant, incorrect parent's link, value: $value" + ) } } @@ -47,9 +50,12 @@ class AVLTreeTest { values.shuffle(randomizer) for (value in values) { tree.delete(value) - assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height") - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height, value: $value") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue( + InvariantTest.checkLinksToParent(tree.root), + "Failed invariant, incorrect parent's link, value: $value" + ) } } diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 77c6e68..4cad530 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -33,8 +33,11 @@ class BSTreeTest { fun `check invariant while adding`() { for (value in values) { tree.add(value) - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue( + InvariantTest.checkLinksToParent(tree.root), + "Failed invariant, incorrect parent's link, value: $value" + ) } } @@ -46,8 +49,11 @@ class BSTreeTest { values.shuffle(randomizer) for (value in values) { tree.delete(value) - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue( + InvariantTest.checkLinksToParent(tree.root), + "Failed invariant, incorrect parent's link, value: $value" + ) } } diff --git a/app/src/test/kotlin/trees/InvariantTest.kt b/app/src/test/kotlin/trees/InvariantTest.kt index 3517953..e40b500 100644 --- a/app/src/test/kotlin/trees/InvariantTest.kt +++ b/app/src/test/kotlin/trees/InvariantTest.kt @@ -68,12 +68,12 @@ object InvariantTest { return getBlackHeightRB(node).first } - fun > checkRedParent(tree: RBTree, node: RBNode?): Boolean { + fun > checkRedParent(node: RBNode?): Boolean { if (node == null) return true - if (tree.isRed(node)) { - if ((tree.isRed(node.left)) or (tree.isRed(node.right))) + if (RBTree.isRed(node)) { + if ((RBTree.isRed(node.left)) or (RBTree.isRed(node.right))) return false } - return checkRedParent(tree, node.left) && checkRedParent(tree, node.right) + return checkRedParent(node.left) && checkRedParent(node.right) } } diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt index cdd2fef..09de6e2 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -34,10 +34,19 @@ class RBTreeTest { fun `check invariant while adding`() { for (value in values) { tree.add(value) - assertTrue(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") - assertTrue(InvariantTest.checkRedParent(tree, tree.root), "Failed invariant, incorrect color for node") - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertTrue( + InvariantTest.checkBlackHeight(tree.root), + "Failed invariant, incorrect black height, value: $value" + ) + assertTrue( + InvariantTest.checkRedParent(tree.root), + "Failed invariant, incorrect color for node, value: $value" + ) + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue( + InvariantTest.checkLinksToParent(tree.root), + "Failed invariant, incorrect parent's link, value: $value" + ) } } @@ -49,10 +58,19 @@ class RBTreeTest { values.shuffle(randomizer) for (value in values) { tree.delete(value) - assertTrue(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") - assertTrue(InvariantTest.checkRedParent(tree, tree.root), "Failed invariant, incorrect color for node") - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertTrue(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertTrue( + InvariantTest.checkBlackHeight(tree.root), + "Failed invariant, incorrect black height, value: $value" + ) + assertTrue( + InvariantTest.checkRedParent(tree.root), + "Failed invariant, incorrect color for node, value: $value" + ) + assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue( + InvariantTest.checkLinksToParent(tree.root), + "Failed invariant, incorrect parent's link, value: $value" + ) } } @@ -64,7 +82,7 @@ class RBTreeTest { tree.root?.right?.color = Color.BLACK tree.root?.color = Color.RED assertFalse(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") - assertFalse(InvariantTest.checkRedParent(tree, tree.root), "Failed invariant, incorrect color for node") + assertFalse(InvariantTest.checkRedParent(tree.root), "Failed invariant, incorrect color for node") assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } From a85c0ae478b485963cf7b17249772e55c71f44aa Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Mon, 17 Apr 2023 22:13:29 +0300 Subject: [PATCH 55/90] fix: fix dependencies --- app/build.gradle.kts | 4 ++-- app/src/main/kotlin/trees/Main.kt | 12 +----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 15b125f..ea4546a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,9 +33,9 @@ dependencies { // This dependency is used by the application. implementation("com.google.guava:guava:31.1-jre") + implementation("org.neo4j.driver:neo4j-java-driver:5.6.0") testImplementation("org.testng:testng:7.7.0") - testImplementation("org.testng:testng:7.7.0") - testImplementation("org.testng:testng:7.7.0") + } application { diff --git a/app/src/main/kotlin/trees/Main.kt b/app/src/main/kotlin/trees/Main.kt index 557c489..df84156 100644 --- a/app/src/main/kotlin/trees/Main.kt +++ b/app/src/main/kotlin/trees/Main.kt @@ -3,16 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import trees.KeyValue -import trees.trees.BSTree - -/* - * Copyright 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - fun main(args: Array) { - val tree = BSTree>() - tree.add(KeyValue(10, 100)) - println(tree.get(KeyValue(10, null))) + } \ No newline at end of file From 7310c8a9e0f84d0331940139d5dd47542c75cd25 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 02:10:50 +0300 Subject: [PATCH 56/90] struct: restructure code (preparation for adding storage database) feat: add primary constructor for nodes --- app/build.gradle.kts | 5 ++++- app/src/main/kotlin/Main.kt | 15 +++++++++++++++ app/src/main/kotlin/{ => app}/trees/ABSTree.kt | 18 +++++++++++++++--- .../main/kotlin/{ => app}/trees/KeyValue.kt | 2 +- .../kotlin/{ => app}/trees/interfaces/Node.kt | 2 +- .../kotlin/{ => app}/trees/interfaces/Tree.kt | 2 +- .../kotlin/{ => app}/trees/nodes/AVLNode.kt | 16 +++++++++------- .../kotlin/{ => app}/trees/nodes/BSNode.kt | 13 ++++++++----- .../kotlin/{ => app}/trees/nodes/MyNode.kt | 4 ++-- .../kotlin/{ => app}/trees/nodes/RBNode.kt | 15 +++++++++------ .../kotlin/{ => app}/trees/trees/AVLTree.kt | 6 +++--- .../kotlin/{ => app}/trees/trees/BSTree.kt | 6 +++--- .../kotlin/{ => app}/trees/trees/RBTree.kt | 8 ++++---- app/src/main/kotlin/trees/Main.kt | 8 -------- app/src/test/kotlin/trees/AVLTreeTest.kt | 4 ++-- app/src/test/kotlin/trees/BSTreeTest.kt | 4 ++-- app/src/test/kotlin/trees/InvariantTest.kt | 10 +++++----- app/src/test/kotlin/trees/RBTreeTest.kt | 6 +++--- 18 files changed, 87 insertions(+), 57 deletions(-) create mode 100644 app/src/main/kotlin/Main.kt rename app/src/main/kotlin/{ => app}/trees/ABSTree.kt (88%) rename app/src/main/kotlin/{ => app}/trees/KeyValue.kt (97%) rename app/src/main/kotlin/{ => app}/trees/interfaces/Node.kt (90%) rename app/src/main/kotlin/{ => app}/trees/interfaces/Tree.kt (87%) rename app/src/main/kotlin/{ => app}/trees/nodes/AVLNode.kt (73%) rename app/src/main/kotlin/{ => app}/trees/nodes/BSNode.kt (60%) rename app/src/main/kotlin/{ => app}/trees/nodes/MyNode.kt (93%) rename app/src/main/kotlin/{ => app}/trees/nodes/RBNode.kt (60%) rename app/src/main/kotlin/{ => app}/trees/trees/AVLTree.kt (95%) rename app/src/main/kotlin/{ => app}/trees/trees/BSTree.kt (87%) rename app/src/main/kotlin/{ => app}/trees/trees/RBTree.kt (98%) delete mode 100644 app/src/main/kotlin/trees/Main.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ea4546a..11810fa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,6 +14,7 @@ plugins { // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. id("org.jetbrains.kotlin.jvm") version "1.8.10" + kotlin("plugin.serialization") version "1.8.20" // Apply the application plugin to add support for building a CLI application in Java. application @@ -33,8 +34,10 @@ dependencies { // This dependency is used by the application. implementation("com.google.guava:guava:31.1-jre") - implementation("org.neo4j.driver:neo4j-java-driver:5.6.0") testImplementation("org.testng:testng:7.7.0") + implementation("org.neo4j:neo4j-ogm-core:4.0.5") + runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") } diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt new file mode 100644 index 0000000..1283e02 --- /dev/null +++ b/app/src/main/kotlin/Main.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import app.trees.trees.AVLTree +import kotlin.random.Random + +fun main(args: Array) { + val tree = AVLTree() + val lst = List(100) { Random.nextInt(1000) } + lst.forEach { tree.add(it) } + tree.root = null + println(tree.preOrder()) +} \ No newline at end of file diff --git a/app/src/main/kotlin/trees/ABSTree.kt b/app/src/main/kotlin/app/trees/ABSTree.kt similarity index 88% rename from app/src/main/kotlin/trees/ABSTree.kt rename to app/src/main/kotlin/app/trees/ABSTree.kt index c8b0a3f..9215166 100644 --- a/app/src/main/kotlin/trees/ABSTree.kt +++ b/app/src/main/kotlin/app/trees/ABSTree.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees +package app.trees -import trees.interfaces.Tree -import trees.nodes.MyNode +import app.trees.interfaces.Tree +import app.trees.nodes.MyNode abstract class ABSTree, NodeType : MyNode> : Tree { var root: NodeType? = null @@ -121,4 +121,16 @@ abstract class ABSTree, NodeType : MyNode> : Tree newChild?.parent = child.parent return newChild } + + fun preOrder(): List { + val result = mutableListOf() + fun walk(node: NodeType, lst: MutableList) { + lst.add(node) + node.left?.let { walk(it, lst) } + node.right?.let { walk(it, lst) } + } + if (root == null) return result + root?.let { walk(it, result) } + return result + } } \ No newline at end of file diff --git a/app/src/main/kotlin/trees/KeyValue.kt b/app/src/main/kotlin/app/trees/KeyValue.kt similarity index 97% rename from app/src/main/kotlin/trees/KeyValue.kt rename to app/src/main/kotlin/app/trees/KeyValue.kt index e9ce478..88d92f5 100644 --- a/app/src/main/kotlin/trees/KeyValue.kt +++ b/app/src/main/kotlin/app/trees/KeyValue.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees +package app.trees class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { fun getKey(): K { diff --git a/app/src/main/kotlin/trees/interfaces/Node.kt b/app/src/main/kotlin/app/trees/interfaces/Node.kt similarity index 90% rename from app/src/main/kotlin/trees/interfaces/Node.kt rename to app/src/main/kotlin/app/trees/interfaces/Node.kt index bd2bf00..8f4aa58 100644 --- a/app/src/main/kotlin/trees/interfaces/Node.kt +++ b/app/src/main/kotlin/app/trees/interfaces/Node.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.interfaces +package app.trees.interfaces interface Node, Subtype : Node> : Comparable> { val data: T diff --git a/app/src/main/kotlin/trees/interfaces/Tree.kt b/app/src/main/kotlin/app/trees/interfaces/Tree.kt similarity index 87% rename from app/src/main/kotlin/trees/interfaces/Tree.kt rename to app/src/main/kotlin/app/trees/interfaces/Tree.kt index e2a8738..225a0cc 100644 --- a/app/src/main/kotlin/trees/interfaces/Tree.kt +++ b/app/src/main/kotlin/app/trees/interfaces/Tree.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.interfaces +package app.trees.interfaces interface Tree> { fun add(data: T) diff --git a/app/src/main/kotlin/trees/nodes/AVLNode.kt b/app/src/main/kotlin/app/trees/nodes/AVLNode.kt similarity index 73% rename from app/src/main/kotlin/trees/nodes/AVLNode.kt rename to app/src/main/kotlin/app/trees/nodes/AVLNode.kt index 11061ed..9a5da65 100644 --- a/app/src/main/kotlin/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/AVLNode.kt @@ -3,13 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.nodes - -data class AVLNode>(override var data: T) : MyNode>() { - internal var height: Int = 1 - override var left: AVLNode? = null - override var right: AVLNode? = null - override var parent: AVLNode? = null +package app.trees.nodes + +data class AVLNode>( + override var data: T, + internal var height: Int = 1, + override var left: AVLNode? = null, + override var right: AVLNode? = null, + override var parent: AVLNode? = null, +) : MyNode>() { internal fun updateHeight() { diff --git a/app/src/main/kotlin/trees/nodes/BSNode.kt b/app/src/main/kotlin/app/trees/nodes/BSNode.kt similarity index 60% rename from app/src/main/kotlin/trees/nodes/BSNode.kt rename to app/src/main/kotlin/app/trees/nodes/BSNode.kt index 663c70c..70e3434 100644 --- a/app/src/main/kotlin/trees/nodes/BSNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/BSNode.kt @@ -3,12 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.nodes +package app.trees.nodes + +data class BSNode>( + override var data: T, + override var left: BSNode? = null, + override var right: BSNode? = null, + override var parent: BSNode? = null, +) : MyNode>() { -data class BSNode>(override var data: T) : MyNode>() { - override var left: BSNode? = null - override var right: BSNode? = null - override var parent: BSNode? = null override fun equals(other: Any?): Boolean { if (other is BSNode<*>) { diff --git a/app/src/main/kotlin/trees/nodes/MyNode.kt b/app/src/main/kotlin/app/trees/nodes/MyNode.kt similarity index 93% rename from app/src/main/kotlin/trees/nodes/MyNode.kt rename to app/src/main/kotlin/app/trees/nodes/MyNode.kt index 885b28b..e6cdc89 100644 --- a/app/src/main/kotlin/trees/nodes/MyNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/MyNode.kt @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.nodes/* +package app.trees.nodes/* * Copyright (c) 2023 teemEight * SPDX-License-Identifier: Apache-2.0 */ -import trees.interfaces.Node +import app.trees.interfaces.Node abstract class MyNode, Subtype : MyNode> : Node { abstract override var data: T diff --git a/app/src/main/kotlin/trees/nodes/RBNode.kt b/app/src/main/kotlin/app/trees/nodes/RBNode.kt similarity index 60% rename from app/src/main/kotlin/trees/nodes/RBNode.kt rename to app/src/main/kotlin/app/trees/nodes/RBNode.kt index 664d68a..ddd2e1a 100644 --- a/app/src/main/kotlin/trees/nodes/RBNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/RBNode.kt @@ -3,18 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.nodes +package app.trees.nodes enum class Color { RED, BLACK } -class RBNode>(override var data: T) : MyNode>() { - var color: Color = Color.RED - override var left: RBNode? = null - override var right: RBNode? = null - override var parent: RBNode? = null +class RBNode>( + override var data: T, + var color: Color = Color.RED, + override var left: RBNode? = null, + override var right: RBNode? = null, + override var parent: RBNode? = null, +) : MyNode>() { + override fun equals(other: Any?): Boolean { if (other is RBNode<*>) { diff --git a/app/src/main/kotlin/trees/trees/AVLTree.kt b/app/src/main/kotlin/app/trees/trees/AVLTree.kt similarity index 95% rename from app/src/main/kotlin/trees/trees/AVLTree.kt rename to app/src/main/kotlin/app/trees/trees/AVLTree.kt index 2e78892..84d138a 100644 --- a/app/src/main/kotlin/trees/trees/AVLTree.kt +++ b/app/src/main/kotlin/app/trees/trees/AVLTree.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.trees +package app.trees.trees -import trees.ABSTree -import trees.nodes.AVLNode +import app.trees.ABSTree +import app.trees.nodes.AVLNode class AVLTree> : ABSTree>() { diff --git a/app/src/main/kotlin/trees/trees/BSTree.kt b/app/src/main/kotlin/app/trees/trees/BSTree.kt similarity index 87% rename from app/src/main/kotlin/trees/trees/BSTree.kt rename to app/src/main/kotlin/app/trees/trees/BSTree.kt index 6702ccf..37516ba 100644 --- a/app/src/main/kotlin/trees/trees/BSTree.kt +++ b/app/src/main/kotlin/app/trees/trees/BSTree.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.trees +package app.trees.trees -import trees.ABSTree -import trees.nodes.BSNode +import app.trees.ABSTree +import app.trees.nodes.BSNode class BSTree> : ABSTree>() { diff --git a/app/src/main/kotlin/trees/trees/RBTree.kt b/app/src/main/kotlin/app/trees/trees/RBTree.kt similarity index 98% rename from app/src/main/kotlin/trees/trees/RBTree.kt rename to app/src/main/kotlin/app/trees/trees/RBTree.kt index 8e422fc..079b3c3 100644 --- a/app/src/main/kotlin/trees/trees/RBTree.kt +++ b/app/src/main/kotlin/app/trees/trees/RBTree.kt @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package trees.trees +package app.trees.trees -import trees.ABSTree -import trees.nodes.Color -import trees.nodes.RBNode +import app.trees.ABSTree +import app.trees.nodes.Color +import app.trees.nodes.RBNode class RBTree> : ABSTree>() { companion object { diff --git a/app/src/main/kotlin/trees/Main.kt b/app/src/main/kotlin/trees/Main.kt deleted file mode 100644 index df84156..0000000 --- a/app/src/main/kotlin/trees/Main.kt +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -fun main(args: Array) { - -} \ No newline at end of file diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index 532ae30..f2111ce 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -5,8 +5,8 @@ package trees -import trees.nodes.AVLNode -import trees.trees.AVLTree +import app.trees.nodes.AVLNode +import app.trees.trees.AVLTree import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 4cad530..9684d83 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -5,8 +5,8 @@ package trees -import trees.nodes.BSNode -import trees.trees.BSTree +import app.trees.nodes.BSNode +import app.trees.trees.BSTree import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/app/src/test/kotlin/trees/InvariantTest.kt b/app/src/test/kotlin/trees/InvariantTest.kt index e40b500..c50d949 100644 --- a/app/src/test/kotlin/trees/InvariantTest.kt +++ b/app/src/test/kotlin/trees/InvariantTest.kt @@ -8,11 +8,11 @@ */ package trees -import trees.interfaces.Node -import trees.nodes.AVLNode -import trees.nodes.Color -import trees.nodes.RBNode -import trees.trees.RBTree +import app.trees.interfaces.Node +import app.trees.nodes.AVLNode +import app.trees.nodes.Color +import app.trees.nodes.RBNode +import app.trees.trees.RBTree import kotlin.math.abs object InvariantTest { diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt index 09de6e2..3f930b6 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -6,9 +6,9 @@ package trees -import trees.nodes.Color -import trees.nodes.RBNode -import trees.trees.RBTree +import app.trees.nodes.Color +import app.trees.nodes.RBNode +import app.trees.trees.RBTree import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test From 9d5ed2a9d9304b7714e5313a002f96ae7996caea Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 03:53:48 +0300 Subject: [PATCH 57/90] feat: add way to save trees to neo4j database --- app/src/main/kotlin/repo/Neo4jRepository.kt | 105 ++++++++++++++++++ app/src/main/kotlin/repo/Repository.kt | 41 +++++++ .../kotlin/repo/serialization/Serializable.kt | 39 +++++++ .../neo4jEntities/SerializableNodeEntity.kt | 29 +++++ .../neo4jEntities/SerializableTreeEntity.kt | 27 +++++ .../serialization/strategies/AVLStrategy.kt | 35 ++++++ .../serialization/strategies/Serialization.kt | 30 +++++ docker-compose.yml | 11 ++ 8 files changed, 317 insertions(+) create mode 100644 app/src/main/kotlin/repo/Neo4jRepository.kt create mode 100644 app/src/main/kotlin/repo/Repository.kt create mode 100644 app/src/main/kotlin/repo/serialization/Serializable.kt create mode 100644 app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt create mode 100644 app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt create mode 100644 app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt create mode 100644 app/src/main/kotlin/repo/serialization/strategies/Serialization.kt create mode 100644 docker-compose.yml diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt new file mode 100644 index 0000000..036cc33 --- /dev/null +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo + +import app.trees.ABSTree +import app.trees.nodes.MyNode +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.cypher.ComparisonOperator +import org.neo4j.ogm.cypher.Filter +import org.neo4j.ogm.cypher.Filters +import org.neo4j.ogm.session.SessionFactory +import repo.serialization.SerializableNode +import repo.serialization.SerializableTree +import repo.serialization.neo4jEntities.SerializableNodeEntity +import repo.serialization.neo4jEntities.SerializableTreeEntity +import repo.serialization.strategies.Serialization + +class Neo4jRepo, + NodeType : MyNode, + TreeType : ABSTree>( + strategy: Serialization, + configuration: Configuration +) : Repository(strategy) { + private val sessionFactory = SessionFactory(configuration, "app.model.repo") + private val session = sessionFactory.openSession() + + private fun SerializableNodeEntity.toSerializableNode(): SerializableNode { + return SerializableNode( + value, + metadata, + left?.toSerializableNode(), + right?.toSerializableNode(), + ) + } + + private fun SerializableTreeEntity.toTree(): SerializableTree { + return SerializableTree( + verboseName, + typeOfTree, + root?.toSerializableNode(), + ) + } + + private fun SerializableNode.toEntity(): SerializableNodeEntity { + return SerializableNodeEntity( + value, + metadata, + left?.toEntity(), + right?.toEntity(), + ) + } + + private fun SerializableTree.toEntity(): SerializableTreeEntity { + return SerializableTreeEntity( + verboseName, + typeOfTree, + root?.toEntity(), + ) + } + + override fun save(verboseName: String, tree: TreeType) { + deleteByVerboseName(verboseName) + val entityTree = tree.toSerializableTree(verboseName).toEntity() + session.save(entityTree) + } + + private fun findByVerboseName(verboseName: String) = session.loadAll( + SerializableTreeEntity::class.java, + Filters().and( + Filter("verboseName", ComparisonOperator.EQUALS, verboseName) + ).and( + Filter("typeOfTree", ComparisonOperator.EQUALS, strategy.typeOfTree) + ), + -1 + ) + + override fun loadByVerboseName(verboseName: String): TreeType? { + val tree = findByVerboseName(verboseName).singleOrNull()?.let { + strategy.createTree().apply { + root = it.root?.deserialize() + } + } + return tree + } + + override fun deleteByVerboseName(verboseName: String) { + session.query( + "MATCH toDelete=(" + + "t:SerializableTreeEntity {typeOfTree: \$typeOfTree, verboseName : \$verboseName}" + + ")-[*0..]->() DETACH DELETE toDelete", + mapOf("typeOfTree" to strategy.typeOfTree, "verboseName" to verboseName) + ) + } + + private fun SerializableNodeEntity.deserialize(parent: NodeType? = null): NodeType? { + val node = strategy.createNode(this.toSerializableNode()) + node?.parent = parent + node?.left = left?.deserialize(node) + node?.right = right?.deserialize(node) + return node + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/Repository.kt b/app/src/main/kotlin/repo/Repository.kt new file mode 100644 index 0000000..0349d46 --- /dev/null +++ b/app/src/main/kotlin/repo/Repository.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo + +import app.trees.ABSTree +import app.trees.nodes.MyNode +import repo.serialization.SerializableNode +import repo.serialization.SerializableTree +import repo.serialization.strategies.Serialization + +abstract class Repository, + NodeType : MyNode, + TreeType : ABSTree>( + protected val strategy: Serialization +) { + protected fun NodeType.toSerializableNode(): SerializableNode { + return SerializableNode( + strategy.serializeValue(this.data), + strategy.serializeMetadata(this), + left?.toSerializableNode(), + right?.toSerializableNode(), + parent?.toSerializableNode(), + ) + } + + + protected fun TreeType.toSerializableTree(verboseName: String): SerializableTree { + return SerializableTree( + verboseName = verboseName, + typeOfTree = strategy.typeOfTree, + root = this.root?.toSerializableNode(), + ) + } + + abstract fun save(verboseName: String, tree: TreeType) + abstract fun loadByVerboseName(verboseName: String): TreeType? + abstract fun deleteByVerboseName(verboseName: String) +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/serialization/Serializable.kt b/app/src/main/kotlin/repo/serialization/Serializable.kt new file mode 100644 index 0000000..ef81eee --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/Serializable.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization + +import kotlinx.serialization.Serializable + +@Serializable +enum class TypeOfTree(val savingName: String) { + BINARY_SEARCH_TREE("BINARY_SEARCH_TREE"), + RED_BLACK_TREE("RED_BLACK_TREE"), + AVL_TREE("AVL_TREE") +} + +@Serializable +class SerializableNode( + val value: SerializableValue, + val metadata: Metadata, + val left: SerializableNode? = null, + val right: SerializableNode? = null, + val parent: SerializableNode? = null, +) + +@Serializable +class SerializableTree( + val verboseName: String, + val typeOfTree: TypeOfTree, + val root: SerializableNode?, +) + +@Serializable +@JvmInline +value class Metadata(val value: String) + +@Serializable +@JvmInline +value class SerializableValue(val value: String) \ No newline at end of file diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt new file mode 100644 index 0000000..53195bd --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization.neo4jEntities + +import org.neo4j.ogm.annotation.* +import repo.serialization.Metadata +import repo.serialization.SerializableValue + +@NodeEntity +class SerializableNodeEntity( + @Property + var value: SerializableValue, + + @Property + var metadata: Metadata, + + @Relationship(type = "LEFT", direction = Relationship.Direction.OUTGOING) + var left: SerializableNodeEntity? = null, + + @Relationship(type = "RIGHT", direction = Relationship.Direction.OUTGOING) + var right: SerializableNodeEntity? = null, +) { + @Id + @GeneratedValue + var id: Long? = null +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt new file mode 100644 index 0000000..07fd71c --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization.neo4jEntities + +import org.neo4j.ogm.annotation.* +import repo.serialization.TypeOfTree + +@NodeEntity +class SerializableTreeEntity( + @Property + var verboseName: String, + + @Property + var typeOfTree: TypeOfTree, + + @Relationship(type = "ROOT") + var root: SerializableNodeEntity? = null, +) { + + @Id + @GeneratedValue + var id: Long? = null + +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt b/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt new file mode 100644 index 0000000..a11f2c9 --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization.strategies + +import app.trees.nodes.AVLNode +import app.trees.trees.AVLTree +import repo.serialization.Metadata +import repo.serialization.SerializableNode +import repo.serialization.SerializableValue +import repo.serialization.TypeOfTree + +class AVLStrategy>( + serializeValue: (T) -> SerializableValue, + deserializeValue: (SerializableValue) -> T +) : Serialization, AVLTree, Int>(serializeValue, deserializeValue) { + override val typeOfTree: TypeOfTree = TypeOfTree.AVL_TREE + + override fun createNode(node: SerializableNode?): AVLNode? = node?.let { + AVLNode( + data = deserializeValue(node.value), + left = createNode(node.left), + right = createNode(node.right), + height = deserializeMetadata(node.metadata), + ) + } + + override fun deserializeMetadata(metadata: Metadata) = metadata.value.toInt() + + override fun serializeMetadata(node: AVLNode) = Metadata(node.height.toString()) + + override fun createTree() = AVLTree() +} diff --git a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt new file mode 100644 index 0000000..370a339 --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization.strategies + +import app.trees.ABSTree +import app.trees.nodes.MyNode +import repo.serialization.Metadata +import repo.serialization.SerializableNode +import repo.serialization.SerializableValue +import repo.serialization.TypeOfTree + + +abstract class Serialization< + E : Comparable, + NodeType : MyNode, + TreeType : ABSTree, + M, + >( + val serializeValue: (E) -> SerializableValue, + val deserializeValue: (SerializableValue) -> E, +) { + abstract val typeOfTree: TypeOfTree + abstract fun createNode(node: SerializableNode?): NodeType? + abstract fun deserializeMetadata(metadata: Metadata): M + abstract fun serializeMetadata(node: NodeType): Metadata + abstract fun createTree(): TreeType +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4ee7854 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.9' + +services: + neo4j: + image: neo4j:latest + container_name: neo4j + ports: + - "7474:7474" + - "7687:7687" + environment: + - NEO4J_AUTH=neo4j/neo4j \ No newline at end of file From 1323217b45b64ad84dc6bace829eeefa1d835c71 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 05:57:45 +0300 Subject: [PATCH 58/90] fix: -fix noarg constructor -fix docker-compose.yml -fix load tree from neo4j -delete parent in SerializableNode -add properties in Neo4j entities --- app/build.gradle.kts | 26 ++++++++++++------- app/src/main/kotlin/repo/Neo4jRepository.kt | 23 ++++++++-------- app/src/main/kotlin/repo/Repository.kt | 1 - .../kotlin/repo/serialization/Serializable.kt | 9 +++---- .../neo4jEntities/SerializableNodeEntity.kt | 6 ++--- .../neo4jEntities/SerializableTreeEntity.kt | 12 ++++----- docker-compose.yml | 4 +-- 7 files changed, 42 insertions(+), 39 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 11810fa..a4f843b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,31 +14,37 @@ plugins { // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. id("org.jetbrains.kotlin.jvm") version "1.8.10" + id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" kotlin("plugin.serialization") version "1.8.20" - // Apply the application plugin to add support for building a CLI application in Java. application } + repositories { // Use Maven Central for resolving dependencies. mavenCentral() } dependencies { - // Use the Kotlin JUnit 5 integration. - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") - - // Use the JUnit 5 integration. - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") - - // This dependency is used by the application. - implementation("com.google.guava:guava:31.1-jre") - testImplementation("org.testng:testng:7.7.0") + testImplementation(kotlin("test")) + testImplementation(platform("org.junit:junit-bom:5.9.2")) + testImplementation("org.junit.jupiter:junit-jupiter") implementation("org.neo4j:neo4j-ogm-core:4.0.5") runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") +} + +tasks.jar { + manifest.attributes["Main-Class"] = "app.AppKt" +} +noArg { + annotation("org.neo4j.ogm.annotation.NodeEntity") + annotation("org.neo4j.ogm.annotation.RelationshipEntity") +} +kotlin { + jvmToolchain(8) } application { diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt index 036cc33..e41d497 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -18,13 +18,15 @@ import repo.serialization.neo4jEntities.SerializableNodeEntity import repo.serialization.neo4jEntities.SerializableTreeEntity import repo.serialization.strategies.Serialization -class Neo4jRepo, +class Neo4jRepo< + E : Comparable, NodeType : MyNode, - TreeType : ABSTree>( + TreeType : ABSTree, + >( strategy: Serialization, configuration: Configuration ) : Repository(strategy) { - private val sessionFactory = SessionFactory(configuration, "app.model.repo") + private val sessionFactory = SessionFactory(configuration, "repo") private val session = sessionFactory.openSession() private fun SerializableNodeEntity.toSerializableNode(): SerializableNode { @@ -38,7 +40,7 @@ class Neo4jRepo, private fun SerializableTreeEntity.toTree(): SerializableTree { return SerializableTree( - verboseName, + name, typeOfTree, root?.toSerializableNode(), ) @@ -70,20 +72,19 @@ class Neo4jRepo, private fun findByVerboseName(verboseName: String) = session.loadAll( SerializableTreeEntity::class.java, Filters().and( - Filter("verboseName", ComparisonOperator.EQUALS, verboseName) + Filter("name", ComparisonOperator.EQUALS, verboseName) ).and( Filter("typeOfTree", ComparisonOperator.EQUALS, strategy.typeOfTree) ), -1 ) - override fun loadByVerboseName(verboseName: String): TreeType? { - val tree = findByVerboseName(verboseName).singleOrNull()?.let { - strategy.createTree().apply { - root = it.root?.deserialize() - } + override fun loadByVerboseName(verboseName: String): TreeType { + val tree = findByVerboseName(verboseName).singleOrNull() + val result = strategy.createTree().apply { + root = tree?.root?.deserialize() } - return tree + return result } override fun deleteByVerboseName(verboseName: String) { diff --git a/app/src/main/kotlin/repo/Repository.kt b/app/src/main/kotlin/repo/Repository.kt index 0349d46..853d75c 100644 --- a/app/src/main/kotlin/repo/Repository.kt +++ b/app/src/main/kotlin/repo/Repository.kt @@ -22,7 +22,6 @@ abstract class Repository, strategy.serializeMetadata(this), left?.toSerializableNode(), right?.toSerializableNode(), - parent?.toSerializableNode(), ) } diff --git a/app/src/main/kotlin/repo/serialization/Serializable.kt b/app/src/main/kotlin/repo/serialization/Serializable.kt index ef81eee..18d32da 100644 --- a/app/src/main/kotlin/repo/serialization/Serializable.kt +++ b/app/src/main/kotlin/repo/serialization/Serializable.kt @@ -8,10 +8,10 @@ package repo.serialization import kotlinx.serialization.Serializable @Serializable -enum class TypeOfTree(val savingName: String) { - BINARY_SEARCH_TREE("BINARY_SEARCH_TREE"), - RED_BLACK_TREE("RED_BLACK_TREE"), - AVL_TREE("AVL_TREE") +enum class TypeOfTree { + BINARY_SEARCH_TREE, + RED_BLACK_TREE, + AVL_TREE } @Serializable @@ -20,7 +20,6 @@ class SerializableNode( val metadata: Metadata, val left: SerializableNode? = null, val right: SerializableNode? = null, - val parent: SerializableNode? = null, ) @Serializable diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt index 53195bd..9e9e879 100644 --- a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt +++ b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt @@ -9,12 +9,12 @@ import org.neo4j.ogm.annotation.* import repo.serialization.Metadata import repo.serialization.SerializableValue -@NodeEntity +@NodeEntity("Node") class SerializableNodeEntity( - @Property + @Property("data") var value: SerializableValue, - @Property + @Property("metadata") var metadata: Metadata, @Relationship(type = "LEFT", direction = Relationship.Direction.OUTGOING) diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt index 07fd71c..0efb237 100644 --- a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt +++ b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt @@ -8,16 +8,16 @@ package repo.serialization.neo4jEntities import org.neo4j.ogm.annotation.* import repo.serialization.TypeOfTree -@NodeEntity +@NodeEntity("Tree") class SerializableTreeEntity( - @Property - var verboseName: String, + @Property("name") + var name: String, - @Property + @Property("typeOfTree") var typeOfTree: TypeOfTree, - @Relationship(type = "ROOT") - var root: SerializableNodeEntity? = null, + @Relationship(type = "ROOT", direction = Relationship.Direction.OUTGOING) + var root: SerializableNodeEntity?, ) { @Id diff --git a/docker-compose.yml b/docker-compose.yml index 4ee7854..4e4f4be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,4 @@ services: container_name: neo4j ports: - "7474:7474" - - "7687:7687" - environment: - - NEO4J_AUTH=neo4j/neo4j \ No newline at end of file + - "7687:7687" \ No newline at end of file From 754cf9c005583d32064c6e49071185b226b3a399 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 08:49:48 +0300 Subject: [PATCH 59/90] refactor: standardize the names of functions and fields in classes --- app/src/main/kotlin/repo/Neo4jRepository.kt | 36 +++++++++---------- app/src/main/kotlin/repo/Repository.kt | 18 +++++----- .../kotlin/repo/serialization/Serializable.kt | 4 +-- .../neo4jEntities/SerializableNodeEntity.kt | 2 +- .../serialization/strategies/AVLStrategy.kt | 8 ++--- .../serialization/strategies/Serialization.kt | 10 +++--- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt index e41d497..3e11f5a 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -19,19 +19,19 @@ import repo.serialization.neo4jEntities.SerializableTreeEntity import repo.serialization.strategies.Serialization class Neo4jRepo< - E : Comparable, - NodeType : MyNode, - TreeType : ABSTree, + T : Comparable, + NodeType : MyNode, + TreeType : ABSTree, >( - strategy: Serialization, + strategy: Serialization, configuration: Configuration -) : Repository(strategy) { +) : Repository(strategy) { private val sessionFactory = SessionFactory(configuration, "repo") private val session = sessionFactory.openSession() private fun SerializableNodeEntity.toSerializableNode(): SerializableNode { return SerializableNode( - value, + data, metadata, left?.toSerializableNode(), right?.toSerializableNode(), @@ -48,7 +48,7 @@ class Neo4jRepo< private fun SerializableNode.toEntity(): SerializableNodeEntity { return SerializableNodeEntity( - value, + data, metadata, left?.toEntity(), right?.toEntity(), @@ -57,42 +57,42 @@ class Neo4jRepo< private fun SerializableTree.toEntity(): SerializableTreeEntity { return SerializableTreeEntity( - verboseName, + name, typeOfTree, root?.toEntity(), ) } - override fun save(verboseName: String, tree: TreeType) { - deleteByVerboseName(verboseName) - val entityTree = tree.toSerializableTree(verboseName).toEntity() + override fun save(name: String, tree: TreeType) { + deleteByName(name) + val entityTree = tree.toSerializableTree(name).toEntity() session.save(entityTree) } - private fun findByVerboseName(verboseName: String) = session.loadAll( + private fun findByVerboseName(name: String) = session.loadAll( SerializableTreeEntity::class.java, Filters().and( - Filter("name", ComparisonOperator.EQUALS, verboseName) + Filter("name", ComparisonOperator.EQUALS, name) ).and( Filter("typeOfTree", ComparisonOperator.EQUALS, strategy.typeOfTree) ), -1 ) - override fun loadByVerboseName(verboseName: String): TreeType { - val tree = findByVerboseName(verboseName).singleOrNull() + override fun loadByName(name: String): TreeType { + val tree = findByVerboseName(name).singleOrNull() val result = strategy.createTree().apply { root = tree?.root?.deserialize() } return result } - override fun deleteByVerboseName(verboseName: String) { + override fun deleteByName(name: String) { session.query( "MATCH toDelete=(" + - "t:SerializableTreeEntity {typeOfTree: \$typeOfTree, verboseName : \$verboseName}" + + "t:SerializableTreeEntity {typeOfTree: \$typeOfTree, name : \$name}" + ")-[*0..]->() DETACH DELETE toDelete", - mapOf("typeOfTree" to strategy.typeOfTree, "verboseName" to verboseName) + mapOf("typeOfTree" to strategy.typeOfTree, "name" to name) ) } diff --git a/app/src/main/kotlin/repo/Repository.kt b/app/src/main/kotlin/repo/Repository.kt index 853d75c..8e89f3d 100644 --- a/app/src/main/kotlin/repo/Repository.kt +++ b/app/src/main/kotlin/repo/Repository.kt @@ -11,10 +11,10 @@ import repo.serialization.SerializableNode import repo.serialization.SerializableTree import repo.serialization.strategies.Serialization -abstract class Repository, - NodeType : MyNode, - TreeType : ABSTree>( - protected val strategy: Serialization +abstract class Repository, + NodeType : MyNode, + TreeType : ABSTree>( + protected val strategy: Serialization ) { protected fun NodeType.toSerializableNode(): SerializableNode { return SerializableNode( @@ -26,15 +26,15 @@ abstract class Repository, } - protected fun TreeType.toSerializableTree(verboseName: String): SerializableTree { + protected fun TreeType.toSerializableTree(name: String): SerializableTree { return SerializableTree( - verboseName = verboseName, + name = name, typeOfTree = strategy.typeOfTree, root = this.root?.toSerializableNode(), ) } - abstract fun save(verboseName: String, tree: TreeType) - abstract fun loadByVerboseName(verboseName: String): TreeType? - abstract fun deleteByVerboseName(verboseName: String) + abstract fun save(name: String, tree: TreeType) + abstract fun loadByName(name: String): TreeType? + abstract fun deleteByName(name: String) } \ No newline at end of file diff --git a/app/src/main/kotlin/repo/serialization/Serializable.kt b/app/src/main/kotlin/repo/serialization/Serializable.kt index 18d32da..a4a47d4 100644 --- a/app/src/main/kotlin/repo/serialization/Serializable.kt +++ b/app/src/main/kotlin/repo/serialization/Serializable.kt @@ -16,7 +16,7 @@ enum class TypeOfTree { @Serializable class SerializableNode( - val value: SerializableValue, + val data: SerializableValue, val metadata: Metadata, val left: SerializableNode? = null, val right: SerializableNode? = null, @@ -24,7 +24,7 @@ class SerializableNode( @Serializable class SerializableTree( - val verboseName: String, + val name: String, val typeOfTree: TypeOfTree, val root: SerializableNode?, ) diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt index 9e9e879..f2a7bbd 100644 --- a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt +++ b/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt @@ -12,7 +12,7 @@ import repo.serialization.SerializableValue @NodeEntity("Node") class SerializableNodeEntity( @Property("data") - var value: SerializableValue, + var data: SerializableValue, @Property("metadata") var metadata: Metadata, diff --git a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt b/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt index a11f2c9..128019a 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt +++ b/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt @@ -13,14 +13,14 @@ import repo.serialization.SerializableValue import repo.serialization.TypeOfTree class AVLStrategy>( - serializeValue: (T) -> SerializableValue, - deserializeValue: (SerializableValue) -> T -) : Serialization, AVLTree, Int>(serializeValue, deserializeValue) { + serializeData: (T) -> SerializableValue, + deserializeData: (SerializableValue) -> T +) : Serialization, AVLTree, Int>(serializeData, deserializeData) { override val typeOfTree: TypeOfTree = TypeOfTree.AVL_TREE override fun createNode(node: SerializableNode?): AVLNode? = node?.let { AVLNode( - data = deserializeValue(node.value), + data = deserializeValue(node.data), left = createNode(node.left), right = createNode(node.right), height = deserializeMetadata(node.metadata), diff --git a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt index 370a339..2c64346 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt +++ b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt @@ -14,13 +14,13 @@ import repo.serialization.TypeOfTree abstract class Serialization< - E : Comparable, - NodeType : MyNode, - TreeType : ABSTree, + T : Comparable, + NodeType : MyNode, + TreeType : ABSTree, M, >( - val serializeValue: (E) -> SerializableValue, - val deserializeValue: (SerializableValue) -> E, + val serializeValue: (T) -> SerializableValue, + val deserializeValue: (SerializableValue) -> T, ) { abstract val typeOfTree: TypeOfTree abstract fun createNode(node: SerializableNode?): NodeType? From 0c36518f1acc82e5b3047c4c692e8ef33176f7c0 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 08:50:41 +0300 Subject: [PATCH 60/90] feat: -change README.md -add DOCS.md --- DOCS.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 29 +++++++------- 2 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 DOCS.md diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..032c0a4 --- /dev/null +++ b/DOCS.md @@ -0,0 +1,114 @@ +## Getting started + +To build the library run + +```bash + ./gradlew build +``` + +## Using Trees + +Any `Comparable` data can be stored in trees. +We also provide access to the `KeyValue` class, which allows you to store a key-value pair in the nodes of the tree. + +```kotlin +import app.trees.KeyValue +import app.trees.trees.AVLTree +import app.trees.trees.BSTree +import app.trees.trees.RBTree + +val alvTree = AVLTree() // instantiate empty AVL tree +val bsTree = BSTree() // instantiate empty simple tree +val rbTree = RBTree>() // instantiate empty red-black tree with key-value +``` + +Each tree supports 3 basic operations: `add`, `contain`, `delete` and `get` (if you need to get value by key using +KeyValue) + +```kotlin +avlTree.add(42) +bsTree.add("42") +rbTree.add(KeyValue(42, "42")) +bsTree.contain("42") // returns true +avlTree.contain(1) // returns false +rbTree.get(KeyValue(42, null))?.getValue() // returns "42" +``` + +Trees' nodes can be read-only accessed by `root` property. + +```kotlin +avlTree.add(10) +avlTree.add(5) +avlTree.add(20) +avlTree.add(30) +avlTree.add(42) +// avlTree after balancing: +// 10 +// ┌─────────┴─────────┐ +// 5 30 +// ┌──┴──┐ +// 20 42 + +avlTree.root?.data // 10 +avlTree.root?.left?.data // 5 +avlTree.root?.right?.data // 30 +avlTree.root?.right?.right?.data // 42 +avlTree.root?.right?.left?.data // 20 +``` + +## Storing Trees + +`teemEight` provides ~~`JsonRepository`, `SqlRepository` and~~ `Neo4jRepository` to save & load trees. + +Each instance of repository is used to store exactly 1 tree type. To store different tree types several repositories can +be instantiated. +Repository must be provided with `Serialization` which describes how to serialize & deserialize any particular +type of tree. + +`bstrees` is shipped with `AVLStrategy`, ~~`RBStrategy` and `SimpleStrategy`~~ to serialize & deserialize AVL trees, +~~Red-black trees and Simple BSTs~~ respectively. As these strategies don't know anything about the data type stored in +trees' nodes, user must provide `serializeData` and `deserializeData` functions to them. + +Different tree types can be stored in the same database (directory) by creating several repositories and passing them +same databases (directory paths). + +### Using Neo4j + +Before using this, you must have [Docker](https://www.docker.com/) (also see [docs](https://docs.docker.com/)) + +#### Before started + +1. Run docker container with `docker-compose.yml` +2. Open http://localhost:7474 +3. Create new user (default password: neo4j) +4. Change the password +5. You got this + +Please note that storing 1 tree type (RB, AVL, Simple) with different data type in the same database (directory) is not +supported. + +#### Example + +```kotlin +val username = "neo4j" +val password = "" // insert password to database here +val conf = Configuration.Builder() + .uri("bolt://localhost") + .credentials(username, password) + .build() + +fun serializeInt(data: Int) = SerializableValue(data.toString()) + +fun deserializeInt(data: SerializableValue) = data.value.toInt() + +val avlRepo = Neo4jRepo(AVLStrategy(::serializeInt, ::deserializeInt), conf) +// !!! storing AVLTree and AVLTree in the same db is unsupported + +val tree = AVLTree() +val randomizer = Random(42) +val lst = List(15) { randomizer.nextInt(1000) } +lst.forEach { tree.add(it) } +avlRepo.save("test", tree) +val testTree = avlRepo.loadByName("test") +println(testTree.preOrder()) // output pre-order traversal of tree +``` \ No newline at end of file diff --git a/README.md b/README.md index c44d00f..c56487a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,10 @@ teemEight's project of implementation of three trees: AVL, red-black, binary ## About -This project provides access to three types of binary-search trees: red-black, AVL, binary +It is a library that provides kotlin implementations of 3 binary search trees data +structures: [BS tree](https://en.wikipedia.org/wiki/Binary_search_tree), [AVL tree](https://en.wikipedia.org/wiki/AVL_trees), [Red-black tree](https://en.wikipedia.org/wiki/Red–black_tree). +It also provides storing BSTs in either plain `.json` files, `SQLite` +or `neo4j` databases. ## Roadmap @@ -20,33 +23,31 @@ This project provides access to three types of binary-search trees: red-black, A - [x] CI - [x] Realized all types of trees - [x] Added tests -- [ ] Storing in databases +- Storing with: + - [x] Neo4j + - [ ] Sqlite + - [ ] json - [ ] GUI -## Features - -- add -- delete -- contains -- Cross platform -- Deploy -- Bugs - ## Building To build this project run ```bash - gradle build + ./gradlew build ``` +## How to use + +See official [documentation](/DOCS.md) + ## Feedback If you have any feedback, please reach out to us at Issues -## About Me +## About creators -I'm a full stack developer... (( \ +We're a full stack developers... (( \ 18-20 y.o SPBU SE
From a5afc9e751056d99b594d06ed75c1b77f1340a1c Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:03:02 +0300 Subject: [PATCH 61/90] fix: fix github-actions.yml, set versions in settings.gradle.kts --- .github/workflows/github-actions.yml | 7 ++----- settings.gradle.kts | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 9aa8fbc..8520b63 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -9,7 +9,6 @@ on: jobs: build: runs-on: windows-latest - steps: - uses: actions/checkout@v3 - name: Set up JDK 17 @@ -17,9 +16,7 @@ jobs: with: java-version: '17' distribution: 'temurin' - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b - - name: Build with Gradle - uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + - name: Build & test with Gradle + uses: gradle/gradle-build-action@v2 with: arguments: build \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e26452f..a6f8ea0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + /* * This file was generated by the Gradle 'init' task. * @@ -9,3 +14,19 @@ rootProject.name = "trees-8" include("app") + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + version("kotlin", "1.8.20") + plugin("kotlin-jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin") + plugin("kotlin-serialization", "org.jetbrains.kotlin.plugin.serialization").versionRef("kotlin") + plugin("kotlin-noarg", "org.jetbrains.kotlin.plugin.noarg").versionRef("kotlin") + library("kotlinx-serialization-json", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + version("neo4j-ogm", "4.0.5") + library("neo4j-ogm-core", "org.neo4j", "neo4j-ogm-core").versionRef("neo4j-ogm") + library("neo4j-ogm-bolt", "org.neo4j", "neo4j-ogm-bolt-driver").versionRef("neo4j-ogm") + library("junit-jupiter", "org.junit.jupiter:junit-jupiter:5.9.2") + } + } +} \ No newline at end of file From 40949335359b08ffd7c8e8ea97e01fc9bd3815fc Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:11:29 +0300 Subject: [PATCH 62/90] fix: fix settings.gradle.kts --- app/build.gradle.kts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a4f843b..92b3437 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,10 +43,6 @@ noArg { annotation("org.neo4j.ogm.annotation.RelationshipEntity") } -kotlin { - jvmToolchain(8) -} - application { // Define the main class for the application. mainClass.set("MainKt") From efcb5dd559fbd2b55035335db74b31b9c010ddab Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+roketflame@users.noreply.github.com> Date: Sun, 23 Apr 2023 09:04:26 +0300 Subject: [PATCH 63/90] struct: move classes of trees in root refactor: rename some methods in AbstractTree fix: delete useless methods in classes of nodes --- DOCS.md | 2 +- app/src/main/kotlin/Main.kt | 2 +- .../kotlin/app/trees/{trees => }/AVLTree.kt | 15 ++++--- .../app/trees/{ABSTree.kt => AbstractTree.kt} | 24 +++++------ app/src/main/kotlin/app/trees/BSTree.kt | 29 +++++++++++++ app/src/main/kotlin/app/trees/KeyValue.kt | 14 +++---- .../kotlin/app/trees/{trees => }/RBTree.kt | 41 +++++++++---------- .../main/kotlin/app/trees/interfaces/Tree.kt | 2 +- .../main/kotlin/app/trees/nodes/AVLNode.kt | 16 +------- .../nodes/{MyNode.kt => AbstractNode.kt} | 8 +++- app/src/main/kotlin/app/trees/nodes/BSNode.kt | 21 +--------- app/src/main/kotlin/app/trees/nodes/RBNode.kt | 18 +------- app/src/main/kotlin/app/trees/trees/BSTree.kt | 30 -------------- app/src/main/kotlin/repo/Neo4jRepository.kt | 8 ++-- app/src/main/kotlin/repo/Repository.kt | 8 ++-- .../serialization/strategies/AVLStrategy.kt | 2 +- .../serialization/strategies/Serialization.kt | 8 ++-- app/src/test/kotlin/trees/AVLTreeTest.kt | 22 +++++----- app/src/test/kotlin/trees/BSTreeTest.kt | 16 ++++---- .../{InvariantTest.kt => InvariantChecker.kt} | 4 +- app/src/test/kotlin/trees/RBTreeTest.kt | 28 ++++++------- 21 files changed, 135 insertions(+), 183 deletions(-) rename app/src/main/kotlin/app/trees/{trees => }/AVLTree.kt (80%) rename app/src/main/kotlin/app/trees/{ABSTree.kt => AbstractTree.kt} (81%) create mode 100644 app/src/main/kotlin/app/trees/BSTree.kt rename app/src/main/kotlin/app/trees/{trees => }/RBTree.kt (95%) rename app/src/main/kotlin/app/trees/nodes/{MyNode.kt => AbstractNode.kt} (78%) delete mode 100644 app/src/main/kotlin/app/trees/trees/BSTree.kt rename app/src/test/kotlin/trees/{InvariantTest.kt => InvariantChecker.kt} (98%) diff --git a/DOCS.md b/DOCS.md index 032c0a4..b4776e4 100644 --- a/DOCS.md +++ b/DOCS.md @@ -12,10 +12,10 @@ Any `Comparable` data can be stored in trees. We also provide access to the `KeyValue` class, which allows you to store a key-value pair in the nodes of the tree. ```kotlin -import app.trees.KeyValue import app.trees.trees.AVLTree import app.trees.trees.BSTree import app.trees.trees.RBTree +import app.trees.KeyValue val alvTree = AVLTree() // instantiate empty AVL tree val bsTree = BSTree() // instantiate empty simple tree diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt index 1283e02..7a5c6e9 100644 --- a/app/src/main/kotlin/Main.kt +++ b/app/src/main/kotlin/Main.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import app.trees.trees.AVLTree +import app.trees.AVLTree import kotlin.random.Random fun main(args: Array) { diff --git a/app/src/main/kotlin/app/trees/trees/AVLTree.kt b/app/src/main/kotlin/app/trees/AVLTree.kt similarity index 80% rename from app/src/main/kotlin/app/trees/trees/AVLTree.kt rename to app/src/main/kotlin/app/trees/AVLTree.kt index 84d138a..505a9cf 100644 --- a/app/src/main/kotlin/app/trees/trees/AVLTree.kt +++ b/app/src/main/kotlin/app/trees/AVLTree.kt @@ -3,25 +3,24 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.trees +package app.trees -import app.trees.ABSTree import app.trees.nodes.AVLNode -class AVLTree> : ABSTree>() { +class AVLTree> : AbstractTree>() { override fun add(data: T) { - root = simpleAdd(root, AVLNode(data)) + root = balancedAdd(root, AVLNode(data)) root?.updateHeight() root?.parent = null } - override fun contain(data: T): Boolean { - return (simpleContains(root, AVLNode(data)) != null) + override fun contains(data: T): Boolean { + return (contains(root, AVLNode(data)) != null) } override fun delete(data: T) { - root = simpleDelete(root, AVLNode(data)) + root = balancedDelete(root, AVLNode(data)) root?.updateHeight() root?.parent = null } @@ -56,7 +55,7 @@ class AVLTree> : ABSTree>() { } fun get(data: T): T? { - return simpleContains(root, AVLNode(data))?.data + return contains(root, AVLNode(data))?.data } private fun updateChildrenHeight(node: AVLNode?) { diff --git a/app/src/main/kotlin/app/trees/ABSTree.kt b/app/src/main/kotlin/app/trees/AbstractTree.kt similarity index 81% rename from app/src/main/kotlin/app/trees/ABSTree.kt rename to app/src/main/kotlin/app/trees/AbstractTree.kt index 9215166..5d30142 100644 --- a/app/src/main/kotlin/app/trees/ABSTree.kt +++ b/app/src/main/kotlin/app/trees/AbstractTree.kt @@ -6,9 +6,9 @@ package app.trees import app.trees.interfaces.Tree -import app.trees.nodes.MyNode +import app.trees.nodes.AbstractNode -abstract class ABSTree, NodeType : MyNode> : Tree { +abstract class AbstractTree, NodeType : AbstractNode> : Tree { var root: NodeType? = null internal set @@ -16,31 +16,31 @@ abstract class ABSTree, NodeType : MyNode> : Tree return initNode } - protected fun simpleAdd(initNode: NodeType?, node: NodeType): NodeType? { + protected fun balancedAdd(initNode: NodeType?, node: NodeType): NodeType? { if (initNode == null) { return node } if (initNode < node) { - initNode.right = simpleAdd(initNode.right, node) + initNode.right = balancedAdd(initNode.right, node) initNode.right?.parent = initNode } else if (initNode > node) { - initNode.left = simpleAdd(initNode.left, node) + initNode.left = balancedAdd(initNode.left, node) initNode.left?.parent = initNode } return balance(initNode) } - protected fun simpleDelete(initNode: NodeType?, node: NodeType): NodeType? { + protected fun balancedDelete(initNode: NodeType?, node: NodeType): NodeType? { if (initNode == null) { return null } if (initNode < node) { - initNode.right = simpleDelete(initNode.right, node) + initNode.right = balancedDelete(initNode.right, node) initNode.right?.parent = initNode } else if (initNode > node) { - initNode.left = simpleDelete(initNode.left, node) + initNode.left = balancedDelete(initNode.left, node) initNode.left?.parent = initNode } else { if ((initNode.left == null) || (initNode.right == null)) { @@ -49,7 +49,7 @@ abstract class ABSTree, NodeType : MyNode> : Tree initNode.right?.let { val tmp = getMinimal(it) initNode.data = tmp.data - initNode.right = simpleDelete(initNode.right, tmp) + initNode.right = balancedDelete(initNode.right, tmp) initNode.right?.parent = initNode } } @@ -57,15 +57,15 @@ abstract class ABSTree, NodeType : MyNode> : Tree return balance(initNode) } - protected fun simpleContains(initNode: NodeType?, node: NodeType): NodeType? { + protected fun contains(initNode: NodeType?, node: NodeType): NodeType? { if (initNode == null) { return null } return if (initNode < node) { - simpleContains(initNode.right, node) + contains(initNode.right, node) } else if (initNode > node) { - simpleContains(initNode.left, node) + contains(initNode.left, node) } else { initNode } diff --git a/app/src/main/kotlin/app/trees/BSTree.kt b/app/src/main/kotlin/app/trees/BSTree.kt new file mode 100644 index 0000000..afa901c --- /dev/null +++ b/app/src/main/kotlin/app/trees/BSTree.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app.trees + +import app.trees.nodes.BSNode + +class BSTree> : AbstractTree>() { + + + override fun add(data: T) { + root = balancedAdd(root, BSNode(data)) + } + + override fun contains(data: T): Boolean { + return (contains(root, BSNode(data)) != null) + } + + override fun delete(data: T) { + root = balancedDelete(root, BSNode(data)) + root?.parent = null + } + + fun get(data: T): T? { + return contains(root, BSNode(data))?.data + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/KeyValue.kt b/app/src/main/kotlin/app/trees/KeyValue.kt index 88d92f5..65004e4 100644 --- a/app/src/main/kotlin/app/trees/KeyValue.kt +++ b/app/src/main/kotlin/app/trees/KeyValue.kt @@ -5,14 +5,10 @@ package app.trees -class KeyValue, V>(private val key: K, private var value: V?) : Comparable> { - fun getKey(): K { - return key - } - - fun getValue(): V? { - return value - } +class KeyValue, V>( + val key: K, + var value: V +) : Comparable> { override fun compareTo(other: KeyValue): Int { return key.compareTo(other.key) @@ -20,7 +16,7 @@ class KeyValue, V>(private val key: K, private var value: V?) override fun equals(other: Any?): Boolean { if (other is KeyValue<*, *>) { - return key == other.getKey() + return key == other.key } return false } diff --git a/app/src/main/kotlin/app/trees/trees/RBTree.kt b/app/src/main/kotlin/app/trees/RBTree.kt similarity index 95% rename from app/src/main/kotlin/app/trees/trees/RBTree.kt rename to app/src/main/kotlin/app/trees/RBTree.kt index 079b3c3..4d7a565 100644 --- a/app/src/main/kotlin/app/trees/trees/RBTree.kt +++ b/app/src/main/kotlin/app/trees/RBTree.kt @@ -3,28 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.trees +package app.trees -import app.trees.ABSTree import app.trees.nodes.Color import app.trees.nodes.RBNode -class RBTree> : ABSTree>() { - companion object { - @JvmStatic - internal fun > isBlack(node: RBNode?): Boolean { - return ((node == null) || (node.color == Color.BLACK)) - } - - @JvmStatic - internal fun > isRed(node: RBNode?): Boolean { - return node?.color == Color.RED - } - } - +class RBTree> : AbstractTree>() { override fun add(data: T) { val node = RBNode(data) - root = simpleAdd(root, node) + root = balancedAdd(root, node) root = balanceAdd(node, root) root?.parent = null root?.color = Color.BLACK @@ -32,7 +19,7 @@ class RBTree> : ABSTree>() { override fun delete(data: T) { - val node = simpleContains(root, RBNode(data)) ?: return + val node = contains(root, RBNode(data)) ?: return val next: RBNode if ((node.left == null) && (node.right == null)) { @@ -73,8 +60,8 @@ class RBTree> : ABSTree>() { } } - override fun contain(data: T): Boolean { - return (simpleContains(root, RBNode(data)) != null) + override fun contains(data: T): Boolean { + return (contains(root, RBNode(data)) != null) } private fun balanceDelete(node: RBNode?): RBNode? { @@ -180,7 +167,7 @@ class RBTree> : ABSTree>() { } fun get(data: T): T? { - return simpleContains(root, RBNode(data))?.data + return contains(root, RBNode(data))?.data } private fun clearRotateLeft(node: RBNode?, initRoot: RBNode?): RBNode? { @@ -221,4 +208,16 @@ class RBTree> : ABSTree>() { subTree?.parent = parent return newRoot } -} \ No newline at end of file + + companion object { + @JvmStatic + internal fun > isBlack(node: RBNode?): Boolean { + return ((node == null) || (node.color == Color.BLACK)) + } + + @JvmStatic + internal fun > isRed(node: RBNode?): Boolean { + return node?.color == Color.RED + } + } +} diff --git a/app/src/main/kotlin/app/trees/interfaces/Tree.kt b/app/src/main/kotlin/app/trees/interfaces/Tree.kt index 225a0cc..965975c 100644 --- a/app/src/main/kotlin/app/trees/interfaces/Tree.kt +++ b/app/src/main/kotlin/app/trees/interfaces/Tree.kt @@ -7,6 +7,6 @@ package app.trees.interfaces interface Tree> { fun add(data: T) - fun contain(data: T): Boolean + fun contains(data: T): Boolean fun delete(data: T) } \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/nodes/AVLNode.kt b/app/src/main/kotlin/app/trees/nodes/AVLNode.kt index 9a5da65..43b55eb 100644 --- a/app/src/main/kotlin/app/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/AVLNode.kt @@ -11,7 +11,7 @@ data class AVLNode>( override var left: AVLNode? = null, override var right: AVLNode? = null, override var parent: AVLNode? = null, -) : MyNode>() { +) : AbstractNode>() { internal fun updateHeight() { @@ -28,18 +28,4 @@ data class AVLNode>( return (rightNode?.height ?: 0) - (leftNode?.height ?: 0) } - override fun equals(other: Any?): Boolean { - if (other is AVLNode<*>) { - return data == other.data - } - return false - } - - override fun toString(): String { - return "$data" - } - - override fun hashCode(): Int { - return data.hashCode() - } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/nodes/MyNode.kt b/app/src/main/kotlin/app/trees/nodes/AbstractNode.kt similarity index 78% rename from app/src/main/kotlin/app/trees/nodes/MyNode.kt rename to app/src/main/kotlin/app/trees/nodes/AbstractNode.kt index e6cdc89..d22f3e5 100644 --- a/app/src/main/kotlin/app/trees/nodes/MyNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/AbstractNode.kt @@ -10,7 +10,7 @@ package app.trees.nodes/* import app.trees.interfaces.Node -abstract class MyNode, Subtype : MyNode> : Node { +abstract class AbstractNode, Subtype : AbstractNode> : Node { abstract override var data: T internal set abstract override var left: Subtype? @@ -29,9 +29,13 @@ abstract class MyNode, Subtype : MyNode> : Node) { + if (other is AbstractNode<*, *>) { return data == other.data } return false } + + override fun toString(): String { + return "$data" + } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/nodes/BSNode.kt b/app/src/main/kotlin/app/trees/nodes/BSNode.kt index 70e3434..c21971a 100644 --- a/app/src/main/kotlin/app/trees/nodes/BSNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/BSNode.kt @@ -5,26 +5,9 @@ package app.trees.nodes -data class BSNode>( +class BSNode>( override var data: T, override var left: BSNode? = null, override var right: BSNode? = null, override var parent: BSNode? = null, -) : MyNode>() { - - - override fun equals(other: Any?): Boolean { - if (other is BSNode<*>) { - return data == other.data - } - return false - } - - override fun toString(): String { - return "$data" - } - - override fun hashCode(): Int { - return data.hashCode() - } -} \ No newline at end of file +) : AbstractNode>() \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/nodes/RBNode.kt b/app/src/main/kotlin/app/trees/nodes/RBNode.kt index ddd2e1a..338ce8b 100644 --- a/app/src/main/kotlin/app/trees/nodes/RBNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/RBNode.kt @@ -6,8 +6,7 @@ package app.trees.nodes enum class Color { - RED, - BLACK + RED, BLACK } class RBNode>( @@ -16,21 +15,8 @@ class RBNode>( override var left: RBNode? = null, override var right: RBNode? = null, override var parent: RBNode? = null, -) : MyNode>() { - - - override fun equals(other: Any?): Boolean { - if (other is RBNode<*>) { - return data == other.data - } - return false - } - +) : AbstractNode>() { override fun toString(): String { return "$color - $data" } - - override fun hashCode(): Int { - return data.hashCode() - } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/trees/BSTree.kt b/app/src/main/kotlin/app/trees/trees/BSTree.kt deleted file mode 100644 index 37516ba..0000000 --- a/app/src/main/kotlin/app/trees/trees/BSTree.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -package app.trees.trees - -import app.trees.ABSTree -import app.trees.nodes.BSNode - -class BSTree> : ABSTree>() { - - - override fun add(data: T) { - root = simpleAdd(root, BSNode(data)) - } - - override fun contain(data: T): Boolean { - return (simpleContains(root, BSNode(data)) != null) - } - - override fun delete(data: T) { - root = simpleDelete(root, BSNode(data)) - root?.parent = null - } - - fun get(data: T): T? { - return simpleContains(root, BSNode(data))?.data - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt index 3e11f5a..9697ed1 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -5,8 +5,8 @@ package repo -import app.trees.ABSTree -import app.trees.nodes.MyNode +import app.trees.AbstractTree +import app.trees.nodes.AbstractNode import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.cypher.ComparisonOperator import org.neo4j.ogm.cypher.Filter @@ -20,8 +20,8 @@ import repo.serialization.strategies.Serialization class Neo4jRepo< T : Comparable, - NodeType : MyNode, - TreeType : ABSTree, + NodeType : AbstractNode, + TreeType : AbstractTree, >( strategy: Serialization, configuration: Configuration diff --git a/app/src/main/kotlin/repo/Repository.kt b/app/src/main/kotlin/repo/Repository.kt index 8e89f3d..c1e666c 100644 --- a/app/src/main/kotlin/repo/Repository.kt +++ b/app/src/main/kotlin/repo/Repository.kt @@ -5,15 +5,15 @@ package repo -import app.trees.ABSTree -import app.trees.nodes.MyNode +import app.trees.AbstractTree +import app.trees.nodes.AbstractNode import repo.serialization.SerializableNode import repo.serialization.SerializableTree import repo.serialization.strategies.Serialization abstract class Repository, - NodeType : MyNode, - TreeType : ABSTree>( + NodeType : AbstractNode, + TreeType : AbstractTree>( protected val strategy: Serialization ) { protected fun NodeType.toSerializableNode(): SerializableNode { diff --git a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt b/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt index 128019a..bf00f3a 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt +++ b/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt @@ -5,8 +5,8 @@ package repo.serialization.strategies +import app.trees.AVLTree import app.trees.nodes.AVLNode -import app.trees.trees.AVLTree import repo.serialization.Metadata import repo.serialization.SerializableNode import repo.serialization.SerializableValue diff --git a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt index 2c64346..1e046d6 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt +++ b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt @@ -5,8 +5,8 @@ package repo.serialization.strategies -import app.trees.ABSTree -import app.trees.nodes.MyNode +import app.trees.AbstractTree +import app.trees.nodes.AbstractNode import repo.serialization.Metadata import repo.serialization.SerializableNode import repo.serialization.SerializableValue @@ -15,8 +15,8 @@ import repo.serialization.TypeOfTree abstract class Serialization< T : Comparable, - NodeType : MyNode, - TreeType : ABSTree, + NodeType : AbstractNode, + TreeType : AbstractTree, M, >( val serializeValue: (T) -> SerializableValue, diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index f2111ce..a5c5c0a 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -5,8 +5,8 @@ package trees +import app.trees.AVLTree import app.trees.nodes.AVLNode -import app.trees.trees.AVLTree import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test @@ -33,10 +33,10 @@ class AVLTreeTest { fun `check invariant while adding`() { for (value in values) { tree.add(value) - assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height, value: $value") - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue(InvariantChecker.checkHeightAVL(tree.root), "Failed invariant, incorrect height, value: $value") + assertTrue(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") assertTrue( - InvariantTest.checkLinksToParent(tree.root), + InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link, value: $value" ) } @@ -50,10 +50,10 @@ class AVLTreeTest { values.shuffle(randomizer) for (value in values) { tree.delete(value) - assertTrue(InvariantTest.checkHeightAVL(tree.root), "Failed invariant, incorrect height, value: $value") - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue(InvariantChecker.checkHeightAVL(tree.root), "Failed invariant, incorrect height, value: $value") + assertTrue(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") assertTrue( - InvariantTest.checkLinksToParent(tree.root), + InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link, value: $value" ) } @@ -66,16 +66,16 @@ class AVLTreeTest { tree.add(5) tree.root?.left?.left = AVLNode(100) tree.root?.left?.left?.left = AVLNode(150) - assertFalse(InvariantTest.checkHeightAVL(tree.root)) - assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertFalse(InvariantChecker.checkHeightAVL(tree.root)) + assertFalse(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertFalse(InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } @Test fun `check for the presence of elements`() { for (value in values) { tree.add(value) - assertTrue(tree.contain(value)) + assertTrue(tree.contains(value)) } } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 9684d83..8d110dc 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -5,8 +5,8 @@ package trees +import app.trees.BSTree import app.trees.nodes.BSNode -import app.trees.trees.BSTree import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test @@ -33,9 +33,9 @@ class BSTreeTest { fun `check invariant while adding`() { for (value in values) { tree.add(value) - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") assertTrue( - InvariantTest.checkLinksToParent(tree.root), + InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link, value: $value" ) } @@ -49,9 +49,9 @@ class BSTreeTest { values.shuffle(randomizer) for (value in values) { tree.delete(value) - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") assertTrue( - InvariantTest.checkLinksToParent(tree.root), + InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link, value: $value" ) } @@ -62,15 +62,15 @@ class BSTreeTest { tree.add(10) tree.root?.left = BSNode(15) tree.root?.right = BSNode(5) - assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertFalse(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertFalse(InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } @Test fun `check for the presence of elements`() { for (value in values) { tree.add(value) - assertTrue(tree.contain(value)) + assertTrue(tree.contains(value)) } } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/InvariantTest.kt b/app/src/test/kotlin/trees/InvariantChecker.kt similarity index 98% rename from app/src/test/kotlin/trees/InvariantTest.kt rename to app/src/test/kotlin/trees/InvariantChecker.kt index c50d949..1aa7678 100644 --- a/app/src/test/kotlin/trees/InvariantTest.kt +++ b/app/src/test/kotlin/trees/InvariantChecker.kt @@ -8,14 +8,14 @@ */ package trees +import app.trees.RBTree import app.trees.interfaces.Node import app.trees.nodes.AVLNode import app.trees.nodes.Color import app.trees.nodes.RBNode -import app.trees.trees.RBTree import kotlin.math.abs -object InvariantTest { +object InvariantChecker { fun , NodeType : Node> checkLinksToParent(node: Node?): Boolean { if (node == null) return true var result = true diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt index 3f930b6..2dcb13b 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -6,9 +6,9 @@ package trees +import app.trees.RBTree import app.trees.nodes.Color import app.trees.nodes.RBNode -import app.trees.trees.RBTree import kotlin.random.Random import kotlin.test.BeforeTest import kotlin.test.Test @@ -35,16 +35,16 @@ class RBTreeTest { for (value in values) { tree.add(value) assertTrue( - InvariantTest.checkBlackHeight(tree.root), + InvariantChecker.checkBlackHeight(tree.root), "Failed invariant, incorrect black height, value: $value" ) assertTrue( - InvariantTest.checkRedParent(tree.root), + InvariantChecker.checkRedParent(tree.root), "Failed invariant, incorrect color for node, value: $value" ) - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") assertTrue( - InvariantTest.checkLinksToParent(tree.root), + InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link, value: $value" ) } @@ -59,16 +59,16 @@ class RBTreeTest { for (value in values) { tree.delete(value) assertTrue( - InvariantTest.checkBlackHeight(tree.root), + InvariantChecker.checkBlackHeight(tree.root), "Failed invariant, incorrect black height, value: $value" ) assertTrue( - InvariantTest.checkRedParent(tree.root), + InvariantChecker.checkRedParent(tree.root), "Failed invariant, incorrect color for node, value: $value" ) - assertTrue(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") + assertTrue(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data, value: $value") assertTrue( - InvariantTest.checkLinksToParent(tree.root), + InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link, value: $value" ) } @@ -81,17 +81,17 @@ class RBTreeTest { tree.root?.right = RBNode(5) tree.root?.right?.color = Color.BLACK tree.root?.color = Color.RED - assertFalse(InvariantTest.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") - assertFalse(InvariantTest.checkRedParent(tree.root), "Failed invariant, incorrect color for node") - assertFalse(InvariantTest.checkDataInNodes(tree.root), "Failed invariant, incorrect data") - assertFalse(InvariantTest.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") + assertFalse(InvariantChecker.checkBlackHeight(tree.root), "Failed invariant, incorrect black height") + assertFalse(InvariantChecker.checkRedParent(tree.root), "Failed invariant, incorrect color for node") + assertFalse(InvariantChecker.checkDataInNodes(tree.root), "Failed invariant, incorrect data") + assertFalse(InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } @Test fun `check for the presence of elements`() { for (value in values) { tree.add(value) - assertTrue(tree.contain(value)) + assertTrue(tree.contains(value)) } } } \ No newline at end of file From 020f46d097be0833bbdc356c1c44edd045a8067c Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sun, 23 Apr 2023 09:10:08 +0300 Subject: [PATCH 64/90] test: add test for deleting --- app/src/test/kotlin/trees/AVLTreeTest.kt | 12 ++++++++---- app/src/test/kotlin/trees/BSTreeTest.kt | 11 +++++++---- app/src/test/kotlin/trees/RBTreeTest.kt | 11 +++++++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index a5c5c0a..5bd5875 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -8,10 +8,7 @@ package trees import app.trees.AVLTree import app.trees.nodes.AVLNode import kotlin.random.Random -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* class AVLTreeTest { @@ -71,6 +68,13 @@ class AVLTreeTest { assertFalse(InvariantChecker.checkLinksToParent(tree.root), "Failed invariant, incorrect parent's link") } + @Test + fun `deleting from empty tree without exceptions`() { + values.forEach { tree.delete(it) } + assertNull(tree.root, "Root should be null") + } + + @Test fun `check for the presence of elements`() { for (value in values) { diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 8d110dc..1b6b2a6 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -8,10 +8,7 @@ package trees import app.trees.BSTree import app.trees.nodes.BSNode import kotlin.random.Random -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* class BSTreeTest { @@ -73,4 +70,10 @@ class BSTreeTest { assertTrue(tree.contains(value)) } } + + @Test + fun `deleting from empty tree without exceptions`() { + values.forEach { tree.delete(it) } + assertNull(tree.root, "Root should be null") + } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt index 2dcb13b..0fc1e46 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -10,10 +10,7 @@ import app.trees.RBTree import app.trees.nodes.Color import app.trees.nodes.RBNode import kotlin.random.Random -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* class RBTreeTest { companion object { @@ -94,4 +91,10 @@ class RBTreeTest { assertTrue(tree.contains(value)) } } + + @Test + fun `deleting from empty tree without exceptions`() { + values.forEach { tree.delete(it) } + assertNull(tree.root, "Root should be null") + } } \ No newline at end of file From 72742c937676fa0757e66962ac05a2902a77771d Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+roketflame@users.noreply.github.com> Date: Sun, 23 Apr 2023 09:29:05 +0300 Subject: [PATCH 65/90] test: add test for using KeyValue as data in node --- app/src/main/kotlin/app/trees/KeyValue.kt | 2 +- app/src/test/kotlin/trees/AVLTreeTest.kt | 17 ++++++++++++++++- app/src/test/kotlin/trees/BSTreeTest.kt | 17 ++++++++++++++++- app/src/test/kotlin/trees/RBTreeTest.kt | 17 ++++++++++++++++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/app/trees/KeyValue.kt b/app/src/main/kotlin/app/trees/KeyValue.kt index 65004e4..1306808 100644 --- a/app/src/main/kotlin/app/trees/KeyValue.kt +++ b/app/src/main/kotlin/app/trees/KeyValue.kt @@ -7,7 +7,7 @@ package app.trees class KeyValue, V>( val key: K, - var value: V + var value: V? ) : Comparable> { override fun compareTo(other: KeyValue): Int { diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/app/src/test/kotlin/trees/AVLTreeTest.kt index 5bd5875..fda7bca 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/app/src/test/kotlin/trees/AVLTreeTest.kt @@ -6,6 +6,7 @@ package trees import app.trees.AVLTree +import app.trees.KeyValue import app.trees.nodes.AVLNode import kotlin.random.Random import kotlin.test.* @@ -69,7 +70,7 @@ class AVLTreeTest { } @Test - fun `deleting from empty tree without exceptions`() { + fun `check deleting from empty tree without exceptions`() { values.forEach { tree.delete(it) } assertNull(tree.root, "Root should be null") } @@ -82,4 +83,18 @@ class AVLTreeTest { assertTrue(tree.contains(value)) } } + + @Test + fun `check using KeyValue in data`() { + val newTree = AVLTree>() + val keyValue = values.map { KeyValue(it.toString(), it) } + for (stringIntKeyValue in keyValue) { + newTree.add(stringIntKeyValue) + assertEquals( + stringIntKeyValue.value, + newTree.get(KeyValue(stringIntKeyValue.key, null))?.value, + "Values should be equals" + ) + } + } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/app/src/test/kotlin/trees/BSTreeTest.kt index 1b6b2a6..fe0edb5 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/app/src/test/kotlin/trees/BSTreeTest.kt @@ -6,6 +6,7 @@ package trees import app.trees.BSTree +import app.trees.KeyValue import app.trees.nodes.BSNode import kotlin.random.Random import kotlin.test.* @@ -72,8 +73,22 @@ class BSTreeTest { } @Test - fun `deleting from empty tree without exceptions`() { + fun `check deleting from empty tree without exceptions`() { values.forEach { tree.delete(it) } assertNull(tree.root, "Root should be null") } + + @Test + fun `check using KeyValue in data`() { + val newTree = BSTree>() + val keyValue = values.map { KeyValue(it.toString(), it) } + for (stringIntKeyValue in keyValue) { + newTree.add(stringIntKeyValue) + assertEquals( + stringIntKeyValue.value, + newTree.get(KeyValue(stringIntKeyValue.key, null))?.value, + "Values should be equals" + ) + } + } } \ No newline at end of file diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/app/src/test/kotlin/trees/RBTreeTest.kt index 0fc1e46..38c0e3c 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/app/src/test/kotlin/trees/RBTreeTest.kt @@ -6,6 +6,7 @@ package trees +import app.trees.KeyValue import app.trees.RBTree import app.trees.nodes.Color import app.trees.nodes.RBNode @@ -93,8 +94,22 @@ class RBTreeTest { } @Test - fun `deleting from empty tree without exceptions`() { + fun `check deleting from empty tree without exceptions`() { values.forEach { tree.delete(it) } assertNull(tree.root, "Root should be null") } + + @Test + fun `check using KeyValue in data`() { + val newTree = RBTree>() + val keyValue = values.map { KeyValue(it.toString(), it) } + for (stringIntKeyValue in keyValue) { + newTree.add(stringIntKeyValue) + assertEquals( + stringIntKeyValue.value, + newTree.get(KeyValue(stringIntKeyValue.key, null))?.value, + "Values should be equals" + ) + } + } } \ No newline at end of file From acdd6ad6e4e3398a3604e99e414c138c8a5cfd52 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sun, 23 Apr 2023 18:04:14 +0300 Subject: [PATCH 66/90] feat: add dependencies for jetbrains.compose --- app/build.gradle.kts | 6 ++++- app/src/main/kotlin/Main.kt | 46 ++++++++++++++++++++++++++++++------- settings.gradle.kts | 7 ++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 92b3437..db29097 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,14 +16,16 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.8.10" id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" kotlin("plugin.serialization") version "1.8.20" + id("org.jetbrains.compose") version "1.4.0" // Apply the application plugin to add support for building a CLI application in Java. application } repositories { - // Use Maven Central for resolving dependencies. mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() } dependencies { @@ -33,8 +35,10 @@ dependencies { implementation("org.neo4j:neo4j-ogm-core:4.0.5") runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation(compose.desktop.currentOs) } + tasks.jar { manifest.attributes["Main-Class"] = "app.AppKt" } diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt index 7a5c6e9..9f1c986 100644 --- a/app/src/main/kotlin/Main.kt +++ b/app/src/main/kotlin/Main.kt @@ -3,13 +3,43 @@ * SPDX-License-Identifier: Apache-2.0 */ -import app.trees.AVLTree -import kotlin.random.Random +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.singleWindowApplication -fun main(args: Array) { - val tree = AVLTree() - val lst = List(100) { Random.nextInt(1000) } - lst.forEach { tree.add(it) } - tree.root = null - println(tree.preOrder()) +@OptIn(ExperimentalComposeUiApi::class) +fun main() = singleWindowApplication { + Column( + Modifier.background(Color.White), + verticalArrangement = Arrangement.spacedBy(1.dp) + ) { + repeat(10) { index -> + var active by remember { mutableStateOf(false) } + Text( + modifier = Modifier + .fillMaxWidth() + .background(color = if (active) Color.Green else Color.White) + .onPointerEvent(PointerEventType.Enter) { active = true } + .onPointerEvent(PointerEventType.Exit) { active = false }, + fontSize = 30.sp, + fontStyle = if (active) FontStyle.Italic else FontStyle.Normal, + text = "Item $index" + ) + } + } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a6f8ea0..ba9e84c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,4 +29,11 @@ dependencyResolutionManagement { library("junit-jupiter", "org.junit.jupiter:junit-jupiter:5.9.2") } } +} + +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } } \ No newline at end of file From 83a6aebdd504c4fab07436f935d064ee93a1b948 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sun, 23 Apr 2023 22:11:56 +0300 Subject: [PATCH 67/90] feat: add way to store trees with .json file --- app/src/main/kotlin/repo/JsonRepository.kt | 95 +++++++++++++++++++ app/src/main/kotlin/repo/Neo4jRepository.kt | 38 ++++---- app/src/main/kotlin/repo/Repository.kt | 1 + .../main/kotlin/repo/jsonEntities/JsonNode.kt | 17 ++++ .../main/kotlin/repo/jsonEntities/JsonTree.kt | 13 +++ .../neo4jEntities/SerializableNodeEntity.kt | 2 +- .../neo4jEntities/SerializableTreeEntity.kt | 2 +- .../kotlin/repo/serialization/Serializable.kt | 4 +- .../serialization/strategies/BSStrategy.kt | 34 +++++++ .../serialization/strategies/RBStrategy.kt | 49 ++++++++++ .../serialization/strategies/Serialization.kt | 8 +- 11 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 app/src/main/kotlin/repo/JsonRepository.kt create mode 100644 app/src/main/kotlin/repo/jsonEntities/JsonNode.kt create mode 100644 app/src/main/kotlin/repo/jsonEntities/JsonTree.kt rename app/src/main/kotlin/repo/{serialization => }/neo4jEntities/SerializableNodeEntity.kt (94%) rename app/src/main/kotlin/repo/{serialization => }/neo4jEntities/SerializableTreeEntity.kt (92%) create mode 100644 app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt create mode 100644 app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt diff --git a/app/src/main/kotlin/repo/JsonRepository.kt b/app/src/main/kotlin/repo/JsonRepository.kt new file mode 100644 index 0000000..400d794 --- /dev/null +++ b/app/src/main/kotlin/repo/JsonRepository.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo + +import JsonNode +import JsonTree +import app.trees.AbstractTree +import app.trees.nodes.AbstractNode +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import repo.serialization.SerializableNode +import repo.serialization.SerializableTree +import repo.serialization.strategies.Serialization +import java.io.File +import java.io.FileNotFoundException + + +class JsonRepository, + NodeType : AbstractNode, + TreeType : AbstractTree> + ( + strategy: Serialization, + dirPath: String +) : Repository(strategy) { + private val dirPath = "$dirPath/${strategy.typeOfTree.name.lowercase()}" + + private fun JsonNode.toSerializableNode(): SerializableNode { + return SerializableNode( + data, + metadata, + left?.toSerializableNode(), + right?.toSerializableNode() + ) + } + + private fun JsonNode.deserialize(parent: NodeType? = null): NodeType? { + val node = strategy.createNode(this.toSerializableNode()) + node?.parent = parent + node?.left = left?.deserialize(node) + node?.right = right?.deserialize(node) + return node + } + + private fun SerializableNode.toJsonNode(): JsonNode { + return JsonNode( + data, + metadata, + left?.toJsonNode(), + right?.toJsonNode() + ) + } + + private fun SerializableTree.toJsonTree(): JsonTree { + return JsonTree( + name, + root?.toJsonNode() + ) + } + + override fun getNames(): List = + File(dirPath).listFiles()?.map { + Json.decodeFromString(it.readText()).name + } ?: listOf() + + override fun loadByName(name: String): TreeType? { + val json = try { + File(dirPath, "${name.hashCode()}.json").readText() + } catch (_: FileNotFoundException) { + return null + } + + val jsonTree = Json.decodeFromString(json) + return strategy.createTree().apply { + root = jsonTree.root?.deserialize() + } + } + + override fun save(name: String, tree: TreeType) { + val jsonTree = tree.toSerializableTree(name).toJsonTree() + + File(dirPath).mkdirs() + File(dirPath, "${name.hashCode()}.json").run { + createNewFile() + writeText(Json.encodeToString(jsonTree)) + } + } + + override fun deleteByName(name: String) { + File(dirPath, "${name.hashCode()}.json").delete() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt index 9697ed1..6f70e59 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -12,17 +12,15 @@ import org.neo4j.ogm.cypher.ComparisonOperator import org.neo4j.ogm.cypher.Filter import org.neo4j.ogm.cypher.Filters import org.neo4j.ogm.session.SessionFactory +import repo.neo4jEntities.SerializableNodeEntity +import repo.neo4jEntities.SerializableTreeEntity import repo.serialization.SerializableNode import repo.serialization.SerializableTree -import repo.serialization.neo4jEntities.SerializableNodeEntity -import repo.serialization.neo4jEntities.SerializableTreeEntity import repo.serialization.strategies.Serialization -class Neo4jRepo< - T : Comparable, +class Neo4jRepo, NodeType : AbstractNode, - TreeType : AbstractTree, - >( + TreeType : AbstractTree>( strategy: Serialization, configuration: Configuration ) : Repository(strategy) { @@ -38,12 +36,12 @@ class Neo4jRepo< ) } - private fun SerializableTreeEntity.toTree(): SerializableTree { - return SerializableTree( - name, - typeOfTree, - root?.toSerializableNode(), - ) + private fun SerializableNodeEntity.deserialize(parent: NodeType? = null): NodeType? { + val node = strategy.createNode(this.toSerializableNode()) + node?.parent = parent + node?.left = left?.deserialize(node) + node?.right = right?.deserialize(node) + return node } private fun SerializableNode.toEntity(): SerializableNodeEntity { @@ -55,6 +53,14 @@ class Neo4jRepo< ) } + private fun SerializableTreeEntity.toTree(): SerializableTree { + return SerializableTree( + name, + typeOfTree, + root?.toSerializableNode(), + ) + } + private fun SerializableTree.toEntity(): SerializableTreeEntity { return SerializableTreeEntity( name, @@ -96,11 +102,7 @@ class Neo4jRepo< ) } - private fun SerializableNodeEntity.deserialize(parent: NodeType? = null): NodeType? { - val node = strategy.createNode(this.toSerializableNode()) - node?.parent = parent - node?.left = left?.deserialize(node) - node?.right = right?.deserialize(node) - return node + override fun getNames(): List { + TODO("Not yet implemented") } } \ No newline at end of file diff --git a/app/src/main/kotlin/repo/Repository.kt b/app/src/main/kotlin/repo/Repository.kt index c1e666c..d8df68e 100644 --- a/app/src/main/kotlin/repo/Repository.kt +++ b/app/src/main/kotlin/repo/Repository.kt @@ -37,4 +37,5 @@ abstract class Repository, abstract fun save(name: String, tree: TreeType) abstract fun loadByName(name: String): TreeType? abstract fun deleteByName(name: String) + abstract fun getNames(): List } \ No newline at end of file diff --git a/app/src/main/kotlin/repo/jsonEntities/JsonNode.kt b/app/src/main/kotlin/repo/jsonEntities/JsonNode.kt new file mode 100644 index 0000000..dfcf70d --- /dev/null +++ b/app/src/main/kotlin/repo/jsonEntities/JsonNode.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import kotlinx.serialization.Serializable +import repo.serialization.Metadata +import repo.serialization.SerializableValue + + +@Serializable +data class JsonNode( + val data: SerializableValue, + val metadata: Metadata, + val left: JsonNode?, + val right: JsonNode? +) \ No newline at end of file diff --git a/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt b/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt new file mode 100644 index 0000000..142bf4a --- /dev/null +++ b/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + + +import kotlinx.serialization.Serializable + +@Serializable +data class JsonTree( + val name: String, + val root: JsonNode? +) \ No newline at end of file diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt b/app/src/main/kotlin/repo/neo4jEntities/SerializableNodeEntity.kt similarity index 94% rename from app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt rename to app/src/main/kotlin/repo/neo4jEntities/SerializableNodeEntity.kt index f2a7bbd..aab43b8 100644 --- a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableNodeEntity.kt +++ b/app/src/main/kotlin/repo/neo4jEntities/SerializableNodeEntity.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization.neo4jEntities +package repo.neo4jEntities import org.neo4j.ogm.annotation.* import repo.serialization.Metadata diff --git a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt b/app/src/main/kotlin/repo/neo4jEntities/SerializableTreeEntity.kt similarity index 92% rename from app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt rename to app/src/main/kotlin/repo/neo4jEntities/SerializableTreeEntity.kt index 0efb237..cddbc49 100644 --- a/app/src/main/kotlin/repo/serialization/neo4jEntities/SerializableTreeEntity.kt +++ b/app/src/main/kotlin/repo/neo4jEntities/SerializableTreeEntity.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization.neo4jEntities +package repo.neo4jEntities import org.neo4j.ogm.annotation.* import repo.serialization.TypeOfTree diff --git a/app/src/main/kotlin/repo/serialization/Serializable.kt b/app/src/main/kotlin/repo/serialization/Serializable.kt index a4a47d4..7cdf85c 100644 --- a/app/src/main/kotlin/repo/serialization/Serializable.kt +++ b/app/src/main/kotlin/repo/serialization/Serializable.kt @@ -9,8 +9,8 @@ import kotlinx.serialization.Serializable @Serializable enum class TypeOfTree { - BINARY_SEARCH_TREE, - RED_BLACK_TREE, + BS_TREE, + RB_TREE, AVL_TREE } diff --git a/app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt b/app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt new file mode 100644 index 0000000..852786c --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization.strategies + +import app.trees.BSTree +import app.trees.nodes.BSNode +import repo.serialization.Metadata +import repo.serialization.SerializableNode +import repo.serialization.SerializableValue +import repo.serialization.TypeOfTree + +class BSStrategy>( + serializeData: (T) -> SerializableValue, + deserializeData: (SerializableValue) -> T +) : Serialization, BSTree, Int>(serializeData, deserializeData) { + override val typeOfTree: TypeOfTree = TypeOfTree.BS_TREE + + override fun createNode(node: SerializableNode?): BSNode? = node?.let { + BSNode( + data = deserializeValue(node.data), + left = createNode(node.left), + right = createNode(node.right), + ) + } + + override fun deserializeMetadata(metadata: Metadata) = metadata.value.toInt() + + override fun serializeMetadata(node: BSNode) = Metadata("") + + override fun createTree() = BSTree() +} diff --git a/app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt b/app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt new file mode 100644 index 0000000..790599e --- /dev/null +++ b/app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo.serialization.strategies + +import app.trees.RBTree +import app.trees.nodes.Color +import app.trees.nodes.RBNode +import repo.serialization.Metadata +import repo.serialization.SerializableNode +import repo.serialization.SerializableValue +import repo.serialization.TypeOfTree + +class RBStrategy>( + serializeData: (T) -> SerializableValue, + deserializeData: (SerializableValue) -> T +) : Serialization, RBTree, Color>(serializeData, deserializeData) { + override val typeOfTree: TypeOfTree = TypeOfTree.RB_TREE + + override fun createNode(node: SerializableNode?): RBNode? = node?.let { + RBNode( + data = deserializeValue(node.data), + color = deserializeMetadata(node.metadata), + left = createNode(node.left), + right = createNode(node.right), + ) + } + + override fun deserializeMetadata(metadata: Metadata): Color { + return when (metadata.value) { + "RED" -> Color.RED + "BLACK" -> Color.BLACK + else -> throw IllegalArgumentException("Can't deserialize metadata $metadata") + } + } + + override fun serializeMetadata(node: RBNode): Metadata { + return Metadata( + when (node.color) { + Color.RED -> "RED" + else -> "BLACK" + } + ) + } + + override fun createTree() = RBTree() +} diff --git a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt index 1e046d6..9855540 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt +++ b/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt @@ -13,14 +13,12 @@ import repo.serialization.SerializableValue import repo.serialization.TypeOfTree -abstract class Serialization< - T : Comparable, +abstract class Serialization, NodeType : AbstractNode, TreeType : AbstractTree, - M, - >( + M>( val serializeValue: (T) -> SerializableValue, - val deserializeValue: (SerializableValue) -> T, + val deserializeValue: (SerializableValue) -> T ) { abstract val typeOfTree: TypeOfTree abstract fun createNode(node: SerializableNode?): NodeType? From bca75038cfcd35c16725790b77ceea9f5b589011 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+roketflame@users.noreply.github.com> Date: Mon, 24 Apr 2023 16:21:44 +0300 Subject: [PATCH 68/90] feat: add a way to store trees in SQL database Signed-off-by: RoketFlame <72881638+RoketFlame@users.noreply.github.com> --- app/build.gradle.kts | 4 + app/src/main/kotlin/repo/SQLRepository.kt | 104 ++++++++++++++++++ .../repo/sqliteEntities/SQLNodeEntity.kt | 28 +++++ .../repo/sqliteEntities/SQLTreeEntity.kt | 27 +++++ 4 files changed, 163 insertions(+) create mode 100644 app/src/main/kotlin/repo/SQLRepository.kt create mode 100644 app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt create mode 100644 app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index db29097..6f5cb2b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,6 +35,10 @@ dependencies { implementation("org.neo4j:neo4j-ogm-core:4.0.5") runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.jetbrains.exposed", "exposed-core", "0.40.1") + implementation("org.jetbrains.exposed", "exposed-dao", "0.40.1") + implementation("org.jetbrains.exposed", "exposed-jdbc", "0.40.1") + implementation("org.xerial:sqlite-jdbc:3.40.1.0") implementation(compose.desktop.currentOs) } diff --git a/app/src/main/kotlin/repo/SQLRepository.kt b/app/src/main/kotlin/repo/SQLRepository.kt new file mode 100644 index 0000000..5232333 --- /dev/null +++ b/app/src/main/kotlin/repo/SQLRepository.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package repo + +import NodesTable +import SQLNodeEntity +import SQLTreeEntity +import TreesTable +import app.trees.AbstractTree +import app.trees.nodes.AbstractNode +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.transactions.transaction +import repo.serialization.Metadata +import repo.serialization.SerializableNode +import repo.serialization.SerializableTree +import repo.serialization.SerializableValue +import repo.serialization.strategies.Serialization + +class SQLRepository, + NodeType : AbstractNode, + TreeType : AbstractTree>( + strategy: Serialization, + private val db: Database +) : Repository(strategy) { + + private val typeOfTree = strategy.typeOfTree.toString() + + init { + transaction(db) { + SchemaUtils.create(TreesTable) + SchemaUtils.create(NodesTable) + } + } + + private fun SQLNodeEntity.toSerializableNode(): SerializableNode { + return SerializableNode( + SerializableValue(data), + Metadata(metadata), + left?.toSerializableNode(), + right?.toSerializableNode(), + ) + } + + private fun SQLNodeEntity.deserialize(parent: NodeType? = null): NodeType? { + val node = strategy.createNode(this.toSerializableNode()) + node?.parent = parent + node?.left = left?.deserialize(node) + node?.right = right?.deserialize(node) + return node + } + + private fun SerializableNode.toEntity(tree: SQLTreeEntity): SQLNodeEntity = SQLNodeEntity.new { + this@new.data = this@toEntity.data.value + this@new.metadata = this@toEntity.metadata.value + this@new.left = this@toEntity.left?.toEntity(tree) + this@new.right = this@toEntity.right?.toEntity(tree) + this.tree = tree + } + + private fun SerializableTree.toEntity(): SQLTreeEntity { + return SQLTreeEntity.new { + this.name = this@toEntity.name + this.typeOfTree = this@SQLRepository.typeOfTree + } + } + + override fun save(name: String, tree: TreeType): Unit = transaction(db) { + deleteByName(name) + val entityTree = tree.toSerializableTree(name).toEntity() + entityTree.root = tree.root?.toSerializableNode()?.toEntity(entityTree) + } + + + override fun loadByName(name: String): TreeType? = transaction(db) { + SQLTreeEntity.find( + TreesTable.typeOfTree eq typeOfTree and (TreesTable.name eq name) + ).firstOrNull()?.let { + strategy.createTree().apply { root = it.root?.deserialize() } + } + } + + override fun deleteByName(name: String): Unit = transaction(db) { + val treeId = SQLTreeEntity.find( + TreesTable.typeOfTree eq strategy.typeOfTree.toString() and (TreesTable.name eq name) + ).firstOrNull()?.id?.value + SQLTreeEntity.find( + TreesTable.id eq treeId + ).firstOrNull()?.delete() + NodesTable.deleteWhere { + tree eq treeId + } + } + + override fun getNames(): List = transaction(db) { + SQLTreeEntity.find(TreesTable.typeOfTree eq typeOfTree).map(SQLTreeEntity::name) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt b/app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt new file mode 100644 index 0000000..47181f4 --- /dev/null +++ b/app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +class SQLNodeEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(NodesTable) + + var data by NodesTable.data + var metadata by NodesTable.metadata + var left by SQLNodeEntity optionalReferencedOn NodesTable.left + var right by SQLNodeEntity optionalReferencedOn NodesTable.right + var tree by SQLTreeEntity referencedOn NodesTable.tree +} + +internal object NodesTable : IntIdTable("nodes") { + val data = text("data") + val metadata = text("metadata") + val left = reference("left_id", NodesTable).nullable() + val right = reference("right_id", NodesTable).nullable() + val tree = reference("tree_id", TreesTable, onDelete = ReferenceOption.CASCADE) +} \ No newline at end of file diff --git a/app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt b/app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt new file mode 100644 index 0000000..58b1f33 --- /dev/null +++ b/app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable + +class SQLTreeEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(TreesTable) + + var name by TreesTable.name + var typeOfTree by TreesTable.typeOfTree + var root by SQLNodeEntity optionalReferencedOn TreesTable.root +} + +internal object TreesTable : IntIdTable("trees") { + val name = text("name") + val typeOfTree = text("type") + val root = reference("root_node_id", NodesTable).nullable() + + init { + uniqueIndex(name, typeOfTree) + } +} \ No newline at end of file From 8d216c7247854fed8fcfa46823e61597056333b5 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Mon, 24 Apr 2023 16:43:54 +0300 Subject: [PATCH 69/90] feat: add field typeOfTree in JsonRepository Signed-off-by: RoketFlame <72881638+RoketFlame@users.noreply.github.com> --- app/src/main/kotlin/repo/JsonRepository.kt | 2 ++ app/src/main/kotlin/repo/jsonEntities/JsonTree.kt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/src/main/kotlin/repo/JsonRepository.kt b/app/src/main/kotlin/repo/JsonRepository.kt index 400d794..41b05db 100644 --- a/app/src/main/kotlin/repo/JsonRepository.kt +++ b/app/src/main/kotlin/repo/JsonRepository.kt @@ -26,6 +26,7 @@ class JsonRepository, strategy: Serialization, dirPath: String ) : Repository(strategy) { + private val dirPath = "$dirPath/${strategy.typeOfTree.name.lowercase()}" private fun JsonNode.toSerializableNode(): SerializableNode { @@ -57,6 +58,7 @@ class JsonRepository, private fun SerializableTree.toJsonTree(): JsonTree { return JsonTree( name, + typeOfTree, root?.toJsonNode() ) } diff --git a/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt b/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt index 142bf4a..6fa3dc7 100644 --- a/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt +++ b/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt @@ -5,9 +5,11 @@ import kotlinx.serialization.Serializable +import repo.serialization.TypeOfTree @Serializable data class JsonTree( val name: String, + val typeOfTree: TypeOfTree, val root: JsonNode? ) \ No newline at end of file From 4f019e61c312cc8f2f4d46ae916c7f29fa8ea4f1 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Mon, 24 Apr 2023 18:21:24 +0300 Subject: [PATCH 70/90] fix: fix AVLNode refactor: format Neo4j repo Signed-off-by: RoketFlame <72881638+RoketFlame@users.noreply.github.com> --- app/src/main/kotlin/Main.kt | 64 ++++++++----------- .../main/kotlin/app/trees/nodes/AVLNode.kt | 2 +- app/src/main/kotlin/repo/Neo4jRepository.kt | 22 +++---- ...izableNodeEntity.kt => Neo4jNodeEntity.kt} | 6 +- ...izableTreeEntity.kt => Neo4jTreeEntity.kt} | 4 +- 5 files changed, 44 insertions(+), 54 deletions(-) rename app/src/main/kotlin/repo/neo4jEntities/{SerializableNodeEntity.kt => Neo4jNodeEntity.kt} (82%) rename app/src/main/kotlin/repo/neo4jEntities/{SerializableTreeEntity.kt => Neo4jTreeEntity.kt} (86%) diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt index 9f1c986..51123fb 100644 --- a/app/src/main/kotlin/Main.kt +++ b/app/src/main/kotlin/Main.kt @@ -3,43 +3,33 @@ * SPDX-License-Identifier: Apache-2.0 */ -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.Text -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.onPointerEvent -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.singleWindowApplication +import app.trees.AVLTree +import org.neo4j.ogm.config.Configuration +import repo.Neo4jRepo +import repo.serialization.SerializableValue +import repo.serialization.strategies.AVLStrategy +import kotlin.random.Random -@OptIn(ExperimentalComposeUiApi::class) -fun main() = singleWindowApplication { - Column( - Modifier.background(Color.White), - verticalArrangement = Arrangement.spacedBy(1.dp) - ) { - repeat(10) { index -> - var active by remember { mutableStateOf(false) } - Text( - modifier = Modifier - .fillMaxWidth() - .background(color = if (active) Color.Green else Color.White) - .onPointerEvent(PointerEventType.Enter) { active = true } - .onPointerEvent(PointerEventType.Exit) { active = false }, - fontSize = 30.sp, - fontStyle = if (active) FontStyle.Italic else FontStyle.Normal, - text = "Item $index" - ) - } +fun main() { + val tree = AVLTree() + val randomizer = Random(40) + val lst = List(10) { randomizer.nextInt(100) } + lst.forEach { tree.add(it) } + val conf = Configuration.Builder() + .uri("neo4j://localhost") + .credentials("neo4j", "") + .build() + + fun serializeInt(data: Int): SerializableValue { + return SerializableValue(data.toString()) + } + + fun deserializeInt(data: SerializableValue): Int { + return data.value.toInt() } + + val avlRepo = Neo4jRepo(AVLStrategy(::serializeInt, ::deserializeInt), conf) + avlRepo.save("main", tree) + val testTree = avlRepo.loadByName("main") + println(testTree.preOrder()) } \ No newline at end of file diff --git a/app/src/main/kotlin/app/trees/nodes/AVLNode.kt b/app/src/main/kotlin/app/trees/nodes/AVLNode.kt index 43b55eb..e8112d6 100644 --- a/app/src/main/kotlin/app/trees/nodes/AVLNode.kt +++ b/app/src/main/kotlin/app/trees/nodes/AVLNode.kt @@ -5,7 +5,7 @@ package app.trees.nodes -data class AVLNode>( +class AVLNode>( override var data: T, internal var height: Int = 1, override var left: AVLNode? = null, diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt index 6f70e59..5eacdaf 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -12,8 +12,8 @@ import org.neo4j.ogm.cypher.ComparisonOperator import org.neo4j.ogm.cypher.Filter import org.neo4j.ogm.cypher.Filters import org.neo4j.ogm.session.SessionFactory -import repo.neo4jEntities.SerializableNodeEntity -import repo.neo4jEntities.SerializableTreeEntity +import repo.neo4jEntities.Neo4jNodeEntity +import repo.neo4jEntities.Neo4jTreeEntity import repo.serialization.SerializableNode import repo.serialization.SerializableTree import repo.serialization.strategies.Serialization @@ -27,7 +27,7 @@ class Neo4jRepo, private val sessionFactory = SessionFactory(configuration, "repo") private val session = sessionFactory.openSession() - private fun SerializableNodeEntity.toSerializableNode(): SerializableNode { + private fun Neo4jNodeEntity.toSerializableNode(): SerializableNode { return SerializableNode( data, metadata, @@ -36,7 +36,7 @@ class Neo4jRepo, ) } - private fun SerializableNodeEntity.deserialize(parent: NodeType? = null): NodeType? { + private fun Neo4jNodeEntity.deserialize(parent: NodeType? = null): NodeType? { val node = strategy.createNode(this.toSerializableNode()) node?.parent = parent node?.left = left?.deserialize(node) @@ -44,8 +44,8 @@ class Neo4jRepo, return node } - private fun SerializableNode.toEntity(): SerializableNodeEntity { - return SerializableNodeEntity( + private fun SerializableNode.toEntity(): Neo4jNodeEntity { + return Neo4jNodeEntity( data, metadata, left?.toEntity(), @@ -53,7 +53,7 @@ class Neo4jRepo, ) } - private fun SerializableTreeEntity.toTree(): SerializableTree { + private fun Neo4jTreeEntity.toTree(): SerializableTree { return SerializableTree( name, typeOfTree, @@ -61,8 +61,8 @@ class Neo4jRepo, ) } - private fun SerializableTree.toEntity(): SerializableTreeEntity { - return SerializableTreeEntity( + private fun SerializableTree.toEntity(): Neo4jTreeEntity { + return Neo4jTreeEntity( name, typeOfTree, root?.toEntity(), @@ -76,7 +76,7 @@ class Neo4jRepo, } private fun findByVerboseName(name: String) = session.loadAll( - SerializableTreeEntity::class.java, + Neo4jTreeEntity::class.java, Filters().and( Filter("name", ComparisonOperator.EQUALS, name) ).and( @@ -96,7 +96,7 @@ class Neo4jRepo, override fun deleteByName(name: String) { session.query( "MATCH toDelete=(" + - "t:SerializableTreeEntity {typeOfTree: \$typeOfTree, name : \$name}" + + "t:Tree {typeOfTree: \$typeOfTree, name : \$name}" + ")-[*0..]->() DETACH DELETE toDelete", mapOf("typeOfTree" to strategy.typeOfTree, "name" to name) ) diff --git a/app/src/main/kotlin/repo/neo4jEntities/SerializableNodeEntity.kt b/app/src/main/kotlin/repo/neo4jEntities/Neo4jNodeEntity.kt similarity index 82% rename from app/src/main/kotlin/repo/neo4jEntities/SerializableNodeEntity.kt rename to app/src/main/kotlin/repo/neo4jEntities/Neo4jNodeEntity.kt index aab43b8..23ca642 100644 --- a/app/src/main/kotlin/repo/neo4jEntities/SerializableNodeEntity.kt +++ b/app/src/main/kotlin/repo/neo4jEntities/Neo4jNodeEntity.kt @@ -10,7 +10,7 @@ import repo.serialization.Metadata import repo.serialization.SerializableValue @NodeEntity("Node") -class SerializableNodeEntity( +class Neo4jNodeEntity( @Property("data") var data: SerializableValue, @@ -18,10 +18,10 @@ class SerializableNodeEntity( var metadata: Metadata, @Relationship(type = "LEFT", direction = Relationship.Direction.OUTGOING) - var left: SerializableNodeEntity? = null, + var left: Neo4jNodeEntity? = null, @Relationship(type = "RIGHT", direction = Relationship.Direction.OUTGOING) - var right: SerializableNodeEntity? = null, + var right: Neo4jNodeEntity? = null, ) { @Id @GeneratedValue diff --git a/app/src/main/kotlin/repo/neo4jEntities/SerializableTreeEntity.kt b/app/src/main/kotlin/repo/neo4jEntities/Neo4jTreeEntity.kt similarity index 86% rename from app/src/main/kotlin/repo/neo4jEntities/SerializableTreeEntity.kt rename to app/src/main/kotlin/repo/neo4jEntities/Neo4jTreeEntity.kt index cddbc49..7ffd358 100644 --- a/app/src/main/kotlin/repo/neo4jEntities/SerializableTreeEntity.kt +++ b/app/src/main/kotlin/repo/neo4jEntities/Neo4jTreeEntity.kt @@ -9,7 +9,7 @@ import org.neo4j.ogm.annotation.* import repo.serialization.TypeOfTree @NodeEntity("Tree") -class SerializableTreeEntity( +class Neo4jTreeEntity( @Property("name") var name: String, @@ -17,7 +17,7 @@ class SerializableTreeEntity( var typeOfTree: TypeOfTree, @Relationship(type = "ROOT", direction = Relationship.Direction.OUTGOING) - var root: SerializableNodeEntity?, + var root: Neo4jNodeEntity?, ) { @Id From 86ff2c0a00da08c99ca66c1857071b3e52ca81df Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+roketflame@users.noreply.github.com> Date: Wed, 26 Apr 2023 21:20:41 +0300 Subject: [PATCH 71/90] feat: implement getNames for Neo4jRepo --- app/src/main/kotlin/repo/Neo4jRepository.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/app/src/main/kotlin/repo/Neo4jRepository.kt index 5eacdaf..79a846b 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/app/src/main/kotlin/repo/Neo4jRepository.kt @@ -102,7 +102,9 @@ class Neo4jRepo, ) } - override fun getNames(): List { - TODO("Not yet implemented") - } + override fun getNames(): List = session.loadAll( + Neo4jTreeEntity::class.java, + Filter("typeOfTree", ComparisonOperator.EQUALS, strategy.typeOfTree), + 0 + ).map(Neo4jTreeEntity::name) } \ No newline at end of file From 297ad582d3a9a1ddf6de2049794c97f4331b688d Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Thu, 27 Apr 2023 18:16:41 +0300 Subject: [PATCH 72/90] feat: implement very early visualizer for trees --- app/build.gradle.kts | 2 + app/src/main/kotlin/Main.kt | 68 ++++-- app/src/main/kotlin/composeApp/App.kt | 222 ++++++++++++++++++ app/src/main/kotlin/composeApp/Controller.kt | 50 ++++ .../main/kotlin/composeApp/DrawableNode.kt | 16 ++ app/src/main/kotlin/composeApp/Editor.kt | 92 ++++++++ .../main/kotlin/composeApp/MainActivity.kt | 91 +++++++ app/src/main/kotlin/repo/JsonRepository.kt | 5 +- 8 files changed, 518 insertions(+), 28 deletions(-) create mode 100644 app/src/main/kotlin/composeApp/App.kt create mode 100644 app/src/main/kotlin/composeApp/Controller.kt create mode 100644 app/src/main/kotlin/composeApp/DrawableNode.kt create mode 100644 app/src/main/kotlin/composeApp/Editor.kt create mode 100644 app/src/main/kotlin/composeApp/MainActivity.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6f5cb2b..3774daa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,7 +39,9 @@ dependencies { implementation("org.jetbrains.exposed", "exposed-dao", "0.40.1") implementation("org.jetbrains.exposed", "exposed-jdbc", "0.40.1") implementation("org.xerial:sqlite-jdbc:3.40.1.0") + implementation("com.google.code.gson:gson:2.10.1") implementation(compose.desktop.currentOs) + implementation(compose.material3) } diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt index 51123fb..8c1711a 100644 --- a/app/src/main/kotlin/Main.kt +++ b/app/src/main/kotlin/Main.kt @@ -3,33 +3,49 @@ * SPDX-License-Identifier: Apache-2.0 */ -import app.trees.AVLTree -import org.neo4j.ogm.config.Configuration -import repo.Neo4jRepo -import repo.serialization.SerializableValue -import repo.serialization.strategies.AVLStrategy -import kotlin.random.Random +import androidx.compose.ui.Alignment +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import java.awt.Dimension -fun main() { - val tree = AVLTree() - val randomizer = Random(40) - val lst = List(10) { randomizer.nextInt(100) } - lst.forEach { tree.add(it) } - val conf = Configuration.Builder() - .uri("neo4j://localhost") - .credentials("neo4j", "") - .build() +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Compose for Desktop", + state = rememberWindowState( + size = DpSize(700.dp, 700.dp), + position = WindowPosition(alignment = Alignment.Center) + ) - fun serializeInt(data: Int): SerializableValue { - return SerializableValue(data.toString()) + ) { + window.minimumSize = Dimension(700, 700) + run(window) } - - fun deserializeInt(data: SerializableValue): Int { - return data.value.toInt() - } - - val avlRepo = Neo4jRepo(AVLStrategy(::serializeInt, ::deserializeInt), conf) - avlRepo.save("main", tree) - val testTree = avlRepo.loadByName("main") - println(testTree.preOrder()) +// val username = "neo4j" +// val password = "isabel-except-toronto-monaco-never-5754" // insert password to database here +// val conf = Configuration.Builder() +// .uri("bolt://localhost") +// .credentials(username, password) +// .build() +// +// fun serializeInt(data: Int) = SerializableValue(data.toString()) +// +// fun deserializeInt(data: SerializableValue) = data.value.toInt() +// +// val avlRepo = Neo4jRepo(AVLStrategy(::serializeInt, ::deserializeInt), conf) +//// !!! storing AVLTree and AVLTree in the same db is unsupported +// +// val tree = AVLTree() +// val randomizer = Random(42) +// val lst = List(15) { randomizer.nextInt(1000) } +// lst.forEach { tree.add(it) } +// avlRepo.save("test", tree) +// val testTree = avlRepo.loadByName("test") +// println(testTree.preOrder()) // output pre-order traversal of tree +// avlRepo.save("wow", tree) +// println(avlRepo.getNames()) } \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/App.kt b/app/src/main/kotlin/composeApp/App.kt new file mode 100644 index 0000000..1a049b7 --- /dev/null +++ b/app/src/main/kotlin/composeApp/App.kt @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package composeApp + +import TypeOfDatabase +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toSize +import java.io.File + + +@Composable +fun OpenTree( + + listOfDatabase: List, + onTypeOfDatabaseChanged: (String) -> Unit, + typeOfDatabaseState: State, + stringTypeOfDatabaseState: State, + + onFilePicked: () -> Unit, + file: State, + pathState: State, + onPathChanged: (String) -> Unit, + + listOfNames: List, + nameState: State, + onNameChanged: (String) -> Unit, + + onLoadTree: () -> Unit, +) { + MaterialTheme { + Scaffold(topBar = { + TopAppBar(title = { Text("Выберите тип дерева") }) + }) { + Column(Modifier.padding(10.dp)) { + ChoosingStorageType(listOfDatabase, stringTypeOfDatabaseState, onTypeOfDatabaseChanged) + when (typeOfDatabaseState.value) { + TypeOfDatabase.Json -> FilePicker(onFilePicked, file) + TypeOfDatabase.Neo4j -> PathToStorage(pathState, onPathChanged) {} + TypeOfDatabase.SQL -> PathToStorage(pathState, onPathChanged) {} + else -> {} + } + ListOfTrees(listOfNames, nameState, onNameChanged, onLoadTree) + } + } + } +} + + +@Composable +fun ListOfTrees( + listOfNames: List, + nameState: State, + onNameChanged: (String) -> Unit, + onLoadTree: () -> Unit, +) { + var mExpanded by remember { mutableStateOf(false) } + var mTextFieldSize by remember { mutableStateOf(Size.Zero) } + val mSelectedText = nameState.value + + val icon = if (mExpanded) + Icons.Filled.KeyboardArrowUp + else + Icons.Filled.KeyboardArrowDown + + Column(Modifier.padding(horizontal = 10.dp)) { + OutlinedTextField( + value = mSelectedText, + singleLine = true, + readOnly = true, + onValueChange = onNameChanged, + modifier = Modifier + .fillMaxWidth() + .onGloballyPositioned { coordinates -> + mTextFieldSize = coordinates.size.toSize() + }, + label = { + Text( + text = "Name of tree", + ) + }, + trailingIcon = { + Icon(icon, "contentDescription", + Modifier.clickable { mExpanded = !mExpanded }) + } + ) + DropdownMenu( + expanded = mExpanded, + onDismissRequest = { mExpanded = false }, + modifier = Modifier.width(with(LocalDensity.current) { mTextFieldSize.width.toDp() }) + ) { + listOfNames.forEach { label -> + DropdownMenuItem(onClick = { + onNameChanged(label) + mExpanded = false + }) { + Text(text = label) + } + } + } + Button( + onClick = onLoadTree + ) { + Text("Let's go!") + } + } +} + +@Composable +fun FilePicker( + onFilePicked: () -> Unit, + file: State, +) { + Row( + modifier = Modifier.padding(horizontal = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Button( + onClick = onFilePicked + ) { + Text( + text = "Выберите файл", + ) + } + Spacer(modifier = Modifier.width(8.dp)) + file.value?.let { + Text(file.value?.name ?: "") + } + } +} + +@Composable +fun ChoosingStorageType( + listOfDatabase: List, + typeOfDatabaseState: State, + onTypeOfDatabaseChanged: (String) -> Unit +) { + var mExpanded by remember { mutableStateOf(false) } + var mTextFieldSize by remember { mutableStateOf(Size.Zero) } + val mTrees = listOfDatabase + val mSelectedText = typeOfDatabaseState.value + + val icon = if (mExpanded) + Icons.Filled.KeyboardArrowUp + else + Icons.Filled.KeyboardArrowDown + + Column(Modifier.padding(horizontal = 10.dp)) { + OutlinedTextField( + value = mSelectedText, + singleLine = true, + readOnly = true, + onValueChange = onTypeOfDatabaseChanged, + modifier = Modifier + .fillMaxWidth() + .onGloballyPositioned { coordinates -> + mTextFieldSize = coordinates.size.toSize() + }, + label = { + Text( + text = "Type of storage", + ) + }, + trailingIcon = { + Icon(icon, "contentDescription", + Modifier.clickable { mExpanded = !mExpanded }) + } + ) + DropdownMenu( + expanded = mExpanded, + onDismissRequest = { mExpanded = false }, + modifier = Modifier.width(with(LocalDensity.current) { mTextFieldSize.width.toDp() }) + ) { + mTrees.forEach { label -> + DropdownMenuItem(onClick = { + onTypeOfDatabaseChanged(label) + mExpanded = false + }) { + Text(text = label) + } + } + } + } +} + +@Composable +fun PathToStorage( + pathState: State, + onPathChanged: (String) -> Unit, + setPath: (String) -> Unit +) { + val path = pathState.value + Column(Modifier.padding(horizontal = 10.dp)) { + OutlinedTextField( + value = path, + singleLine = true, + onValueChange = onPathChanged, + label = { Text(text = "Введите путь к базе") }, + modifier = Modifier.fillMaxWidth() + ) + Button( +// modifier = Modifier.padding(vertical = 10.dp), + onClick = {} + ) { + Text("Загрузить") + } + } +} diff --git a/app/src/main/kotlin/composeApp/Controller.kt b/app/src/main/kotlin/composeApp/Controller.kt new file mode 100644 index 0000000..d74791d --- /dev/null +++ b/app/src/main/kotlin/composeApp/Controller.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import androidx.compose.ui.awt.ComposeWindow +import com.google.gson.Gson +import repo.JsonRepository +import repo.serialization.SerializableValue +import repo.serialization.strategies.AVLStrategy +import java.awt.FileDialog +import java.io.File + +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +class Controller { + fun openFileDialog( + window: ComposeWindow, title: String, allowedExtensions: List, allowMultiSelection: Boolean = true + ): File? { + val fileDialog = FileDialog(window, title, FileDialog.LOAD) + fileDialog.isMultipleMode = allowMultiSelection + fileDialog.isVisible = true + fileDialog.setFilenameFilter { _, name -> name.endsWith(".jpg") } + return fileDialog.files.firstOrNull() + } + + fun getNamesOfTrees(dirPath: String?, filename: String?): List { + if (dirPath == null || filename == null) + return listOf() + val gson = Gson() + + fun drawableNodeToString(node: DrawableNode): SerializableValue { + return SerializableValue(gson.toJson(node)) + } + + fun jsonStringToDrawableNode(string: SerializableValue): DrawableNode { + return gson.fromJson(string.value, DrawableNode::class.java)!! + } + + val repo = JsonRepository(AVLStrategy(::drawableNodeToString, ::jsonStringToDrawableNode), dirPath, filename) + return repo.getNames() + } + + fun loadTree(name: String) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/DrawableNode.kt b/app/src/main/kotlin/composeApp/DrawableNode.kt new file mode 100644 index 0000000..3b1d18a --- /dev/null +++ b/app/src/main/kotlin/composeApp/DrawableNode.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import app.trees.KeyValue + +class DrawableNode( + var data: KeyValue, + var x: Long, + var y: Long +) : Comparable { + override fun compareTo(other: DrawableNode): Int { + return data.compareTo(other.data) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/Editor.kt b/app/src/main/kotlin/composeApp/Editor.kt new file mode 100644 index 0000000..b604e8f --- /dev/null +++ b/app/src/main/kotlin/composeApp/Editor.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +@Composable +fun Editor( + +) { + Row { + Menu() + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color(245, 245, 245)) + .padding(20.dp) + ) { + MaterialTheme( + colorScheme = MaterialTheme.colorScheme.copy( + surface = Color.White, + ) + ) { +// EditorScreen() + } + + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Menu( + +) { + val addState = remember { mutableStateOf("") } + val deleteState = remember { mutableStateOf("") } + // Размещаем поля ввода в вертикальном столбце + Column( + Modifier.padding(16.dp).width(260.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + TextField( + value = addState.value, + onValueChange = { addState.value = it }, + label = { Text("Key") }, + modifier = Modifier.fillMaxWidth(), + ) + + TextField( + value = deleteState.value, + onValueChange = { deleteState.value = it }, + label = { Text("Value") }, + modifier = Modifier.fillMaxWidth(), + ) + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceAround + ) { + Button(onClick = {}) { + Icon(Icons.Default.Add, contentDescription = "Add") + } + Button(onClick = {}) { + Icon(Icons.Default.Search, contentDescription = "Search") + } + Button(onClick = {}) { + Icon(Icons.Default.Delete, contentDescription = "Delete") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/MainActivity.kt b/app/src/main/kotlin/composeApp/MainActivity.kt new file mode 100644 index 0000000..32a46ac --- /dev/null +++ b/app/src/main/kotlin/composeApp/MainActivity.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.awt.ComposeWindow +import composeApp.OpenTree +import java.io.File + +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +enum class States { + OPENING_TREE, DRAW_TREE +} + + +enum class TypeOfDatabase { + Neo4j, + Json, + SQL +} + +@Composable +fun run(window: ComposeWindow) { + val databases = remember { + mapOf( + "SQL" to TypeOfDatabase.SQL, + "Neo4j" to TypeOfDatabase.Neo4j, + ".json file" to TypeOfDatabase.Json + ) + } + val listOfDatabase = databases.keys.toList() + val stringTypeOfDatabaseState = remember { mutableStateOf(".json file") } + val pathState = remember { mutableStateOf("") } + val typeOfDatabaseState = + remember { mutableStateOf(databases.getOrDefault(stringTypeOfDatabaseState.value, TypeOfDatabase.Json)) } + + + val state = remember { mutableStateOf(States.OPENING_TREE) } + val controller = remember { mutableStateOf(Controller()) } + val file = remember { mutableStateOf(null) } + + val listNames = remember { mutableStateOf(listOf("test", "main")) } + val nameState = remember { mutableStateOf("") } + when (state.value) { + States.OPENING_TREE -> OpenTree( + listOfDatabase = listOfDatabase, + onTypeOfDatabaseChanged = { newType -> + stringTypeOfDatabaseState.value = newType + typeOfDatabaseState.value = databases.getOrDefault(stringTypeOfDatabaseState.value, TypeOfDatabase.Json) + }, + typeOfDatabaseState = typeOfDatabaseState, + stringTypeOfDatabaseState = stringTypeOfDatabaseState, + + pathState = pathState, + file = file, + onPathChanged = { newPath -> pathState.value = newPath }, + onFilePicked = { + file.value = + controller.value.openFileDialog( + window, + "Load a file", + listOf(".json"), + allowMultiSelection = false + ) + }, + + listOfNames = listNames.value, + nameState = nameState, + onNameChanged = { newName -> nameState.value = newName }, + + onLoadTree = { +// controller.value.loadTree(nameState.value) + state.value = States.DRAW_TREE + } + + + ) + + else -> { + window.setSize(1080, 800) + Editor() + } + } +} diff --git a/app/src/main/kotlin/repo/JsonRepository.kt b/app/src/main/kotlin/repo/JsonRepository.kt index 41b05db..f460cf6 100644 --- a/app/src/main/kotlin/repo/JsonRepository.kt +++ b/app/src/main/kotlin/repo/JsonRepository.kt @@ -24,7 +24,8 @@ class JsonRepository, TreeType : AbstractTree> ( strategy: Serialization, - dirPath: String + dirPath: String, + private val filename: String, ) : Repository(strategy) { private val dirPath = "$dirPath/${strategy.typeOfTree.name.lowercase()}" @@ -85,7 +86,7 @@ class JsonRepository, val jsonTree = tree.toSerializableTree(name).toJsonTree() File(dirPath).mkdirs() - File(dirPath, "${name.hashCode()}.json").run { + File(dirPath, filename).run { createNewFile() writeText(Json.encodeToString(jsonTree)) } From 05f8fa6aeab763d19d6e81377328138512216916 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Fri, 28 Apr 2023 23:50:30 +0300 Subject: [PATCH 73/90] feat: update version of gradle, add building project on macOS and ubuntu (CI) --- .github/workflows/github-actions.yml | 7 ++++++- gradle/wrapper/gradle-wrapper.properties | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 8520b63..082400b 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -8,7 +8,12 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + steps: - uses: actions/checkout@v3 - name: Set up JDK 17 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bdc9a83..11f6173 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,10 @@ +# +# Copyright (c) 2023 teemEight +# SPDX-License-Identifier: Apache-2.0 +# distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 445c8faad8ea63bed5f91e3d870d46957c6baefc Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Fri, 28 Apr 2023 23:55:32 +0300 Subject: [PATCH 74/90] feat: update properties for gradle, prepare for restructure of project --- app/build.gradle.kts | 59 +++++++------------------------------------- build.gradle.kts | 10 ++++++++ lib/build.gradle.kts | 38 ++++++++++++++++++++++++++++ settings.gradle.kts | 42 ++++++++++++++++++------------- 4 files changed, 82 insertions(+), 67 deletions(-) create mode 100644 build.gradle.kts create mode 100644 lib/build.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3774daa..ab3e20b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,62 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* - * This file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Kotlin application project to get you started. - * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle - * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html - */ - plugins { - // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. - id("org.jetbrains.kotlin.jvm") version "1.8.10" - id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" - kotlin("plugin.serialization") version "1.8.20" - id("org.jetbrains.compose") version "1.4.0" - // Apply the application plugin to add support for building a CLI application in Java. - application -} - - -repositories { - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - google() + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.compose) } dependencies { - testImplementation(kotlin("test")) - testImplementation(platform("org.junit:junit-bom:5.9.2")) - testImplementation("org.junit.jupiter:junit-jupiter") - implementation("org.neo4j:neo4j-ogm-core:4.0.5") - runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") - implementation("org.jetbrains.exposed", "exposed-core", "0.40.1") - implementation("org.jetbrains.exposed", "exposed-dao", "0.40.1") - implementation("org.jetbrains.exposed", "exposed-jdbc", "0.40.1") - implementation("org.xerial:sqlite-jdbc:3.40.1.0") - implementation("com.google.code.gson:gson:2.10.1") + implementation(libs.gson) + implementation(libs.kotlinx.serialization.json) implementation(compose.desktop.currentOs) implementation(compose.material3) + implementation(project(":lib")) } - -tasks.jar { - manifest.attributes["Main-Class"] = "app.AppKt" -} -noArg { - annotation("org.neo4j.ogm.annotation.NodeEntity") - annotation("org.neo4j.ogm.annotation.RelationshipEntity") -} - -application { - // Define the main class for the application. - mainClass.set("MainKt") -} - -tasks.test { - // Use JUnit Platform for unit tests. - useJUnitPlatform() +compose.desktop { + application { + mainClass = "MainKt" + } } diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..d3ecf21 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + // this is necessary to avoid the plugins to be loaded multiple times + // in each subproject's classloader + alias(libs.plugins.kotlin.jvm).apply(false) +} diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts new file mode 100644 index 0000000..9feed94 --- /dev/null +++ b/lib/build.gradle.kts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Kotlin application project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.noarg) + `java-library` +} + +dependencies { + implementation(libs.kotlinx.serialization.json) + + implementation(libs.exposed.core) + implementation(libs.exposed.dao) + implementation(libs.exposed.jdbc) + + implementation(libs.neo4j.ogm.core) + implementation(libs.neo4j.ogm.bolt) + testImplementation(kotlin("test")) +} + +noArg { + annotation("org.neo4j.ogm.annotation.NodeEntity") + annotation("org.neo4j.ogm.annotation.RelationshipEntity") +} + +tasks.test { +} diff --git a/settings.gradle.kts b/settings.gradle.kts index ba9e84c..47bb24a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,37 +3,45 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html - */ - rootProject.name = "trees-8" +include("lib") include("app") +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } +} + dependencyResolutionManagement { + repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() + } + versionCatalogs { create("libs") { version("kotlin", "1.8.20") plugin("kotlin-jvm", "org.jetbrains.kotlin.jvm").versionRef("kotlin") plugin("kotlin-serialization", "org.jetbrains.kotlin.plugin.serialization").versionRef("kotlin") plugin("kotlin-noarg", "org.jetbrains.kotlin.plugin.noarg").versionRef("kotlin") + + plugin("compose", "org.jetbrains.compose").version("1.4.0") + library("kotlinx-serialization-json", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + library("gson", "com.google.code.gson:gson:2.10.1") + + version("exposed", "0.41.1") + library("exposed-core", "org.jetbrains.exposed", "exposed-core").versionRef("exposed") + library("exposed-dao", "org.jetbrains.exposed", "exposed-dao").versionRef("exposed") + library("exposed-jdbc", "org.jetbrains.exposed", "exposed-jdbc").versionRef("exposed") + version("neo4j-ogm", "4.0.5") library("neo4j-ogm-core", "org.neo4j", "neo4j-ogm-core").versionRef("neo4j-ogm") library("neo4j-ogm-bolt", "org.neo4j", "neo4j-ogm-bolt-driver").versionRef("neo4j-ogm") - library("junit-jupiter", "org.junit.jupiter:junit-jupiter:5.9.2") + } } } - -pluginManagement { - repositories { - gradlePluginPortal() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") - } -} \ No newline at end of file From 1c3d41ada9df6cb589c6808fa23ac01a3bf75b49 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Fri, 28 Apr 2023 23:59:14 +0300 Subject: [PATCH 75/90] struct: huge refactor, now project include lib (with trees and repositories) and app (GUI) --- app/src/main/kotlin/composeApp/App.kt | 222 ------------------ .../main/kotlin/repository}/JsonRepository.kt | 16 +- .../kotlin/repository}/Neo4jRepository.kt | 18 +- .../src/main/kotlin/repository}/Repository.kt | 12 +- .../main/kotlin/repository}/SQLRepository.kt | 24 +- .../repository}/jsonEntities/JsonNode.kt | 6 +- .../repository}/jsonEntities/JsonTree.kt | 4 +- .../neo4jEntities/Neo4jNodeEntity.kt | 6 +- .../neo4jEntities/Neo4jTreeEntity.kt | 4 +- .../repository}/serialization/Serializable.kt | 2 +- .../serialization/strategies/AVLStrategy.kt | 14 +- .../serialization/strategies/BSStrategy.kt | 14 +- .../serialization/strategies/RBStrategy.kt | 16 +- .../serialization/strategies/Serialization.kt | 14 +- .../sqliteEntities/SQLNodeEntity.kt | 2 + .../sqliteEntities/SQLTreeEntity.kt | 2 + .../src/main/kotlin}/trees/AVLTree.kt | 6 +- .../src/main/kotlin}/trees/AbstractTree.kt | 6 +- .../src/main/kotlin}/trees/BSTree.kt | 4 +- .../src/main/kotlin}/trees/KeyValue.kt | 2 +- .../src/main/kotlin}/trees/RBTree.kt | 6 +- .../src/main/kotlin}/trees/interfaces/Node.kt | 2 +- .../src/main/kotlin}/trees/interfaces/Tree.kt | 2 +- .../src/main/kotlin}/trees/nodes/AVLNode.kt | 2 +- .../main/kotlin}/trees/nodes/AbstractNode.kt | 7 +- .../src/main/kotlin}/trees/nodes/BSNode.kt | 2 +- .../src/main/kotlin}/trees/nodes/RBNode.kt | 2 +- .../src/test/kotlin/trees/AVLTreeTest.kt | 4 +- .../src/test/kotlin/trees/BSTreeTest.kt | 4 +- .../src/test/kotlin/trees/InvariantChecker.kt | 9 +- .../src/test/kotlin/trees/RBTreeTest.kt | 6 +- 31 files changed, 108 insertions(+), 332 deletions(-) delete mode 100644 app/src/main/kotlin/composeApp/App.kt rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/JsonRepository.kt (88%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/Neo4jRepository.kt (90%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/Repository.kt (81%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/SQLRepository.kt (86%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/jsonEntities/JsonNode.kt (68%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/jsonEntities/JsonTree.kt (75%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/neo4jEntities/Neo4jNodeEntity.kt (81%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/neo4jEntities/Neo4jTreeEntity.kt (85%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/serialization/Serializable.kt (95%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/serialization/strategies/AVLStrategy.kt (76%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/serialization/strategies/BSStrategy.kt (74%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/serialization/strategies/RBStrategy.kt (80%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/serialization/strategies/Serialization.kt (67%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/sqliteEntities/SQLNodeEntity.kt (96%) rename {app/src/main/kotlin/repo => lib/src/main/kotlin/repository}/sqliteEntities/SQLTreeEntity.kt (95%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/AVLTree.kt (95%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/AbstractTree.kt (97%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/BSTree.kt (91%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/KeyValue.kt (97%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/RBTree.kt (99%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/interfaces/Node.kt (90%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/interfaces/Tree.kt (87%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/nodes/AVLNode.kt (96%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/nodes/AbstractNode.kt (86%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/nodes/BSNode.kt (92%) rename {app/src/main/kotlin/app => lib/src/main/kotlin}/trees/nodes/RBNode.kt (94%) rename {app => lib}/src/test/kotlin/trees/AVLTreeTest.kt (97%) rename {app => lib}/src/test/kotlin/trees/BSTreeTest.kt (97%) rename {app => lib}/src/test/kotlin/trees/InvariantChecker.kt (95%) rename {app => lib}/src/test/kotlin/trees/RBTreeTest.kt (97%) diff --git a/app/src/main/kotlin/composeApp/App.kt b/app/src/main/kotlin/composeApp/App.kt deleted file mode 100644 index 1a049b7..0000000 --- a/app/src/main/kotlin/composeApp/App.kt +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -package composeApp - -import TypeOfDatabase -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material.icons.filled.KeyboardArrowUp -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.toSize -import java.io.File - - -@Composable -fun OpenTree( - - listOfDatabase: List, - onTypeOfDatabaseChanged: (String) -> Unit, - typeOfDatabaseState: State, - stringTypeOfDatabaseState: State, - - onFilePicked: () -> Unit, - file: State, - pathState: State, - onPathChanged: (String) -> Unit, - - listOfNames: List, - nameState: State, - onNameChanged: (String) -> Unit, - - onLoadTree: () -> Unit, -) { - MaterialTheme { - Scaffold(topBar = { - TopAppBar(title = { Text("Выберите тип дерева") }) - }) { - Column(Modifier.padding(10.dp)) { - ChoosingStorageType(listOfDatabase, stringTypeOfDatabaseState, onTypeOfDatabaseChanged) - when (typeOfDatabaseState.value) { - TypeOfDatabase.Json -> FilePicker(onFilePicked, file) - TypeOfDatabase.Neo4j -> PathToStorage(pathState, onPathChanged) {} - TypeOfDatabase.SQL -> PathToStorage(pathState, onPathChanged) {} - else -> {} - } - ListOfTrees(listOfNames, nameState, onNameChanged, onLoadTree) - } - } - } -} - - -@Composable -fun ListOfTrees( - listOfNames: List, - nameState: State, - onNameChanged: (String) -> Unit, - onLoadTree: () -> Unit, -) { - var mExpanded by remember { mutableStateOf(false) } - var mTextFieldSize by remember { mutableStateOf(Size.Zero) } - val mSelectedText = nameState.value - - val icon = if (mExpanded) - Icons.Filled.KeyboardArrowUp - else - Icons.Filled.KeyboardArrowDown - - Column(Modifier.padding(horizontal = 10.dp)) { - OutlinedTextField( - value = mSelectedText, - singleLine = true, - readOnly = true, - onValueChange = onNameChanged, - modifier = Modifier - .fillMaxWidth() - .onGloballyPositioned { coordinates -> - mTextFieldSize = coordinates.size.toSize() - }, - label = { - Text( - text = "Name of tree", - ) - }, - trailingIcon = { - Icon(icon, "contentDescription", - Modifier.clickable { mExpanded = !mExpanded }) - } - ) - DropdownMenu( - expanded = mExpanded, - onDismissRequest = { mExpanded = false }, - modifier = Modifier.width(with(LocalDensity.current) { mTextFieldSize.width.toDp() }) - ) { - listOfNames.forEach { label -> - DropdownMenuItem(onClick = { - onNameChanged(label) - mExpanded = false - }) { - Text(text = label) - } - } - } - Button( - onClick = onLoadTree - ) { - Text("Let's go!") - } - } -} - -@Composable -fun FilePicker( - onFilePicked: () -> Unit, - file: State, -) { - Row( - modifier = Modifier.padding(horizontal = 10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onFilePicked - ) { - Text( - text = "Выберите файл", - ) - } - Spacer(modifier = Modifier.width(8.dp)) - file.value?.let { - Text(file.value?.name ?: "") - } - } -} - -@Composable -fun ChoosingStorageType( - listOfDatabase: List, - typeOfDatabaseState: State, - onTypeOfDatabaseChanged: (String) -> Unit -) { - var mExpanded by remember { mutableStateOf(false) } - var mTextFieldSize by remember { mutableStateOf(Size.Zero) } - val mTrees = listOfDatabase - val mSelectedText = typeOfDatabaseState.value - - val icon = if (mExpanded) - Icons.Filled.KeyboardArrowUp - else - Icons.Filled.KeyboardArrowDown - - Column(Modifier.padding(horizontal = 10.dp)) { - OutlinedTextField( - value = mSelectedText, - singleLine = true, - readOnly = true, - onValueChange = onTypeOfDatabaseChanged, - modifier = Modifier - .fillMaxWidth() - .onGloballyPositioned { coordinates -> - mTextFieldSize = coordinates.size.toSize() - }, - label = { - Text( - text = "Type of storage", - ) - }, - trailingIcon = { - Icon(icon, "contentDescription", - Modifier.clickable { mExpanded = !mExpanded }) - } - ) - DropdownMenu( - expanded = mExpanded, - onDismissRequest = { mExpanded = false }, - modifier = Modifier.width(with(LocalDensity.current) { mTextFieldSize.width.toDp() }) - ) { - mTrees.forEach { label -> - DropdownMenuItem(onClick = { - onTypeOfDatabaseChanged(label) - mExpanded = false - }) { - Text(text = label) - } - } - } - } -} - -@Composable -fun PathToStorage( - pathState: State, - onPathChanged: (String) -> Unit, - setPath: (String) -> Unit -) { - val path = pathState.value - Column(Modifier.padding(horizontal = 10.dp)) { - OutlinedTextField( - value = path, - singleLine = true, - onValueChange = onPathChanged, - label = { Text(text = "Введите путь к базе") }, - modifier = Modifier.fillMaxWidth() - ) - Button( -// modifier = Modifier.padding(vertical = 10.dp), - onClick = {} - ) { - Text("Загрузить") - } - } -} diff --git a/app/src/main/kotlin/repo/JsonRepository.kt b/lib/src/main/kotlin/repository/JsonRepository.kt similarity index 88% rename from app/src/main/kotlin/repo/JsonRepository.kt rename to lib/src/main/kotlin/repository/JsonRepository.kt index f460cf6..98f00eb 100644 --- a/app/src/main/kotlin/repo/JsonRepository.kt +++ b/lib/src/main/kotlin/repository/JsonRepository.kt @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo +package repository -import JsonNode -import JsonTree -import app.trees.AbstractTree -import app.trees.nodes.AbstractNode import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import repo.serialization.SerializableNode -import repo.serialization.SerializableTree -import repo.serialization.strategies.Serialization +import repository.jsonEntities.JsonNode +import repository.jsonEntities.JsonTree +import repository.serialization.SerializableNode +import repository.serialization.SerializableTree +import repository.serialization.strategies.Serialization +import trees.AbstractTree +import trees.nodes.AbstractNode import java.io.File import java.io.FileNotFoundException diff --git a/app/src/main/kotlin/repo/Neo4jRepository.kt b/lib/src/main/kotlin/repository/Neo4jRepository.kt similarity index 90% rename from app/src/main/kotlin/repo/Neo4jRepository.kt rename to lib/src/main/kotlin/repository/Neo4jRepository.kt index 79a846b..25d6993 100644 --- a/app/src/main/kotlin/repo/Neo4jRepository.kt +++ b/lib/src/main/kotlin/repository/Neo4jRepository.kt @@ -3,20 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo +package repository -import app.trees.AbstractTree -import app.trees.nodes.AbstractNode import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.cypher.ComparisonOperator import org.neo4j.ogm.cypher.Filter import org.neo4j.ogm.cypher.Filters import org.neo4j.ogm.session.SessionFactory -import repo.neo4jEntities.Neo4jNodeEntity -import repo.neo4jEntities.Neo4jTreeEntity -import repo.serialization.SerializableNode -import repo.serialization.SerializableTree -import repo.serialization.strategies.Serialization +import repository.neo4jEntities.Neo4jNodeEntity +import repository.neo4jEntities.Neo4jTreeEntity +import repository.serialization.SerializableNode +import repository.serialization.SerializableTree +import repository.serialization.strategies.Serialization +import trees.AbstractTree +import trees.nodes.AbstractNode class Neo4jRepo, NodeType : AbstractNode, @@ -24,7 +24,7 @@ class Neo4jRepo, strategy: Serialization, configuration: Configuration ) : Repository(strategy) { - private val sessionFactory = SessionFactory(configuration, "repo") + private val sessionFactory = SessionFactory(configuration, "repository") private val session = sessionFactory.openSession() private fun Neo4jNodeEntity.toSerializableNode(): SerializableNode { diff --git a/app/src/main/kotlin/repo/Repository.kt b/lib/src/main/kotlin/repository/Repository.kt similarity index 81% rename from app/src/main/kotlin/repo/Repository.kt rename to lib/src/main/kotlin/repository/Repository.kt index d8df68e..8cce2ff 100644 --- a/app/src/main/kotlin/repo/Repository.kt +++ b/lib/src/main/kotlin/repository/Repository.kt @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo +package repository -import app.trees.AbstractTree -import app.trees.nodes.AbstractNode -import repo.serialization.SerializableNode -import repo.serialization.SerializableTree -import repo.serialization.strategies.Serialization +import repository.serialization.SerializableNode +import repository.serialization.SerializableTree +import repository.serialization.strategies.Serialization +import trees.AbstractTree +import trees.nodes.AbstractNode abstract class Repository, NodeType : AbstractNode, diff --git a/app/src/main/kotlin/repo/SQLRepository.kt b/lib/src/main/kotlin/repository/SQLRepository.kt similarity index 86% rename from app/src/main/kotlin/repo/SQLRepository.kt rename to lib/src/main/kotlin/repository/SQLRepository.kt index 5232333..c5b21cc 100644 --- a/app/src/main/kotlin/repo/SQLRepository.kt +++ b/lib/src/main/kotlin/repository/SQLRepository.kt @@ -3,25 +3,25 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo +package repository -import NodesTable -import SQLNodeEntity -import SQLTreeEntity -import TreesTable -import app.trees.AbstractTree -import app.trees.nodes.AbstractNode import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.transactions.transaction -import repo.serialization.Metadata -import repo.serialization.SerializableNode -import repo.serialization.SerializableTree -import repo.serialization.SerializableValue -import repo.serialization.strategies.Serialization +import repository.serialization.Metadata +import repository.serialization.SerializableNode +import repository.serialization.SerializableTree +import repository.serialization.SerializableValue +import repository.serialization.strategies.Serialization +import repository.sqliteEntities.NodesTable +import repository.sqliteEntities.SQLNodeEntity +import repository.sqliteEntities.SQLTreeEntity +import repository.sqliteEntities.TreesTable +import trees.AbstractTree +import trees.nodes.AbstractNode class SQLRepository, NodeType : AbstractNode, diff --git a/app/src/main/kotlin/repo/jsonEntities/JsonNode.kt b/lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt similarity index 68% rename from app/src/main/kotlin/repo/jsonEntities/JsonNode.kt rename to lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt index dfcf70d..49c6e5b 100644 --- a/app/src/main/kotlin/repo/jsonEntities/JsonNode.kt +++ b/lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt @@ -3,9 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ +package repository.jsonEntities + import kotlinx.serialization.Serializable -import repo.serialization.Metadata -import repo.serialization.SerializableValue +import repository.serialization.Metadata +import repository.serialization.SerializableValue @Serializable diff --git a/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt b/lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt similarity index 75% rename from app/src/main/kotlin/repo/jsonEntities/JsonTree.kt rename to lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt index 6fa3dc7..83ab485 100644 --- a/app/src/main/kotlin/repo/jsonEntities/JsonTree.kt +++ b/lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt @@ -3,9 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ +package repository.jsonEntities + import kotlinx.serialization.Serializable -import repo.serialization.TypeOfTree +import repository.serialization.TypeOfTree @Serializable data class JsonTree( diff --git a/app/src/main/kotlin/repo/neo4jEntities/Neo4jNodeEntity.kt b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt similarity index 81% rename from app/src/main/kotlin/repo/neo4jEntities/Neo4jNodeEntity.kt rename to lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt index 23ca642..d16a1dd 100644 --- a/app/src/main/kotlin/repo/neo4jEntities/Neo4jNodeEntity.kt +++ b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.neo4jEntities +package repository.neo4jEntities import org.neo4j.ogm.annotation.* -import repo.serialization.Metadata -import repo.serialization.SerializableValue +import repository.serialization.Metadata +import repository.serialization.SerializableValue @NodeEntity("Node") class Neo4jNodeEntity( diff --git a/app/src/main/kotlin/repo/neo4jEntities/Neo4jTreeEntity.kt b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt similarity index 85% rename from app/src/main/kotlin/repo/neo4jEntities/Neo4jTreeEntity.kt rename to lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt index 7ffd358..9601fd7 100644 --- a/app/src/main/kotlin/repo/neo4jEntities/Neo4jTreeEntity.kt +++ b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.neo4jEntities +package repository.neo4jEntities import org.neo4j.ogm.annotation.* -import repo.serialization.TypeOfTree +import repository.serialization.TypeOfTree @NodeEntity("Tree") class Neo4jTreeEntity( diff --git a/app/src/main/kotlin/repo/serialization/Serializable.kt b/lib/src/main/kotlin/repository/serialization/Serializable.kt similarity index 95% rename from app/src/main/kotlin/repo/serialization/Serializable.kt rename to lib/src/main/kotlin/repository/serialization/Serializable.kt index 7cdf85c..6d87aad 100644 --- a/app/src/main/kotlin/repo/serialization/Serializable.kt +++ b/lib/src/main/kotlin/repository/serialization/Serializable.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization +package repository.serialization import kotlinx.serialization.Serializable diff --git a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt b/lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt similarity index 76% rename from app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt rename to lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt index bf00f3a..b8d8bc5 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/AVLStrategy.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt @@ -3,14 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization.strategies +package repository.serialization.strategies -import app.trees.AVLTree -import app.trees.nodes.AVLNode -import repo.serialization.Metadata -import repo.serialization.SerializableNode -import repo.serialization.SerializableValue -import repo.serialization.TypeOfTree +import repository.serialization.Metadata +import repository.serialization.SerializableNode +import repository.serialization.SerializableValue +import repository.serialization.TypeOfTree +import trees.AVLTree +import trees.nodes.AVLNode class AVLStrategy>( serializeData: (T) -> SerializableValue, diff --git a/app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt b/lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt similarity index 74% rename from app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt rename to lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt index 852786c..b453bfd 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/BSStrategy.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt @@ -3,14 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization.strategies +package repository.serialization.strategies -import app.trees.BSTree -import app.trees.nodes.BSNode -import repo.serialization.Metadata -import repo.serialization.SerializableNode -import repo.serialization.SerializableValue -import repo.serialization.TypeOfTree +import repository.serialization.Metadata +import repository.serialization.SerializableNode +import repository.serialization.SerializableValue +import repository.serialization.TypeOfTree +import trees.BSTree +import trees.nodes.BSNode class BSStrategy>( serializeData: (T) -> SerializableValue, diff --git a/app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt b/lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt similarity index 80% rename from app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt rename to lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt index 790599e..78983cf 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/RBStrategy.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt @@ -3,15 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization.strategies +package repository.serialization.strategies -import app.trees.RBTree -import app.trees.nodes.Color -import app.trees.nodes.RBNode -import repo.serialization.Metadata -import repo.serialization.SerializableNode -import repo.serialization.SerializableValue -import repo.serialization.TypeOfTree +import repository.serialization.Metadata +import repository.serialization.SerializableNode +import repository.serialization.SerializableValue +import repository.serialization.TypeOfTree +import trees.RBTree +import trees.nodes.Color +import trees.nodes.RBNode class RBStrategy>( serializeData: (T) -> SerializableValue, diff --git a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt b/lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt similarity index 67% rename from app/src/main/kotlin/repo/serialization/strategies/Serialization.kt rename to lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt index 9855540..2a5442c 100644 --- a/app/src/main/kotlin/repo/serialization/strategies/Serialization.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt @@ -3,14 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repo.serialization.strategies +package repository.serialization.strategies -import app.trees.AbstractTree -import app.trees.nodes.AbstractNode -import repo.serialization.Metadata -import repo.serialization.SerializableNode -import repo.serialization.SerializableValue -import repo.serialization.TypeOfTree +import repository.serialization.Metadata +import repository.serialization.SerializableNode +import repository.serialization.SerializableValue +import repository.serialization.TypeOfTree +import trees.AbstractTree +import trees.nodes.AbstractNode abstract class Serialization, diff --git a/app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt b/lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt similarity index 96% rename from app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt rename to lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt index 47181f4..042b437 100644 --- a/app/src/main/kotlin/repo/sqliteEntities/SQLNodeEntity.kt +++ b/lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package repository.sqliteEntities + import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID diff --git a/app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt b/lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt similarity index 95% rename from app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt rename to lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt index 58b1f33..78449f1 100644 --- a/app/src/main/kotlin/repo/sqliteEntities/SQLTreeEntity.kt +++ b/lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +package repository.sqliteEntities + import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID diff --git a/app/src/main/kotlin/app/trees/AVLTree.kt b/lib/src/main/kotlin/trees/AVLTree.kt similarity index 95% rename from app/src/main/kotlin/app/trees/AVLTree.kt rename to lib/src/main/kotlin/trees/AVLTree.kt index 505a9cf..7a4e80f 100644 --- a/app/src/main/kotlin/app/trees/AVLTree.kt +++ b/lib/src/main/kotlin/trees/AVLTree.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees +package trees -import app.trees.nodes.AVLNode +import trees.nodes.AVLNode class AVLTree> : AbstractTree>() { @@ -34,7 +34,7 @@ class AVLTree> : AbstractTree>() { if (balanceFactor > 1) { if ((initNode.right?.balanceFactor() ?: 0) < 0) { - initNode.right = initNode.right?.let { rotateRight(it) } // right = right?.rotateRight() + initNode.right = initNode.right?.let { rotateRight(it) } initNode.right?.updateHeight() } val node = rotateLeft(initNode) diff --git a/app/src/main/kotlin/app/trees/AbstractTree.kt b/lib/src/main/kotlin/trees/AbstractTree.kt similarity index 97% rename from app/src/main/kotlin/app/trees/AbstractTree.kt rename to lib/src/main/kotlin/trees/AbstractTree.kt index 5d30142..f8c10d8 100644 --- a/app/src/main/kotlin/app/trees/AbstractTree.kt +++ b/lib/src/main/kotlin/trees/AbstractTree.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees +package trees -import app.trees.interfaces.Tree -import app.trees.nodes.AbstractNode +import trees.interfaces.Tree +import trees.nodes.AbstractNode abstract class AbstractTree, NodeType : AbstractNode> : Tree { var root: NodeType? = null diff --git a/app/src/main/kotlin/app/trees/BSTree.kt b/lib/src/main/kotlin/trees/BSTree.kt similarity index 91% rename from app/src/main/kotlin/app/trees/BSTree.kt rename to lib/src/main/kotlin/trees/BSTree.kt index afa901c..c9042c6 100644 --- a/app/src/main/kotlin/app/trees/BSTree.kt +++ b/lib/src/main/kotlin/trees/BSTree.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees +package trees -import app.trees.nodes.BSNode +import trees.nodes.BSNode class BSTree> : AbstractTree>() { diff --git a/app/src/main/kotlin/app/trees/KeyValue.kt b/lib/src/main/kotlin/trees/KeyValue.kt similarity index 97% rename from app/src/main/kotlin/app/trees/KeyValue.kt rename to lib/src/main/kotlin/trees/KeyValue.kt index 1306808..e22bf21 100644 --- a/app/src/main/kotlin/app/trees/KeyValue.kt +++ b/lib/src/main/kotlin/trees/KeyValue.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees +package trees class KeyValue, V>( val key: K, diff --git a/app/src/main/kotlin/app/trees/RBTree.kt b/lib/src/main/kotlin/trees/RBTree.kt similarity index 99% rename from app/src/main/kotlin/app/trees/RBTree.kt rename to lib/src/main/kotlin/trees/RBTree.kt index 4d7a565..942b9d6 100644 --- a/app/src/main/kotlin/app/trees/RBTree.kt +++ b/lib/src/main/kotlin/trees/RBTree.kt @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees +package trees -import app.trees.nodes.Color -import app.trees.nodes.RBNode +import trees.nodes.Color +import trees.nodes.RBNode class RBTree> : AbstractTree>() { override fun add(data: T) { diff --git a/app/src/main/kotlin/app/trees/interfaces/Node.kt b/lib/src/main/kotlin/trees/interfaces/Node.kt similarity index 90% rename from app/src/main/kotlin/app/trees/interfaces/Node.kt rename to lib/src/main/kotlin/trees/interfaces/Node.kt index 8f4aa58..bd2bf00 100644 --- a/app/src/main/kotlin/app/trees/interfaces/Node.kt +++ b/lib/src/main/kotlin/trees/interfaces/Node.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.interfaces +package trees.interfaces interface Node, Subtype : Node> : Comparable> { val data: T diff --git a/app/src/main/kotlin/app/trees/interfaces/Tree.kt b/lib/src/main/kotlin/trees/interfaces/Tree.kt similarity index 87% rename from app/src/main/kotlin/app/trees/interfaces/Tree.kt rename to lib/src/main/kotlin/trees/interfaces/Tree.kt index 965975c..8edd59b 100644 --- a/app/src/main/kotlin/app/trees/interfaces/Tree.kt +++ b/lib/src/main/kotlin/trees/interfaces/Tree.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.interfaces +package trees.interfaces interface Tree> { fun add(data: T) diff --git a/app/src/main/kotlin/app/trees/nodes/AVLNode.kt b/lib/src/main/kotlin/trees/nodes/AVLNode.kt similarity index 96% rename from app/src/main/kotlin/app/trees/nodes/AVLNode.kt rename to lib/src/main/kotlin/trees/nodes/AVLNode.kt index e8112d6..e3e1d76 100644 --- a/app/src/main/kotlin/app/trees/nodes/AVLNode.kt +++ b/lib/src/main/kotlin/trees/nodes/AVLNode.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.nodes +package trees.nodes class AVLNode>( override var data: T, diff --git a/app/src/main/kotlin/app/trees/nodes/AbstractNode.kt b/lib/src/main/kotlin/trees/nodes/AbstractNode.kt similarity index 86% rename from app/src/main/kotlin/app/trees/nodes/AbstractNode.kt rename to lib/src/main/kotlin/trees/nodes/AbstractNode.kt index d22f3e5..ef8df78 100644 --- a/app/src/main/kotlin/app/trees/nodes/AbstractNode.kt +++ b/lib/src/main/kotlin/trees/nodes/AbstractNode.kt @@ -3,12 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.nodes/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ +package trees.nodes -import app.trees.interfaces.Node +import trees.interfaces.Node abstract class AbstractNode, Subtype : AbstractNode> : Node { abstract override var data: T diff --git a/app/src/main/kotlin/app/trees/nodes/BSNode.kt b/lib/src/main/kotlin/trees/nodes/BSNode.kt similarity index 92% rename from app/src/main/kotlin/app/trees/nodes/BSNode.kt rename to lib/src/main/kotlin/trees/nodes/BSNode.kt index c21971a..bc2ea2a 100644 --- a/app/src/main/kotlin/app/trees/nodes/BSNode.kt +++ b/lib/src/main/kotlin/trees/nodes/BSNode.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.nodes +package trees.nodes class BSNode>( override var data: T, diff --git a/app/src/main/kotlin/app/trees/nodes/RBNode.kt b/lib/src/main/kotlin/trees/nodes/RBNode.kt similarity index 94% rename from app/src/main/kotlin/app/trees/nodes/RBNode.kt rename to lib/src/main/kotlin/trees/nodes/RBNode.kt index 338ce8b..2832552 100644 --- a/app/src/main/kotlin/app/trees/nodes/RBNode.kt +++ b/lib/src/main/kotlin/trees/nodes/RBNode.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package app.trees.nodes +package trees.nodes enum class Color { RED, BLACK diff --git a/app/src/test/kotlin/trees/AVLTreeTest.kt b/lib/src/test/kotlin/trees/AVLTreeTest.kt similarity index 97% rename from app/src/test/kotlin/trees/AVLTreeTest.kt rename to lib/src/test/kotlin/trees/AVLTreeTest.kt index fda7bca..f195599 100644 --- a/app/src/test/kotlin/trees/AVLTreeTest.kt +++ b/lib/src/test/kotlin/trees/AVLTreeTest.kt @@ -5,9 +5,7 @@ package trees -import app.trees.AVLTree -import app.trees.KeyValue -import app.trees.nodes.AVLNode +import trees.nodes.AVLNode import kotlin.random.Random import kotlin.test.* diff --git a/app/src/test/kotlin/trees/BSTreeTest.kt b/lib/src/test/kotlin/trees/BSTreeTest.kt similarity index 97% rename from app/src/test/kotlin/trees/BSTreeTest.kt rename to lib/src/test/kotlin/trees/BSTreeTest.kt index fe0edb5..9b07f03 100644 --- a/app/src/test/kotlin/trees/BSTreeTest.kt +++ b/lib/src/test/kotlin/trees/BSTreeTest.kt @@ -5,9 +5,7 @@ package trees -import app.trees.BSTree -import app.trees.KeyValue -import app.trees.nodes.BSNode +import trees.nodes.BSNode import kotlin.random.Random import kotlin.test.* diff --git a/app/src/test/kotlin/trees/InvariantChecker.kt b/lib/src/test/kotlin/trees/InvariantChecker.kt similarity index 95% rename from app/src/test/kotlin/trees/InvariantChecker.kt rename to lib/src/test/kotlin/trees/InvariantChecker.kt index 1aa7678..3d623a6 100644 --- a/app/src/test/kotlin/trees/InvariantChecker.kt +++ b/lib/src/test/kotlin/trees/InvariantChecker.kt @@ -8,11 +8,10 @@ */ package trees -import app.trees.RBTree -import app.trees.interfaces.Node -import app.trees.nodes.AVLNode -import app.trees.nodes.Color -import app.trees.nodes.RBNode +import trees.interfaces.Node +import trees.nodes.AVLNode +import trees.nodes.Color +import trees.nodes.RBNode import kotlin.math.abs object InvariantChecker { diff --git a/app/src/test/kotlin/trees/RBTreeTest.kt b/lib/src/test/kotlin/trees/RBTreeTest.kt similarity index 97% rename from app/src/test/kotlin/trees/RBTreeTest.kt rename to lib/src/test/kotlin/trees/RBTreeTest.kt index 38c0e3c..8b5ff18 100644 --- a/app/src/test/kotlin/trees/RBTreeTest.kt +++ b/lib/src/test/kotlin/trees/RBTreeTest.kt @@ -6,10 +6,8 @@ package trees -import app.trees.KeyValue -import app.trees.RBTree -import app.trees.nodes.Color -import app.trees.nodes.RBNode +import trees.nodes.Color +import trees.nodes.RBNode import kotlin.random.Random import kotlin.test.* From 934d808cf8efcb1d85ae23858ae6c6a8a2403b6e Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:08:24 +0300 Subject: [PATCH 76/90] feat: add comments to code to `repository` part --- .../main/kotlin/repository/JsonRepository.kt | 21 +++++++++++----- .../main/kotlin/repository/Neo4jRepository.kt | 9 +++++++ lib/src/main/kotlin/repository/Repository.kt | 15 ++++++++++- .../main/kotlin/repository/SQLRepository.kt | 18 +++++++++---- .../repository/jsonEntities/JsonNode.kt | 7 +++++- .../repository/jsonEntities/JsonTree.kt | 6 ++++- .../neo4jEntities/Neo4jNodeEntity.kt | 9 ++++--- .../neo4jEntities/Neo4jTreeEntity.kt | 7 +++--- .../serialization/strategies/AVLStrategy.kt | 4 +++ .../serialization/strategies/BSStrategy.kt | 7 ++++-- .../serialization/strategies/RBStrategy.kt | 4 +++ .../serialization/strategies/Serialization.kt | 9 ++++++- .../sqliteEntities/SQLNodeEntity.kt | 25 +++++++++++++++++-- .../sqliteEntities/SQLTreeEntity.kt | 14 +++++++++-- 14 files changed, 128 insertions(+), 27 deletions(-) diff --git a/lib/src/main/kotlin/repository/JsonRepository.kt b/lib/src/main/kotlin/repository/JsonRepository.kt index 98f00eb..fc1b9d2 100644 --- a/lib/src/main/kotlin/repository/JsonRepository.kt +++ b/lib/src/main/kotlin/repository/JsonRepository.kt @@ -18,18 +18,20 @@ import trees.nodes.AbstractNode import java.io.File import java.io.FileNotFoundException - +//creates a new JsonRepository object with the given strategy and dirPath. strategy class JsonRepository, NodeType : AbstractNode, TreeType : AbstractTree> ( + //serialization strategy for working with trees and nodes. strategy: Serialization, - dirPath: String, - private val filename: String, + //path to the directory where tree files will be stored in JSON format. + dirPath: String ) : Repository(strategy) { private val dirPath = "$dirPath/${strategy.typeOfTree.name.lowercase()}" + //function to convert JsonNode to SerializableNode. private fun JsonNode.toSerializableNode(): SerializableNode { return SerializableNode( data, @@ -39,6 +41,7 @@ class JsonRepository, ) } + //a function to deserialize a JsonNode into a tree node private fun JsonNode.deserialize(parent: NodeType? = null): NodeType? { val node = strategy.createNode(this.toSerializableNode()) node?.parent = parent @@ -47,6 +50,7 @@ class JsonRepository, return node } + //function to convert SerializableNode to JsonNode. private fun SerializableNode.toJsonNode(): JsonNode { return JsonNode( data, @@ -56,6 +60,7 @@ class JsonRepository, ) } + //function to convert SerializableTree to JsonTree private fun SerializableTree.toJsonTree(): JsonTree { return JsonTree( name, @@ -64,14 +69,16 @@ class JsonRepository, ) } + //method for getting a list of tree names. override fun getNames(): List = File(dirPath).listFiles()?.map { Json.decodeFromString(it.readText()).name } ?: listOf() + //method to load a tree by name override fun loadByName(name: String): TreeType? { val json = try { - File(dirPath, "${name.hashCode()}.json").readText() + File(dirPath, "${name}.json").readText() } catch (_: FileNotFoundException) { return null } @@ -82,17 +89,19 @@ class JsonRepository, } } + //method to save tree by name override fun save(name: String, tree: TreeType) { val jsonTree = tree.toSerializableTree(name).toJsonTree() File(dirPath).mkdirs() - File(dirPath, filename).run { + File(dirPath, "${name}.json").run { createNewFile() writeText(Json.encodeToString(jsonTree)) } } + //a method for deleting a tree by name override fun deleteByName(name: String) { - File(dirPath, "${name.hashCode()}.json").delete() + File(dirPath, "${name}.json").delete() } } \ No newline at end of file diff --git a/lib/src/main/kotlin/repository/Neo4jRepository.kt b/lib/src/main/kotlin/repository/Neo4jRepository.kt index 25d6993..5c4c066 100644 --- a/lib/src/main/kotlin/repository/Neo4jRepository.kt +++ b/lib/src/main/kotlin/repository/Neo4jRepository.kt @@ -27,6 +27,7 @@ class Neo4jRepo, private val sessionFactory = SessionFactory(configuration, "repository") private val session = sessionFactory.openSession() + //converts Neo4jNodeEntity to SerializableNode. private fun Neo4jNodeEntity.toSerializableNode(): SerializableNode { return SerializableNode( data, @@ -36,6 +37,7 @@ class Neo4jRepo, ) } + //converts Neo4jNodeEntity to a node private fun Neo4jNodeEntity.deserialize(parent: NodeType? = null): NodeType? { val node = strategy.createNode(this.toSerializableNode()) node?.parent = parent @@ -44,6 +46,7 @@ class Neo4jRepo, return node } + //converts SerializableTree to Neo4jTreeEntity. private fun SerializableNode.toEntity(): Neo4jNodeEntity { return Neo4jNodeEntity( data, @@ -53,6 +56,7 @@ class Neo4jRepo, ) } + //converts Neo4jTreeEntity to SerializableTree. private fun Neo4jTreeEntity.toTree(): SerializableTree { return SerializableTree( name, @@ -69,12 +73,14 @@ class Neo4jRepo, ) } + //saves the tree with the specified name to the database. override fun save(name: String, tree: TreeType) { deleteByName(name) val entityTree = tree.toSerializableTree(name).toEntity() session.save(entityTree) } + //is used to get a list of trees from the database that match the specified filtering options. private fun findByVerboseName(name: String) = session.loadAll( Neo4jTreeEntity::class.java, Filters().and( @@ -85,6 +91,7 @@ class Neo4jRepo, -1 ) + //loads the tree with the specified name from the database. override fun loadByName(name: String): TreeType { val tree = findByVerboseName(name).singleOrNull() val result = strategy.createTree().apply { @@ -93,6 +100,7 @@ class Neo4jRepo, return result } + //removes the tree with the specified name from the database. override fun deleteByName(name: String) { session.query( "MATCH toDelete=(" + @@ -102,6 +110,7 @@ class Neo4jRepo, ) } + //returns a list of the names of all saved trees in the database. override fun getNames(): List = session.loadAll( Neo4jTreeEntity::class.java, Filter("typeOfTree", ComparisonOperator.EQUALS, strategy.typeOfTree), diff --git a/lib/src/main/kotlin/repository/Repository.kt b/lib/src/main/kotlin/repository/Repository.kt index 8cce2ff..8601a85 100644 --- a/lib/src/main/kotlin/repository/Repository.kt +++ b/lib/src/main/kotlin/repository/Repository.kt @@ -16,6 +16,8 @@ abstract class Repository, TreeType : AbstractTree>( protected val strategy: Serialization ) { + //An extension function that converts an instance of a NodeType to a serializable + //representation of a SerializableNode using the serialization strategy protected fun NodeType.toSerializableNode(): SerializableNode { return SerializableNode( strategy.serializeValue(this.data), @@ -25,7 +27,9 @@ abstract class Repository, ) } - + //An extension function that converts a TreeType instance to a serializable + // representation of a SerializableTree using the serialization strategy. + // The name of the tree is given by the name parameter protected fun TreeType.toSerializableTree(name: String): SerializableTree { return SerializableTree( name = name, @@ -34,8 +38,17 @@ abstract class Repository, ) } + //an abstract method that saves an instance of tree in the repository under the given name abstract fun save(name: String, tree: TreeType) + + //abstract method that loads a tree instance from the repository given name. + // If no tree with the specified name is found, the method should return null. abstract fun loadByName(name: String): TreeType? + + //an abstract method that removes a tree instance from the repository at the given name abstract fun deleteByName(name: String) + + //abstract method that returns a list of the names of all trees stored in the repository. + // If there are no trees in the repository, the method should return an empty list abstract fun getNames(): List } \ No newline at end of file diff --git a/lib/src/main/kotlin/repository/SQLRepository.kt b/lib/src/main/kotlin/repository/SQLRepository.kt index c5b21cc..07f9943 100644 --- a/lib/src/main/kotlin/repository/SQLRepository.kt +++ b/lib/src/main/kotlin/repository/SQLRepository.kt @@ -5,6 +5,10 @@ package repository +import NodesTable +import SQLNodeEntity +import SQLTreeEntity +import TreesTable import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq @@ -16,10 +20,6 @@ import repository.serialization.SerializableNode import repository.serialization.SerializableTree import repository.serialization.SerializableValue import repository.serialization.strategies.Serialization -import repository.sqliteEntities.NodesTable -import repository.sqliteEntities.SQLNodeEntity -import repository.sqliteEntities.SQLTreeEntity -import repository.sqliteEntities.TreesTable import trees.AbstractTree import trees.nodes.AbstractNode @@ -32,6 +32,7 @@ class SQLRepository, private val typeOfTree = strategy.typeOfTree.toString() + //Initializes the database tables for storing trees and nodes. init { transaction(db) { SchemaUtils.create(TreesTable) @@ -39,6 +40,7 @@ class SQLRepository, } } + //Converts an instance of SQLNodeEntity to a SerializableNode object using the serialization strategy. private fun SQLNodeEntity.toSerializableNode(): SerializableNode { return SerializableNode( SerializableValue(data), @@ -48,6 +50,7 @@ class SQLRepository, ) } + //Deserializes an instance of SQLNodeEntity to a NodeType object using the serialization strategy. private fun SQLNodeEntity.deserialize(parent: NodeType? = null): NodeType? { val node = strategy.createNode(this.toSerializableNode()) node?.parent = parent @@ -56,6 +59,7 @@ class SQLRepository, return node } + //Converts a SerializableNode object to an instance of SQLNodeEntity using the serialization strategy. private fun SerializableNode.toEntity(tree: SQLTreeEntity): SQLNodeEntity = SQLNodeEntity.new { this@new.data = this@toEntity.data.value this@new.metadata = this@toEntity.metadata.value @@ -64,6 +68,7 @@ class SQLRepository, this.tree = tree } + //Converts a SerializableTree object to an instance of SQLTreeEntity. private fun SerializableTree.toEntity(): SQLTreeEntity { return SQLTreeEntity.new { this.name = this@toEntity.name @@ -71,13 +76,14 @@ class SQLRepository, } } + //Saves a TreeType object to the database with a given name by deleting any existing tree with the same name and creating a new one. override fun save(name: String, tree: TreeType): Unit = transaction(db) { deleteByName(name) val entityTree = tree.toSerializableTree(name).toEntity() entityTree.root = tree.root?.toSerializableNode()?.toEntity(entityTree) } - + //Loads a TreeType object from the database by name, if it exists. override fun loadByName(name: String): TreeType? = transaction(db) { SQLTreeEntity.find( TreesTable.typeOfTree eq typeOfTree and (TreesTable.name eq name) @@ -86,6 +92,7 @@ class SQLRepository, } } + //Deletes a tree from the database by name. override fun deleteByName(name: String): Unit = transaction(db) { val treeId = SQLTreeEntity.find( TreesTable.typeOfTree eq strategy.typeOfTree.toString() and (TreesTable.name eq name) @@ -98,6 +105,7 @@ class SQLRepository, } } + //Returns a list of names of all the trees in the database. override fun getNames(): List = transaction(db) { SQLTreeEntity.find(TreesTable.typeOfTree eq typeOfTree).map(SQLTreeEntity::name) } diff --git a/lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt b/lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt index 49c6e5b..fd10028 100644 --- a/lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt +++ b/lib/src/main/kotlin/repository/jsonEntities/JsonNode.kt @@ -9,11 +9,16 @@ import kotlinx.serialization.Serializable import repository.serialization.Metadata import repository.serialization.SerializableValue - +//A class that represents a tree node in JSON format. Contains @Serializable data class JsonNode( + //Node value in serializable format; val data: SerializableValue, + //Node metadata, including node height, balancing factor, + //and other information needed to work with the tree; val metadata: Metadata, + //Link to the left child node. val left: JsonNode?, + //Link to the right child node. val right: JsonNode? ) \ No newline at end of file diff --git a/lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt b/lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt index 83ab485..3540f62 100644 --- a/lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt +++ b/lib/src/main/kotlin/repository/jsonEntities/JsonTree.kt @@ -5,13 +5,17 @@ package repository.jsonEntities - import kotlinx.serialization.Serializable import repository.serialization.TypeOfTree +//Describes the "repository.jsonEntities.JsonTree" object - this is the tree +// that will be serialized and stored in JSON format @Serializable data class JsonTree( + //a string that contains the name of the tree. val name: String, + //contains a tree type, which can be one of the "TypeOfTree" enums. val typeOfTree: TypeOfTree, + //This is a reference to the root node of the tree, which can either be a "repository.jsonEntities.JsonNode" object val root: JsonNode? ) \ No newline at end of file diff --git a/lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt index d16a1dd..d7636ef 100644 --- a/lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt +++ b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jNodeEntity.kt @@ -11,18 +11,21 @@ import repository.serialization.SerializableValue @NodeEntity("Node") class Neo4jNodeEntity( + + //serializable node value stored in the database as a string. @Property("data") var data: SerializableValue, - + //node metadata stored in the database as a string. @Property("metadata") var metadata: Metadata, - + //reference to the left child node in the tree structure. @Relationship(type = "LEFT", direction = Relationship.Direction.OUTGOING) var left: Neo4jNodeEntity? = null, - + //reference to the right child node in the tree structure. @Relationship(type = "RIGHT", direction = Relationship.Direction.OUTGOING) var right: Neo4jNodeEntity? = null, ) { + //the node ID generated by the database on save. @Id @GeneratedValue var id: Long? = null diff --git a/lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt index 9601fd7..da90a7a 100644 --- a/lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt +++ b/lib/src/main/kotlin/repository/neo4jEntities/Neo4jTreeEntity.kt @@ -10,16 +10,17 @@ import repository.serialization.TypeOfTree @NodeEntity("Tree") class Neo4jTreeEntity( + //a class property that stores the name of the tree. @Property("name") var name: String, - + //a class property that stores the tree type. @Property("typeOfTree") var typeOfTree: TypeOfTree, - + //a class property that stores a reference to the root node of the tree. @Relationship(type = "ROOT", direction = Relationship.Direction.OUTGOING) var root: Neo4jNodeEntity?, ) { - + //a class property that stores the tree ID in the Neo4j database. @Id @GeneratedValue var id: Long? = null diff --git a/lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt b/lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt index b8d8bc5..fb4e127 100644 --- a/lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/AVLStrategy.kt @@ -18,6 +18,7 @@ class AVLStrategy>( ) : Serialization, AVLTree, Int>(serializeData, deserializeData) { override val typeOfTree: TypeOfTree = TypeOfTree.AVL_TREE + //method that creates an AVL tree node from the passed SerializableNode, which was obtained as a result of serialization. override fun createNode(node: SerializableNode?): AVLNode? = node?.let { AVLNode( data = deserializeValue(node.data), @@ -27,9 +28,12 @@ class AVLStrategy>( ) } + //method that deserializes the node's metadata (in this case, the node's height). Returns the deserialized metadata. override fun deserializeMetadata(metadata: Metadata) = metadata.value.toInt() + //method that serializes the node's metadata (in this case, the node's height). Returns the serialized metadata. override fun serializeMetadata(node: AVLNode) = Metadata(node.height.toString()) + //method that creates an AVL tree. Returns a new AVL tree of type AVLTree. override fun createTree() = AVLTree() } diff --git a/lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt b/lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt index b453bfd..46664e4 100644 --- a/lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/BSStrategy.kt @@ -13,11 +13,11 @@ import trees.BSTree import trees.nodes.BSNode class BSStrategy>( - serializeData: (T) -> SerializableValue, - deserializeData: (SerializableValue) -> T + serializeData: (T) -> SerializableValue, deserializeData: (SerializableValue) -> T ) : Serialization, BSTree, Int>(serializeData, deserializeData) { override val typeOfTree: TypeOfTree = TypeOfTree.BS_TREE + //method that creates an AVL tree node from the passed SerializableNode, which was obtained as a result of serialization. override fun createNode(node: SerializableNode?): BSNode? = node?.let { BSNode( data = deserializeValue(node.data), @@ -26,9 +26,12 @@ class BSStrategy>( ) } + //method that deserializes the node's metadata (in this case, the node's height). Returns the deserialized metadata. override fun deserializeMetadata(metadata: Metadata) = metadata.value.toInt() + //method that serializes the node's metadata (in this case, the node's height). Returns the serialized metadata. override fun serializeMetadata(node: BSNode) = Metadata("") + //method that creates an AVL tree. Returns a new tree of type BSTree. override fun createTree() = BSTree() } diff --git a/lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt b/lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt index 78983cf..d306e56 100644 --- a/lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/RBStrategy.kt @@ -19,6 +19,7 @@ class RBStrategy>( ) : Serialization, RBTree, Color>(serializeData, deserializeData) { override val typeOfTree: TypeOfTree = TypeOfTree.RB_TREE + //method that creates an AVL tree node from the passed SerializableNode, which was obtained as a result of serialization. override fun createNode(node: SerializableNode?): RBNode? = node?.let { RBNode( data = deserializeValue(node.data), @@ -28,6 +29,7 @@ class RBStrategy>( ) } + //method that deserializes the node's metadata (in this case, the node's height). Returns the deserialized metadata. override fun deserializeMetadata(metadata: Metadata): Color { return when (metadata.value) { "RED" -> Color.RED @@ -36,6 +38,7 @@ class RBStrategy>( } } + //method that serializes the node's metadata (in this case, the node's height). Returns the serialized metadata. override fun serializeMetadata(node: RBNode): Metadata { return Metadata( when (node.color) { @@ -45,5 +48,6 @@ class RBStrategy>( ) } + //method that creates an AVL tree. Returns a new tree of type RBTree. override fun createTree() = RBTree() } diff --git a/lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt b/lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt index 2a5442c..769db59 100644 --- a/lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt +++ b/lib/src/main/kotlin/repository/serialization/strategies/Serialization.kt @@ -12,7 +12,6 @@ import repository.serialization.TypeOfTree import trees.AbstractTree import trees.nodes.AbstractNode - abstract class Serialization, NodeType : AbstractNode, TreeType : AbstractTree, @@ -21,8 +20,16 @@ abstract class Serialization, val deserializeValue: (SerializableValue) -> T ) { abstract val typeOfTree: TypeOfTree + + //abstract method that creates an AVL tree node from the passed SerializableNode, which was obtained as a result of serialization. abstract fun createNode(node: SerializableNode?): NodeType? + + //abstract method that deserializes the node's metadata (in this case, the node's height). Returns the deserialized metadata. abstract fun deserializeMetadata(metadata: Metadata): M + + //abstract method that serializes the node's metadata (in this case, the node's height). Returns the serialized metadata. abstract fun serializeMetadata(node: NodeType): Metadata + + //abstract method that creates an AVL tree. Returns a new tree of type Tree. abstract fun createTree(): TreeType } \ No newline at end of file diff --git a/lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt b/lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt index 042b437..49741ab 100644 --- a/lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt +++ b/lib/src/main/kotlin/repository/sqliteEntities/SQLNodeEntity.kt @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repository.sqliteEntities - import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID @@ -12,19 +10,42 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption class SQLNodeEntity(id: EntityID) : IntEntity(id) { + //companion object of a class that inherits from IntEntityClass + //and defines the base class for the entity. companion object : IntEntityClass(NodesTable) + //a variable responsible for storing node data in the database. var data by NodesTable.data + + //a variable responsible for storing the node's metadata in the database. var metadata by NodesTable.metadata + + //a variable responsible for storing a link to the left node of the tree in the database. var left by SQLNodeEntity optionalReferencedOn NodesTable.left + + //a variable responsible for storing a link to the right node of the tree in the database. var right by SQLNodeEntity optionalReferencedOn NodesTable.right + + //variable responsible for saving the link to the tree in the database. var tree by SQLTreeEntity referencedOn NodesTable.tree } internal object NodesTable : IntIdTable("nodes") { + //table column containing node data. val data = text("data") + + //a table column containing node metadata. val metadata = text("metadata") + + //table column containing a link to the left node of the tree. + // It may be undefined if there is no left node. val left = reference("left_id", NodesTable).nullable() + + //table column containing a link to the right node of the tree. + //It may be undefined if the right node is missing. val right = reference("right_id", NodesTable).nullable() + + //a table column containing a link to the tree. + // When a tree is removed from the database, all nodes associated with it are also removed. val tree = reference("tree_id", TreesTable, onDelete = ReferenceOption.CASCADE) } \ No newline at end of file diff --git a/lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt b/lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt index 78449f1..10a1ce2 100644 --- a/lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt +++ b/lib/src/main/kotlin/repository/sqliteEntities/SQLTreeEntity.kt @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -package repository.sqliteEntities - import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID @@ -13,16 +11,28 @@ import org.jetbrains.exposed.dao.id.IntIdTable class SQLTreeEntity(id: EntityID) : IntEntity(id) { companion object : IntEntityClass(TreesTable) + //tree name var name by TreesTable.name + + //tree type var typeOfTree by TreesTable.typeOfTree + + //root node of the tree, refers to a node in the node table var root by SQLNodeEntity optionalReferencedOn TreesTable.root } +//an object containing metadata for the trees table in the database. internal object TreesTable : IntIdTable("trees") { + //tree name val name = text("name") + + //tree type val typeOfTree = text("type") + + //root node of the tree, refers to a node in the node table val root = reference("root_node_id", NodesTable).nullable() + //a table initializer that sets a unique index by tree name and type. init { uniqueIndex(name, typeOfTree) } From 2b6eb367cc2af9ccd50c49dc20e9093295b6892b Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:18:08 +0300 Subject: [PATCH 77/90] feat: add comments to code to `trees` package --- app/src/main/kotlin/composeApp/Controller.kt | 13 +-- .../main/kotlin/composeApp/DrawableNode.kt | 2 +- .../main/kotlin/composeApp/MainActivity.kt | 81 +++++++++---------- lib/src/main/kotlin/trees/AVLTree.kt | 7 +- lib/src/main/kotlin/trees/AbstractTree.kt | 17 ++++ lib/src/main/kotlin/trees/BSTree.kt | 5 +- lib/src/main/kotlin/trees/KeyValue.kt | 5 ++ lib/src/main/kotlin/trees/RBTree.kt | 32 ++++++-- lib/src/main/kotlin/trees/nodes/AVLNode.kt | 14 +++- .../main/kotlin/trees/nodes/AbstractNode.kt | 12 +++ lib/src/main/kotlin/trees/nodes/BSNode.kt | 4 + lib/src/main/kotlin/trees/nodes/RBNode.kt | 9 +++ 12 files changed, 142 insertions(+), 59 deletions(-) diff --git a/app/src/main/kotlin/composeApp/Controller.kt b/app/src/main/kotlin/composeApp/Controller.kt index d74791d..e781b02 100644 --- a/app/src/main/kotlin/composeApp/Controller.kt +++ b/app/src/main/kotlin/composeApp/Controller.kt @@ -5,17 +5,12 @@ import androidx.compose.ui.awt.ComposeWindow import com.google.gson.Gson -import repo.JsonRepository -import repo.serialization.SerializableValue -import repo.serialization.strategies.AVLStrategy +import repository.JsonRepository +import repository.serialization.SerializableValue +import repository.serialization.strategies.AVLStrategy import java.awt.FileDialog import java.io.File -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - class Controller { fun openFileDialog( window: ComposeWindow, title: String, allowedExtensions: List, allowMultiSelection: Boolean = true @@ -40,7 +35,7 @@ class Controller { return gson.fromJson(string.value, DrawableNode::class.java)!! } - val repo = JsonRepository(AVLStrategy(::drawableNodeToString, ::jsonStringToDrawableNode), dirPath, filename) + val repo = JsonRepository(AVLStrategy(::drawableNodeToString, ::jsonStringToDrawableNode), dirPath) return repo.getNames() } diff --git a/app/src/main/kotlin/composeApp/DrawableNode.kt b/app/src/main/kotlin/composeApp/DrawableNode.kt index 3b1d18a..8b5f90d 100644 --- a/app/src/main/kotlin/composeApp/DrawableNode.kt +++ b/app/src/main/kotlin/composeApp/DrawableNode.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import app.trees.KeyValue +import trees.KeyValue class DrawableNode( var data: KeyValue, diff --git a/app/src/main/kotlin/composeApp/MainActivity.kt b/app/src/main/kotlin/composeApp/MainActivity.kt index 32a46ac..0150446 100644 --- a/app/src/main/kotlin/composeApp/MainActivity.kt +++ b/app/src/main/kotlin/composeApp/MainActivity.kt @@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.awt.ComposeWindow -import composeApp.OpenTree import java.io.File /* @@ -48,44 +47,44 @@ fun run(window: ComposeWindow) { val listNames = remember { mutableStateOf(listOf("test", "main")) } val nameState = remember { mutableStateOf("") } - when (state.value) { - States.OPENING_TREE -> OpenTree( - listOfDatabase = listOfDatabase, - onTypeOfDatabaseChanged = { newType -> - stringTypeOfDatabaseState.value = newType - typeOfDatabaseState.value = databases.getOrDefault(stringTypeOfDatabaseState.value, TypeOfDatabase.Json) - }, - typeOfDatabaseState = typeOfDatabaseState, - stringTypeOfDatabaseState = stringTypeOfDatabaseState, - - pathState = pathState, - file = file, - onPathChanged = { newPath -> pathState.value = newPath }, - onFilePicked = { - file.value = - controller.value.openFileDialog( - window, - "Load a file", - listOf(".json"), - allowMultiSelection = false - ) - }, - - listOfNames = listNames.value, - nameState = nameState, - onNameChanged = { newName -> nameState.value = newName }, - - onLoadTree = { -// controller.value.loadTree(nameState.value) - state.value = States.DRAW_TREE - } - - - ) - - else -> { - window.setSize(1080, 800) - Editor() - } - } +// when (state.value) { +// States.OPENING_TREE -> OpenTree( +// listOfDatabase = listOfDatabase, +// onTypeOfDatabaseChanged = { newType -> +// stringTypeOfDatabaseState.value = newType +// typeOfDatabaseState.value = databases.getOrDefault(stringTypeOfDatabaseState.value, TypeOfDatabase.Json) +// }, +// typeOfDatabaseState = typeOfDatabaseState, +// stringTypeOfDatabaseState = stringTypeOfDatabaseState, +// +// pathState = pathState, +// file = file, +// onPathChanged = { newPath -> pathState.value = newPath }, +// onFilePicked = { +// file.value = +// controller.value.openFileDialog( +// window, +// "Load a file", +// listOf(".json"), +// allowMultiSelection = false +// ) +// }, +// +// listOfNames = listNames.value, +// nameState = nameState, +// onNameChanged = { newName -> nameState.value = newName }, +// +// onLoadTree = { +//// controller.value.loadTree(nameState.value) +// state.value = States.DRAW_TREE +// } +// +// +// ) +// +// else -> { +// window.setSize(1080, 800) +// Editor() +// } +// } } diff --git a/lib/src/main/kotlin/trees/AVLTree.kt b/lib/src/main/kotlin/trees/AVLTree.kt index 7a4e80f..895ce94 100644 --- a/lib/src/main/kotlin/trees/AVLTree.kt +++ b/lib/src/main/kotlin/trees/AVLTree.kt @@ -8,23 +8,26 @@ package trees import trees.nodes.AVLNode class AVLTree> : AbstractTree>() { - + //adds a new node to the AVL tree. override fun add(data: T) { root = balancedAdd(root, AVLNode(data)) root?.updateHeight() root?.parent = null } + //checks if the given value is contained in the AVL tree. override fun contains(data: T): Boolean { return (contains(root, AVLNode(data)) != null) } + //removes the node with the given value from the AVL tree. override fun delete(data: T) { root = balancedDelete(root, AVLNode(data)) root?.updateHeight() root?.parent = null } + //method that balances the tree starting from the given node. override fun balance(initNode: AVLNode?): AVLNode? { if (initNode == null) { return null @@ -54,10 +57,12 @@ class AVLTree> : AbstractTree>() { return initNode } + //method that returns the value of the node with the given value. fun get(data: T): T? { return contains(root, AVLNode(data))?.data } + //helper method of the AVLTree class that updates the height of the passed node's child nodes. private fun updateChildrenHeight(node: AVLNode?) { node?.left?.updateHeight() node?.right?.updateHeight() diff --git a/lib/src/main/kotlin/trees/AbstractTree.kt b/lib/src/main/kotlin/trees/AbstractTree.kt index f8c10d8..5f337cb 100644 --- a/lib/src/main/kotlin/trees/AbstractTree.kt +++ b/lib/src/main/kotlin/trees/AbstractTree.kt @@ -12,10 +12,16 @@ abstract class AbstractTree, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode, NodeType : AbstractNode { val result = mutableListOf() fun walk(node: NodeType, lst: MutableList) { diff --git a/lib/src/main/kotlin/trees/BSTree.kt b/lib/src/main/kotlin/trees/BSTree.kt index c9042c6..177b5a0 100644 --- a/lib/src/main/kotlin/trees/BSTree.kt +++ b/lib/src/main/kotlin/trees/BSTree.kt @@ -9,20 +9,23 @@ import trees.nodes.BSNode class BSTree> : AbstractTree>() { - + //method that adds a new node override fun add(data: T) { root = balancedAdd(root, BSNode(data)) } + //method that checks if the given value is contained override fun contains(data: T): Boolean { return (contains(root, BSNode(data)) != null) } + //method that removes a node with a given value override fun delete(data: T) { root = balancedDelete(root, BSNode(data)) root?.parent = null } + //method that returns the value of the node with the given value. fun get(data: T): T? { return contains(root, BSNode(data))?.data } diff --git a/lib/src/main/kotlin/trees/KeyValue.kt b/lib/src/main/kotlin/trees/KeyValue.kt index e22bf21..c373d45 100644 --- a/lib/src/main/kotlin/trees/KeyValue.kt +++ b/lib/src/main/kotlin/trees/KeyValue.kt @@ -10,10 +10,13 @@ class KeyValue, V>( var value: V? ) : Comparable> { + //Compares KeyValue objects based on their keys. override fun compareTo(other: KeyValue): Int { return key.compareTo(other.key) } + //Checks if a KeyValue object is equal to another object. + //Two objects are considered equal if their keys are equal. override fun equals(other: Any?): Boolean { if (other is KeyValue<*, *>) { return key == other.key @@ -21,10 +24,12 @@ class KeyValue, V>( return false } + //Returns the string representation of the KeyValue object in "key: value" format. override fun toString(): String { return "$key: $value" } + //Returns the hash code of the KeyValue object based on the hash code of the key. override fun hashCode(): Int { return key.hashCode() } diff --git a/lib/src/main/kotlin/trees/RBTree.kt b/lib/src/main/kotlin/trees/RBTree.kt index 942b9d6..132f736 100644 --- a/lib/src/main/kotlin/trees/RBTree.kt +++ b/lib/src/main/kotlin/trees/RBTree.kt @@ -9,14 +9,24 @@ import trees.nodes.Color import trees.nodes.RBNode class RBTree> : AbstractTree>() { + //This function adds a new node containing the provided data to the tree while ensuring + //that it maintains the Red-Black tree properties. + //It first calls balancedAdd to add the node, + //and then balanceAfterAdd to perform any necessary rotations and color changes. + //It then sets the root's parent to null and the root's color to black. override fun add(data: T) { val node = RBNode(data) root = balancedAdd(root, node) - root = balanceAdd(node, root) + root = balanceAfterAdd(node, root) root?.parent = null root?.color = Color.BLACK } + //This function deletes a node containing the provided data from the tree while ensuring + //that it maintains the Red-Black tree properties. If the node has no children, it simply deletes it. + // If it has one child, it replaces it with its child. If it has two children, + // it replaces it with the next node in the in-order traversal (i.e., the node with the smallest data value in its right subtree), + // and then deletes that node. It then calls balanceAfterDelete to perform any necessary rotations and color changes. override fun delete(data: T) { val node = contains(root, RBNode(data)) ?: return @@ -31,7 +41,7 @@ class RBTree> : AbstractTree>() { replaceChild(node, null) } else { // delete black node without children - root = balanceDelete(node) + root = balanceAfterDelete(node) replaceChild(node, null) } } @@ -50,7 +60,7 @@ class RBTree> : AbstractTree>() { replaceChild(next, null) } else { if (next.right == null) { - root = balanceDelete(next) + root = balanceAfterDelete(next) replaceChild(next, null) } else { // delete for black node with one child @@ -60,11 +70,15 @@ class RBTree> : AbstractTree>() { } } + //method that checks if the given value is contained override fun contains(data: T): Boolean { return (contains(root, RBNode(data)) != null) } - private fun balanceDelete(node: RBNode?): RBNode? { + //This function performs any necessary rotations and color changes to ensure that the tree maintains + //the Red-Black tree properties after a node has been deleted from the tree. It takes as input the node + //that was just deleted, and returns the new root node of the tree. + private fun balanceAfterDelete(node: RBNode?): RBNode? { var newRoot = root var current = node @@ -123,7 +137,10 @@ class RBTree> : AbstractTree>() { return newRoot } - private fun balanceAdd(initNode: RBNode?, subRoot: RBNode?): RBNode? { + //This function performs any necessary rotations and color changes to ensure that + //the tree maintains the Red-Black tree properties after a node has been added to the tree. + //It takes as input the node that was just ad + private fun balanceAfterAdd(initNode: RBNode?, subRoot: RBNode?): RBNode? { if (initNode?.parent == null) return subRoot var newRoot = subRoot var current = initNode @@ -166,10 +183,12 @@ class RBTree> : AbstractTree>() { return newRoot ?: root } + //method that returns the value of the node with the given value. fun get(data: T): T? { return contains(root, RBNode(data))?.data } + //This function performs a left rotation on the subtree rooted at the provided node, and returns the new root node of the subtree. private fun clearRotateLeft(node: RBNode?, initRoot: RBNode?): RBNode? { if (node?.right == null) return null @@ -191,6 +210,7 @@ class RBTree> : AbstractTree>() { return newRoot } + //This function performs a right rotation on the subtree rooted at the provided node, and returns the new root node of the subtree. private fun clearRotateRight(node: RBNode?, root: RBNode?): RBNode? { if (node?.left == null) return null @@ -210,11 +230,13 @@ class RBTree> : AbstractTree>() { } companion object { + //This function returns a Boolean indicating whether the provided node is black. If the node is null, it returns true. @JvmStatic internal fun > isBlack(node: RBNode?): Boolean { return ((node == null) || (node.color == Color.BLACK)) } + //This function returns a Boolean indicating whether the provided node is red. If the node is null, it returns false. @JvmStatic internal fun > isRed(node: RBNode?): Boolean { return node?.color == Color.RED diff --git a/lib/src/main/kotlin/trees/nodes/AVLNode.kt b/lib/src/main/kotlin/trees/nodes/AVLNode.kt index e3e1d76..671a18c 100644 --- a/lib/src/main/kotlin/trees/nodes/AVLNode.kt +++ b/lib/src/main/kotlin/trees/nodes/AVLNode.kt @@ -6,14 +6,22 @@ package trees.nodes class AVLNode>( + //stores the data value of the current node override var data: T, + //Stores the height of the tree internal var height: Int = 1, + //stores the left subtree of the current node override var left: AVLNode? = null, + //stores the right subtree of the current node override var right: AVLNode? = null, + //stores the parent node of the current node override var parent: AVLNode? = null, ) : AbstractNode>() { - + //Updates the node's height value based on the height of its left and right subtrees. +// Gets the height of the left subtree and right subtree, +// then sets the height of the node to 1 + the maximum height of the left and right subtrees. +// This function is used to maintain balance in the tree. internal fun updateHeight() { val leftNode = left val rightNode = right @@ -22,6 +30,10 @@ class AVLNode>( height = 1 + maxOf(leftHeight, rightHeight) } + //Calculates the balance factor of a node, +//which is the difference between the height of the right subtree and the height of the left subtree. +// The function is used to determine if the tree needs to be rebalanced. +//If the balance factor is greater than 1 or less than -1, then the tree needs to be rebalanced. internal fun balanceFactor(): Int { val leftNode = left val rightNode = right diff --git a/lib/src/main/kotlin/trees/nodes/AbstractNode.kt b/lib/src/main/kotlin/trees/nodes/AbstractNode.kt index ef8df78..a8e07dd 100644 --- a/lib/src/main/kotlin/trees/nodes/AbstractNode.kt +++ b/lib/src/main/kotlin/trees/nodes/AbstractNode.kt @@ -8,23 +8,34 @@ package trees.nodes import trees.interfaces.Node abstract class AbstractNode, Subtype : AbstractNode> : Node { + //stores the data value of the current node abstract override var data: T internal set + + //stores the left subtree of the current node abstract override var left: Subtype? internal set + + //stores the right subtree of the current node abstract override var right: Subtype? internal set + + //stores the parent node of the current node abstract override var parent: Subtype? internal set + //method to compare nodes based on their data values override fun compareTo(other: Node): Int { return data.compareTo(other.data) } + //method to get the hash code of the current node override fun hashCode(): Int { return data.hashCode() } + //method for comparing the current node with another object, + // checking for equality of the value of the data property override fun equals(other: Any?): Boolean { if (other is AbstractNode<*, *>) { return data == other.data @@ -32,6 +43,7 @@ abstract class AbstractNode, Subtype : AbstractNode>( + //stores the data value of the current node override var data: T, + //stores the left subtree of the current node override var left: BSNode? = null, + //stores the right subtree of the current node override var right: BSNode? = null, + //stores the parent node of the current node override var parent: BSNode? = null, ) : AbstractNode>() \ No newline at end of file diff --git a/lib/src/main/kotlin/trees/nodes/RBNode.kt b/lib/src/main/kotlin/trees/nodes/RBNode.kt index 2832552..01c04db 100644 --- a/lib/src/main/kotlin/trees/nodes/RBNode.kt +++ b/lib/src/main/kotlin/trees/nodes/RBNode.kt @@ -5,17 +5,26 @@ package trees.nodes +//enum that defines two colors: RED and BLACK enum class Color { RED, BLACK } class RBNode>( + //stores the data value of the current node override var data: T, + //node color, default is set to RED var color: Color = Color.RED, + //stores the left subtree of the current node override var left: RBNode? = null, + //stores the right subtree of the current node override var right: RBNode? = null, + //stores the parent node of the current node override var parent: RBNode? = null, ) : AbstractNode>() { + + //an overridden toString function that returns a string + //representation of a node containing its color and data. override fun toString(): String { return "$color - $data" } From 17c821736b77d19817479a7ab1728e2d1e39467b Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sat, 29 Apr 2023 12:32:59 +0300 Subject: [PATCH 78/90] fix: update CI script --- .github/workflows/github-actions.yml | 5 ++++- gradlew | 15 ++------------- 2 files changed, 6 insertions(+), 14 deletions(-) mode change 100644 => 100755 gradlew diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 082400b..3961720 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -6,6 +6,9 @@ on: - main - release +permissions: + contents: read + jobs: build: runs-on: ${{ matrix.os }} @@ -20,7 +23,7 @@ jobs: uses: actions/setup-java@v3 with: java-version: '17' - distribution: 'temurin' + distribution: zulu - name: Build & test with Gradle uses: gradle/gradle-build-action@v2 with: diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index 79a61d4..8e7990c --- a/gradlew +++ b/gradlew @@ -1,19 +1,8 @@ #!/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. +# Copyright (c) 2023 teemEight +# SPDX-License-Identifier: Apache-2.0 # ############################################################################## From ead2d74191ec9ed461572488ca3ea7e3c6ad65ed Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Sat, 29 Apr 2023 14:14:06 +0300 Subject: [PATCH 79/90] hotfix: update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c56487a..6c34770 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ or `neo4j` databases. - [x] Added tests - Storing with: - [x] Neo4j - - [ ] Sqlite - - [ ] json + - [x] Sqlite + - [x] json - [ ] GUI ## Building From f25ab8a4e0ecd4bbc95537decbbb243f0bef5c28 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 02:48:15 +0300 Subject: [PATCH 80/90] feat: simplify work with JsonRepository, update dependencies fix: value in KeyValue must be not null test: update test for keyValue --- lib/build.gradle.kts | 1 + .../main/kotlin/repository/JsonRepository.kt | 68 +++++++++++-------- lib/src/main/kotlin/trees/KeyValue.kt | 2 +- lib/src/test/kotlin/trees/AVLTreeTest.kt | 2 +- lib/src/test/kotlin/trees/BSTreeTest.kt | 2 +- lib/src/test/kotlin/trees/RBTreeTest.kt | 2 +- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 9feed94..b1a91f2 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -19,6 +19,7 @@ plugins { dependencies { implementation(libs.kotlinx.serialization.json) + implementation(libs.gson) implementation(libs.exposed.core) implementation(libs.exposed.dao) diff --git a/lib/src/main/kotlin/repository/JsonRepository.kt b/lib/src/main/kotlin/repository/JsonRepository.kt index fc1b9d2..52e7cb0 100644 --- a/lib/src/main/kotlin/repository/JsonRepository.kt +++ b/lib/src/main/kotlin/repository/JsonRepository.kt @@ -5,9 +5,8 @@ package repository -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import repository.jsonEntities.JsonNode import repository.jsonEntities.JsonTree import repository.serialization.SerializableNode @@ -19,25 +18,21 @@ import java.io.File import java.io.FileNotFoundException //creates a new JsonRepository object with the given strategy and dirPath. strategy -class JsonRepository, - NodeType : AbstractNode, - TreeType : AbstractTree> +class JsonRepository, NodeType : AbstractNode, TreeType : AbstractTree> ( //serialization strategy for working with trees and nodes. strategy: Serialization, //path to the directory where tree files will be stored in JSON format. - dirPath: String + private val dirPath: String, private val filename: String ) : Repository(strategy) { - private val dirPath = "$dirPath/${strategy.typeOfTree.name.lowercase()}" + private val typeToken = object : TypeToken>() {}.type + //function to convert JsonNode to SerializableNode. private fun JsonNode.toSerializableNode(): SerializableNode { return SerializableNode( - data, - metadata, - left?.toSerializableNode(), - right?.toSerializableNode() + data, metadata, left?.toSerializableNode(), right?.toSerializableNode() ) } @@ -53,37 +48,40 @@ class JsonRepository, //function to convert SerializableNode to JsonNode. private fun SerializableNode.toJsonNode(): JsonNode { return JsonNode( - data, - metadata, - left?.toJsonNode(), - right?.toJsonNode() + data, metadata, left?.toJsonNode(), right?.toJsonNode() ) } //function to convert SerializableTree to JsonTree private fun SerializableTree.toJsonTree(): JsonTree { return JsonTree( - name, - typeOfTree, - root?.toJsonNode() + name, typeOfTree, root?.toJsonNode() ) } //method for getting a list of tree names. - override fun getNames(): List = - File(dirPath).listFiles()?.map { - Json.decodeFromString(it.readText()).name - } ?: listOf() + override fun getNames(): List { + try { + File(dirPath, filename).run { + val names = Gson().fromJson>(readText(), typeToken) + ?.filter { it.typeOfTree == strategy.typeOfTree }?.map { it.name } + return names ?: listOf() + } + } catch (_: FileNotFoundException) { + return listOf() + } + } //method to load a tree by name override fun loadByName(name: String): TreeType? { val json = try { - File(dirPath, "${name}.json").readText() + File(dirPath, filename).readText() } catch (_: FileNotFoundException) { return null } - val jsonTree = Json.decodeFromString(json) + val jsonTree = Gson().fromJson>(json, typeToken) + ?.firstOrNull { it.name == name && it.typeOfTree == strategy.typeOfTree } ?: return null return strategy.createTree().apply { root = jsonTree.root?.deserialize() } @@ -93,15 +91,29 @@ class JsonRepository, override fun save(name: String, tree: TreeType) { val jsonTree = tree.toSerializableTree(name).toJsonTree() + deleteByName(name) + File(dirPath).mkdirs() - File(dirPath, "${name}.json").run { + File(dirPath, filename).run { createNewFile() - writeText(Json.encodeToString(jsonTree)) + var trees = Gson().fromJson>(readText(), typeToken) + if (trees == null) { + trees = mutableListOf() + } + trees.add(jsonTree) + writeText(Gson().toJson(trees)) } } //a method for deleting a tree by name override fun deleteByName(name: String) { - File(dirPath, "${name}.json").delete() + try { + File(dirPath, filename).run { + val trees = Gson().fromJson>(readText(), typeToken) ?: mutableListOf() + trees.removeIf { it.name == name && it.typeOfTree == strategy.typeOfTree } + writeText(Gson().toJson(trees)) + } + } catch (_: FileNotFoundException) { + } } } \ No newline at end of file diff --git a/lib/src/main/kotlin/trees/KeyValue.kt b/lib/src/main/kotlin/trees/KeyValue.kt index c373d45..bf996d2 100644 --- a/lib/src/main/kotlin/trees/KeyValue.kt +++ b/lib/src/main/kotlin/trees/KeyValue.kt @@ -7,7 +7,7 @@ package trees class KeyValue, V>( val key: K, - var value: V? + var value: V, ) : Comparable> { //Compares KeyValue objects based on their keys. diff --git a/lib/src/test/kotlin/trees/AVLTreeTest.kt b/lib/src/test/kotlin/trees/AVLTreeTest.kt index f195599..6674792 100644 --- a/lib/src/test/kotlin/trees/AVLTreeTest.kt +++ b/lib/src/test/kotlin/trees/AVLTreeTest.kt @@ -90,7 +90,7 @@ class AVLTreeTest { newTree.add(stringIntKeyValue) assertEquals( stringIntKeyValue.value, - newTree.get(KeyValue(stringIntKeyValue.key, null))?.value, + newTree.get(KeyValue(stringIntKeyValue.key, 0))?.value, "Values should be equals" ) } diff --git a/lib/src/test/kotlin/trees/BSTreeTest.kt b/lib/src/test/kotlin/trees/BSTreeTest.kt index 9b07f03..7d4857e 100644 --- a/lib/src/test/kotlin/trees/BSTreeTest.kt +++ b/lib/src/test/kotlin/trees/BSTreeTest.kt @@ -84,7 +84,7 @@ class BSTreeTest { newTree.add(stringIntKeyValue) assertEquals( stringIntKeyValue.value, - newTree.get(KeyValue(stringIntKeyValue.key, null))?.value, + newTree.get(KeyValue(stringIntKeyValue.key, 0))?.value, "Values should be equals" ) } diff --git a/lib/src/test/kotlin/trees/RBTreeTest.kt b/lib/src/test/kotlin/trees/RBTreeTest.kt index 8b5ff18..31f3f2a 100644 --- a/lib/src/test/kotlin/trees/RBTreeTest.kt +++ b/lib/src/test/kotlin/trees/RBTreeTest.kt @@ -105,7 +105,7 @@ class RBTreeTest { newTree.add(stringIntKeyValue) assertEquals( stringIntKeyValue.value, - newTree.get(KeyValue(stringIntKeyValue.key, null))?.value, + newTree.get(KeyValue(stringIntKeyValue.key, 0))?.value, "Values should be equals" ) } From 868ae7aa562a7998991575d95f041cf7db575694 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 02:49:24 +0300 Subject: [PATCH 81/90] feat: first version of App, so dirty code (sorry) --- app/src/main/kotlin/Main.kt | 3 +- app/src/main/kotlin/app/App.kt | 168 ++++++++++++++++++ app/src/main/kotlin/app/EditorController.kt | 117 ++++++++++++ app/src/main/kotlin/app/EditorField.kt | 36 ++++ app/src/main/kotlin/app/MainActivity.kt | 115 ++++++++++++ app/src/main/kotlin/app/NodeDataGUI.kt | 32 ++++ app/src/main/kotlin/app/graph/DrawableNode.kt | 37 ++++ app/src/main/kotlin/app/graph/GraphLine.kt | 42 +++++ app/src/main/kotlin/app/graph/GraphNode.kt | 124 +++++++++++++ app/src/main/kotlin/app/graph/GraphState.kt | 62 +++++++ app/src/main/kotlin/app/graph/TreeGraph.kt | 123 +++++++++++++ .../main/kotlin/composeApp/DrawableNode.kt | 16 -- .../main/kotlin/composeApp/MainActivity.kt | 90 ---------- 13 files changed, 858 insertions(+), 107 deletions(-) create mode 100644 app/src/main/kotlin/app/App.kt create mode 100644 app/src/main/kotlin/app/EditorController.kt create mode 100644 app/src/main/kotlin/app/EditorField.kt create mode 100644 app/src/main/kotlin/app/MainActivity.kt create mode 100644 app/src/main/kotlin/app/NodeDataGUI.kt create mode 100644 app/src/main/kotlin/app/graph/DrawableNode.kt create mode 100644 app/src/main/kotlin/app/graph/GraphLine.kt create mode 100644 app/src/main/kotlin/app/graph/GraphNode.kt create mode 100644 app/src/main/kotlin/app/graph/GraphState.kt create mode 100644 app/src/main/kotlin/app/graph/TreeGraph.kt delete mode 100644 app/src/main/kotlin/composeApp/DrawableNode.kt delete mode 100644 app/src/main/kotlin/composeApp/MainActivity.kt diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt index 8c1711a..a1695ad 100644 --- a/app/src/main/kotlin/Main.kt +++ b/app/src/main/kotlin/Main.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import app.app import java.awt.Dimension fun main() = application { @@ -23,7 +24,7 @@ fun main() = application { ) { window.minimumSize = Dimension(700, 700) - run(window) + app(window) } // val username = "neo4j" // val password = "isabel-except-toronto-monaco-never-5754" // insert password to database here diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt new file mode 100644 index 0000000..cf5667e --- /dev/null +++ b/app/src/main/kotlin/app/App.kt @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toSize +import java.io.File + + +@Composable +fun OpenTree( + + listOfDatabase: List, + listOfTypes: List, + listOfNames: List, + + typeOfDatabaseState: State, + file: State, + + onTypeChanged: (String) -> Unit, + onTypeOfDatabaseChanged: (String) -> Unit, + onFilePicked: () -> Unit, + onPathChanged: (String) -> Unit, + onNameChanged: (String) -> Unit, + onLoadTree: () -> Unit, + onLoadDatabase: () -> Unit, +) { + MaterialTheme { + Scaffold(topBar = { + TopAppBar(title = { Text("Выберите тип дерева") }) + }) { + Column(Modifier.padding(10.dp)) { + DropDownTextFiled("Type of database", listOfDatabase, onTypeOfDatabaseChanged) + DropDownTextFiled("Type of tree", listOfTypes, onTypeChanged) + when (typeOfDatabaseState.value) { + TypeOfDatabase.Json -> FilePicker(onFilePicked, file) + TypeOfDatabase.Neo4j -> PathToStorage(onPathChanged, onLoadDatabase) + TypeOfDatabase.SQL -> PathToStorage(onPathChanged, onLoadDatabase) + else -> {} + } + if (typeOfDatabaseState.value != null) { + + DropDownTextFiled("Name of tree", listOfNames, onNameChanged, false) + Button( + onClick = onLoadTree, + modifier = Modifier.padding(horizontal = 10.dp) + ) { + Text("Let's go!") + } + } + } + } + } +} + + +@Composable +fun FilePicker( + onFilePicked: () -> Unit, + file: State, +) { + Row( + modifier = Modifier.padding(horizontal = 10.dp), verticalAlignment = Alignment.CenterVertically + ) { + Button( + onClick = onFilePicked + ) { + Text( + text = "Выберите файл", + ) + } + Spacer(modifier = Modifier.width(8.dp)) + file.value?.let { + Text(file.value?.name ?: "") + } + } +} + +@Composable +fun PathToStorage( + onPathChanged: (String) -> Unit, + onLoadDatabase: () -> Unit, +) { + var path by remember { mutableStateOf("") } + Column(Modifier.padding(horizontal = 10.dp)) { + OutlinedTextField( + value = path, + singleLine = true, + onValueChange = { + onPathChanged(it) + path = it + }, + label = { Text(text = "Введите путь к базе") }, + modifier = Modifier.fillMaxWidth() + ) + Button( + onClick = onLoadDatabase + ) { + Text("Загрузить") + } + } +} + +@Composable +fun DropDownTextFiled( + title: String, + listOfValues: List, + onValueChanged: (String) -> Unit, + readOnly: Boolean = true, +) { + var mExpanded by remember { mutableStateOf(false) } + var mTextFieldSize by remember { mutableStateOf(Size.Zero) } + var mSelectedText by remember { mutableStateOf("") } + + val icon = if (mExpanded) Icons.Filled.KeyboardArrowUp + else Icons.Filled.KeyboardArrowDown + + Column(Modifier.padding(horizontal = 10.dp)) { + OutlinedTextField(value = mSelectedText, + singleLine = true, + readOnly = readOnly, + onValueChange = { newName -> + mSelectedText = newName + onValueChanged(newName) + }, + modifier = Modifier.fillMaxWidth().onGloballyPositioned { coordinates -> + mTextFieldSize = coordinates.size.toSize() + }, + label = { + Text( + text = title, + ) + }, + trailingIcon = { + Icon(icon, "contentDescription", Modifier.clickable { mExpanded = !mExpanded }) + }) + DropdownMenu( + expanded = mExpanded, + onDismissRequest = { mExpanded = false }, + modifier = Modifier.width(with(LocalDensity.current) { mTextFieldSize.width.toDp() }) + ) { + listOfValues.forEach { label -> + DropdownMenuItem(onClick = { + mSelectedText = label + onValueChanged(label) + mExpanded = false + }) { + Text(text = label) + } + } + } + } +} diff --git a/app/src/main/kotlin/app/EditorController.kt b/app/src/main/kotlin/app/EditorController.kt new file mode 100644 index 0000000..3870f43 --- /dev/null +++ b/app/src/main/kotlin/app/EditorController.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import app.graph.DrawableNode +import app.graph.ImDrawableNode +import repository.Repository +import trees.AbstractTree +import trees.KeyValue +import trees.nodes.AbstractNode +import trees.nodes.Color +import trees.nodes.RBNode + + +class EditorController>( + private val tree: AbstractTree?, + private val repository: Repository>?, + private val name: String, +) { + + var drawableRoot: ImDrawableNode? by mutableStateOf(toDrawable(tree?.root as NodeType)) + private set + + fun initTree() { + drawableRoot = tree?.root?.let { toDrawable(it as NodeType, respectXY = true) } + } + + fun resetTree() { + drawableRoot = tree?.root?.let { toDrawable(it as NodeType) } + } + + fun saveTree() { + fun copyCoordinates(node: NodeType, drawableNode: ImDrawableNode?) { + if (node == null || drawableNode == null) { + return + } + node.left?.let { copyCoordinates(it, drawableNode.left) } + node.data.x = drawableNode.x + node.data.y = drawableNode.y + node.right?.let { copyCoordinates(it, drawableNode.right) } + } + + copyCoordinates(tree?.root as NodeType, drawableRoot) + if (tree != null) { + repository?.save(name, tree) + } + } + + fun add(key: Int, value: String) { + tree?.add(NodeDataGUI(KeyValue(key, value))) + drawableRoot = tree?.root?.let { toDrawable(it as NodeType) } + } + + fun delete(key: Int) { + val res = tree?.delete(NodeDataGUI(KeyValue(key, ""))) + } + + fun contains(key: Int) { + val res = tree?.contains(NodeDataGUI(KeyValue(key, ""))) + } + + private fun toDrawable(root: NodeType?, respectXY: Boolean = false): ImDrawableNode? { + if (root == null) { + return null + } + + val drawRoot = DrawableNode(root.data.data.key, root.data.data.value) + + fun calcCoordinates( + node: NodeType, + drawableNode: DrawableNode, + offsetX: Int, + curH: Int, + ): Int { + var resX = offsetX + node.left?.let { left -> + drawableNode.left = DrawableNode(left.data.data.key, left.data.data.value).also { drawLeft -> + resX = calcCoordinates(left, drawLeft, offsetX, curH + 1) + 1 + } + } + + drawableNode.x = if (respectXY) node.data.x else ((60.dp * 2 / 3) * resX) + drawableNode.y = if (respectXY) node.data.y else ((60.dp * 5 / 4) * curH) + if (node is RBNode<*>) { + drawableNode.color = when (node.color) { + Color.RED -> androidx.compose.ui.graphics.Color.Red + Color.BLACK -> androidx.compose.ui.graphics.Color.Black + } + } + + node.right?.let { right -> + drawableNode.right = DrawableNode(right.data.data.key, right.data.data.value).also { drawRight -> + resX = calcCoordinates(right, drawRight, resX + 1, curH + 1) + } + } + + return resX + } + calcCoordinates(root as NodeType, drawRoot, 0, 0) + return drawRoot + } + + fun dragNode(node: ImDrawableNode, dragAmount: DpOffset) { + (node as? DrawableNode)?.let { + node.x += dragAmount.x + node.y += dragAmount.y + } + } +} diff --git a/app/src/main/kotlin/app/EditorField.kt b/app/src/main/kotlin/app/EditorField.kt new file mode 100644 index 0000000..c78fe3d --- /dev/null +++ b/app/src/main/kotlin/app/EditorField.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import app.graph.TreeGraph +import trees.nodes.AbstractNode + +@Composable +fun > EditorScreen( + editorController: EditorController +) { + val viewModel = remember { editorController } + Column(verticalArrangement = Arrangement.spacedBy(20.dp)) { + Surface( + modifier = Modifier.fillMaxSize().weight(1f), + shape = MaterialTheme.shapes.medium, + color = MaterialTheme.colorScheme.surface + ) { + viewModel.drawableRoot?.let { + TreeGraph(it, 60.dp, viewModel::dragNode) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/MainActivity.kt b/app/src/main/kotlin/app/MainActivity.kt new file mode 100644 index 0000000..bb165a9 --- /dev/null +++ b/app/src/main/kotlin/app/MainActivity.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app + +import Controller +import Editor +import androidx.compose.runtime.* +import androidx.compose.ui.awt.ComposeWindow +import repository.serialization.TypeOfTree +import java.io.File + + +enum class States { + OPENING_TREE, DRAW_TREE +} + + +enum class TypeOfDatabase { + Neo4j, + Json, + SQL +} + +@Composable +fun app(window: ComposeWindow) { + + val typesOfDatabase = remember { + mapOf( + "SQL" to TypeOfDatabase.SQL, + "Neo4j" to TypeOfDatabase.Neo4j, + ".json file" to TypeOfDatabase.Json + ) + } + + val typesOfTrees = remember { + mapOf( + "AVL" to TypeOfTree.AVL_TREE, + "Red-black" to TypeOfTree.RB_TREE, + "Binary" to TypeOfTree.BS_TREE, + ) + } + + val listOfTrees = typesOfTrees.keys.toList() + val listOfDatabase = typesOfDatabase.keys.toList() + var listNames by remember { mutableStateOf(listOf("test", "main")) } + + val typeOfTree = remember { mutableStateOf(TypeOfTree.BS_TREE) } + val stringTypeOfDatabaseState = remember { mutableStateOf("") } + val pathState = remember { mutableStateOf("") } + val typeOfDatabaseState = + remember { mutableStateOf(typesOfDatabase[stringTypeOfDatabaseState.value]) } + + val state = remember { mutableStateOf(States.OPENING_TREE) } + val controller by remember { mutableStateOf(Controller()) } + val fileState = remember { mutableStateOf(null) } + + val nameState = remember { mutableStateOf("") } + when (state.value) { + States.OPENING_TREE -> OpenTree( + listOfDatabase = listOfDatabase, + listOfNames = listNames, + listOfTypes = listOfTrees, + + typeOfDatabaseState = typeOfDatabaseState, + file = fileState, + + onTypeOfDatabaseChanged = { newType -> + stringTypeOfDatabaseState.value = newType + typeOfDatabaseState.value = + typesOfDatabase[stringTypeOfDatabaseState.value] + }, + onPathChanged = { newPath -> pathState.value = newPath }, + onFilePicked = { + fileState.value = + controller.openFileDialog( + window, + "Load a file", + listOf(".json"), + allowMultiSelection = false + ) + controller.loadDatabase( + typeOfDatabaseState.value, + typeOfTree.value, + fileState.value?.parent, + fileState.value?.name + ) + listNames = controller.getNamesOfTrees() + }, + onNameChanged = { newName -> nameState.value = newName }, + onTypeChanged = { newType -> typeOfTree.value = typesOfTrees[newType] }, + onLoadTree = { + state.value = States.DRAW_TREE + typeOfTree.value?.let { controller.loadTree(it, nameState.value) } + }, + onLoadDatabase = { + controller.loadDatabase( + typeOfDatabaseState.value, + typeOfTree.value, + pathState.value, + fileState.value?.name + ) + } + ) + + else -> { + window.setSize(1080, 800) + Editor( + viewModel = EditorController(controller.tree, controller.repository, nameState.value) + ) + } + } +} diff --git a/app/src/main/kotlin/app/NodeDataGUI.kt b/app/src/main/kotlin/app/NodeDataGUI.kt new file mode 100644 index 0000000..d9e7784 --- /dev/null +++ b/app/src/main/kotlin/app/NodeDataGUI.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.google.gson.Gson +import repository.serialization.SerializableValue +import trees.KeyValue + +class NodeDataGUI( + val data: KeyValue, + var x: Dp = 0.dp, + var y: Dp = 0.dp, +) : Comparable { + override fun compareTo(other: NodeDataGUI) = data.compareTo(other.data) + + override fun toString() = "key=${data.key} value=${data.value}\nx=$x y=$y" + + companion object { + @JvmStatic + fun serialize(data: NodeDataGUI) = SerializableValue(Gson().toJson(data)) + + @JvmStatic + fun deserialize(data: SerializableValue): NodeDataGUI = Gson().fromJson(data.value, NodeDataGUI::class.java) + + } +} + diff --git a/app/src/main/kotlin/app/graph/DrawableNode.kt b/app/src/main/kotlin/app/graph/DrawableNode.kt new file mode 100644 index 0000000..10cb575 --- /dev/null +++ b/app/src/main/kotlin/app/graph/DrawableNode.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app.graph + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +interface ImDrawableNode { + val key: Int + val value: String + val left: ImDrawableNode? + val right: ImDrawableNode? + val color: Color? + val x: Dp + val y: Dp +} + +class DrawableNode( + override val key: Int, + override var value: String, + override var left: DrawableNode? = null, + override var right: DrawableNode? = null, + override var color: Color? = null, + y: Dp = 0.dp, + x: Dp = 0.dp, +) : ImDrawableNode { + override var x by mutableStateOf(x) + override var y by mutableStateOf(y) + +} diff --git a/app/src/main/kotlin/app/graph/GraphLine.kt b/app/src/main/kotlin/app/graph/GraphLine.kt new file mode 100644 index 0000000..f4d1df7 --- /dev/null +++ b/app/src/main/kotlin/app/graph/GraphLine.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app.graph + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import visualizer.editor.graph.ScreenScale + +@Composable +fun GraphLine( + modifier: Modifier = Modifier, + start: ImDrawableNode, + end: ImDrawableNode, + nodeSize: Dp, + sDragProvider: () -> Offset, + sScaleProvider: () -> ScreenScale +) { + Canvas(modifier = modifier.fillMaxSize()) { + val drag = sDragProvider() + val scale = sScaleProvider() + drawLine( + start = Offset( + ((start.x + nodeSize / 2).toPx() + drag.x) * scale.scale + scale.posRelXYScale.x, + ((start.y + nodeSize / 2).toPx() + drag.y) * scale.scale + scale.posRelXYScale.y, + ), + end = Offset( + ((end.x + nodeSize / 2).toPx() + drag.x) * scale.scale + scale.posRelXYScale.x, + ((end.y + nodeSize / 2).toPx() + drag.y) * scale.scale + scale.posRelXYScale.y, + ), + strokeWidth = 2f * scale.scale, + color = Color.Black + ) + } +} diff --git a/app/src/main/kotlin/app/graph/GraphNode.kt b/app/src/main/kotlin/app/graph/GraphNode.kt new file mode 100644 index 0000000..dbfd9af --- /dev/null +++ b/app/src/main/kotlin/app/graph/GraphNode.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package visualizer.editor.graph + +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.layout.* +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import app.graph.ImDrawableNode +import kotlin.math.roundToInt + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun GraphNode( + modifier: Modifier = Modifier, + node: ImDrawableNode, + nodeSize: Dp, + onNodeDrag: (ImDrawableNode, DpOffset) -> Unit, + sDragProvider: () -> Offset, + sScaleProvider: () -> ScreenScale +) { + TooltipArea( + modifier = modifier.zIndex(5f) // nodes must be in the front of the screen, covering lines + .layout { measurable: Measurable, _: Constraints -> + // avoid recomposition of nodes by reading scale, drag and cords in layout stage + + val placeable = measurable.measure( + // set fixed size = node size * scale + Constraints.fixed( + (nodeSize * sScaleProvider().scale).roundToPx(), + (nodeSize * sScaleProvider().scale).roundToPx() + ) + ) + + layout(placeable.width, placeable.height) { + val drag = sDragProvider() + val scale = sScaleProvider() + + // place node considering screen drag and scale + placeable.placeRelative( + ((node.x.toPx() + drag.x) * scale.scale + scale.posRelXYScale.x).roundToInt(), + ((node.y.toPx() + drag.y) * scale.scale + scale.posRelXYScale.y).roundToInt(), + ) + } + }, + tooltip = { + Surface( + shape = MaterialTheme.shapes.medium, + shadowElevation = 3.dp + ) { + Text( + text = "Key: ${node.key}\nValue: ${node.value}", + modifier = Modifier.padding(13.dp) + ) + } + }, + tooltipPlacement = TooltipPlacement.CursorPoint( + offset = DpOffset(0.dp, 16.dp) + ), + delayMillis = 800, + ) { + Box(modifier = modifier + .fillMaxSize() + .background( + color = node.color ?: MaterialTheme.colorScheme.primary, + shape = CircleShape + ) + .pointerInput(node) { + detectDragGestures { change, dragAmount -> + change.consume() + + val scale = sScaleProvider().scale + onNodeDrag( + node, + DpOffset( + dragAmount.x.toDp() / scale, + dragAmount.y.toDp() / scale + ), + ) + } + } + ) { + + NodeText( + modifier = Modifier.align(Alignment.Center), + text = node.key.toString(), + scaleProvider = { sScaleProvider().scale } + ) + } + } +} + +@Composable +fun NodeText( + modifier: Modifier = Modifier, + text: String, + scaleProvider: () -> Float, +) { + val scale = scaleProvider() + Text( + modifier = modifier, + text = if (text.length > 4) text.substring(0, 4) + ".." else text, + color = MaterialTheme.colorScheme.onPrimary, + ) +} diff --git a/app/src/main/kotlin/app/graph/GraphState.kt b/app/src/main/kotlin/app/graph/GraphState.kt new file mode 100644 index 0000000..4f42507 --- /dev/null +++ b/app/src/main/kotlin/app/graph/GraphState.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package visualizer.editor.graph + +import androidx.compose.runtime.* +import androidx.compose.ui.geometry.Offset +import kotlin.math.max +import kotlin.math.min + +data class ScreenScale( + val scale: Float, + val posRelXYScale: Offset +) + +class GraphState { + var screenScale by mutableStateOf(ScreenScale(1f, Offset(0f, 0f))) + private set + + var screenDrag by mutableStateOf(Offset(0f, 0f)) + private set + + fun handleScroll(scrollDelta: Offset, scrollPosition: Offset) { + screenDrag += Offset(-scrollDelta.x / screenScale.scale * 25, 0f) + + val prevScale = screenScale.scale + val newScale = min( + max( + screenScale.scale - scrollDelta.y / 20, + 0.1f + ), 2f + ) + val relScale = newScale / prevScale + + screenScale = ScreenScale( + scale = newScale, + posRelXYScale = Offset( + screenScale.posRelXYScale.x * relScale + scrollPosition.x * (1 - relScale), + screenScale.posRelXYScale.y * relScale + scrollPosition.y * (1 - relScale) + ) + ) + } + + fun handleScreenDrag(dragAmount: Offset) { + screenDrag += Offset( + dragAmount.x / screenScale.scale, + dragAmount.y / screenScale.scale + ) + } + + + fun resetGraphView(dragX: Float = 0f, dragY: Float = 0f) { + screenDrag = Offset(dragX, dragY) + screenScale = ScreenScale(1f, Offset(0f, 0f)) + } + +} + +@Composable +fun rememberGraphState() = remember { GraphState() } diff --git a/app/src/main/kotlin/app/graph/TreeGraph.kt b/app/src/main/kotlin/app/graph/TreeGraph.kt new file mode 100644 index 0000000..ef3a112 --- /dev/null +++ b/app/src/main/kotlin/app/graph/TreeGraph.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app.graph + +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import visualizer.editor.graph.GraphNode +import visualizer.editor.graph.GraphState +import visualizer.editor.graph.ScreenScale +import visualizer.editor.graph.rememberGraphState + + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun TreeGraph( + root: ImDrawableNode, + nodeSize: Dp, + onNodeDrag: (ImDrawableNode, DpOffset) -> Unit, + graphState: GraphState = rememberGraphState() +) { + val currentDensity = LocalDensity.current + fun centerGraph(viewWidth: Int) { + currentDensity.run { + graphState.resetGraphView( + dragX = viewWidth / 2 - (nodeSize / 2 + root.x).toPx(), + dragY = nodeSize.toPx() + ) + } + } + + var graphViewWidth = 0 + Box(modifier = Modifier + .fillMaxSize() + .onPointerEvent(PointerEventType.Scroll) { + graphState.handleScroll( + it.changes.first().scrollDelta, + it.changes.first().position + ) + } + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + graphState.handleScreenDrag(dragAmount) + } + } + .onSizeChanged { + graphViewWidth = it.width + } + ) { + LaunchedEffect(Unit) { + centerGraph(graphViewWidth) + } + + drawTree( + node = root, + nodeSize = nodeSize, + onNodeDrag = onNodeDrag, + sDragProvider = { graphState.screenDrag }, + sScaleProvider = { graphState.screenScale } + ) + + TextButton( + modifier = Modifier.align(Alignment.TopEnd).padding(10.dp), + onClick = { centerGraph(graphViewWidth) } + ) { + Text("Reset view") + } + } +} + +@Composable +fun drawTree( + node: ImDrawableNode?, + parent: ImDrawableNode? = null, + nodeSize: Dp, + onNodeDrag: (ImDrawableNode, DpOffset) -> Unit, + sDragProvider: () -> Offset, + sScaleProvider: () -> ScreenScale +) { + node?.let { + parent?.let { parent -> + GraphLine( + start = parent, + end = node, + nodeSize = nodeSize, + sDragProvider = sDragProvider, + sScaleProvider = sScaleProvider + ) + } + + drawTree(node.left, node, nodeSize, onNodeDrag, sDragProvider, sScaleProvider) + drawTree(node.right, node, nodeSize, onNodeDrag, sDragProvider, sScaleProvider) + + GraphNode( + node = node, + nodeSize = nodeSize, + onNodeDrag = onNodeDrag, + sDragProvider = sDragProvider, + sScaleProvider = sScaleProvider + ) + } +} diff --git a/app/src/main/kotlin/composeApp/DrawableNode.kt b/app/src/main/kotlin/composeApp/DrawableNode.kt deleted file mode 100644 index 8b5f90d..0000000 --- a/app/src/main/kotlin/composeApp/DrawableNode.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -import trees.KeyValue - -class DrawableNode( - var data: KeyValue, - var x: Long, - var y: Long -) : Comparable { - override fun compareTo(other: DrawableNode): Int { - return data.compareTo(other.data) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/MainActivity.kt b/app/src/main/kotlin/composeApp/MainActivity.kt deleted file mode 100644 index 0150446..0000000 --- a/app/src/main/kotlin/composeApp/MainActivity.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.awt.ComposeWindow -import java.io.File - -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -enum class States { - OPENING_TREE, DRAW_TREE -} - - -enum class TypeOfDatabase { - Neo4j, - Json, - SQL -} - -@Composable -fun run(window: ComposeWindow) { - val databases = remember { - mapOf( - "SQL" to TypeOfDatabase.SQL, - "Neo4j" to TypeOfDatabase.Neo4j, - ".json file" to TypeOfDatabase.Json - ) - } - val listOfDatabase = databases.keys.toList() - val stringTypeOfDatabaseState = remember { mutableStateOf(".json file") } - val pathState = remember { mutableStateOf("") } - val typeOfDatabaseState = - remember { mutableStateOf(databases.getOrDefault(stringTypeOfDatabaseState.value, TypeOfDatabase.Json)) } - - - val state = remember { mutableStateOf(States.OPENING_TREE) } - val controller = remember { mutableStateOf(Controller()) } - val file = remember { mutableStateOf(null) } - - val listNames = remember { mutableStateOf(listOf("test", "main")) } - val nameState = remember { mutableStateOf("") } -// when (state.value) { -// States.OPENING_TREE -> OpenTree( -// listOfDatabase = listOfDatabase, -// onTypeOfDatabaseChanged = { newType -> -// stringTypeOfDatabaseState.value = newType -// typeOfDatabaseState.value = databases.getOrDefault(stringTypeOfDatabaseState.value, TypeOfDatabase.Json) -// }, -// typeOfDatabaseState = typeOfDatabaseState, -// stringTypeOfDatabaseState = stringTypeOfDatabaseState, -// -// pathState = pathState, -// file = file, -// onPathChanged = { newPath -> pathState.value = newPath }, -// onFilePicked = { -// file.value = -// controller.value.openFileDialog( -// window, -// "Load a file", -// listOf(".json"), -// allowMultiSelection = false -// ) -// }, -// -// listOfNames = listNames.value, -// nameState = nameState, -// onNameChanged = { newName -> nameState.value = newName }, -// -// onLoadTree = { -//// controller.value.loadTree(nameState.value) -// state.value = States.DRAW_TREE -// } -// -// -// ) -// -// else -> { -// window.setSize(1080, 800) -// Editor() -// } -// } -} From 32e3c9750b2bd473f3c81a3237fac3310621c856 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 02:50:33 +0300 Subject: [PATCH 82/90] feat: first version of App, so dirty code (sorry) --- app/src/main/kotlin/app/Controller.kt | 79 +++++++++++ app/src/main/kotlin/app/Editor.kt | 135 +++++++++++++++++++ app/src/main/kotlin/composeApp/Controller.kt | 45 ------- app/src/main/kotlin/composeApp/Editor.kt | 92 ------------- 4 files changed, 214 insertions(+), 137 deletions(-) create mode 100644 app/src/main/kotlin/app/Controller.kt create mode 100644 app/src/main/kotlin/app/Editor.kt delete mode 100644 app/src/main/kotlin/composeApp/Controller.kt delete mode 100644 app/src/main/kotlin/composeApp/Editor.kt diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt new file mode 100644 index 0000000..3de4933 --- /dev/null +++ b/app/src/main/kotlin/app/Controller.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import androidx.compose.ui.awt.ComposeWindow +import app.NodeDataGUI +import app.TypeOfDatabase +import repository.JsonRepository +import repository.Repository +import repository.serialization.TypeOfTree +import repository.serialization.strategies.AVLStrategy +import repository.serialization.strategies.BSStrategy +import repository.serialization.strategies.RBStrategy +import trees.AVLTree +import trees.AbstractTree +import trees.BSTree +import trees.RBTree +import java.awt.FileDialog +import java.io.File + +class Controller { + + var repository: Repository>? = null + var tree: AbstractTree? = null + fun openFileDialog( + window: ComposeWindow, title: String, allowedExtensions: List, allowMultiSelection: Boolean = true + ): File? { + val fileDialog = FileDialog(window, title, FileDialog.LOAD) + fileDialog.isMultipleMode = allowMultiSelection + fileDialog.isVisible = true + fileDialog.setFilenameFilter { _, name -> name.endsWith(".jpg") } + return fileDialog.files.firstOrNull() + } + + fun loadDatabase( + typeOfDatabase: TypeOfDatabase?, + typeOfTree: TypeOfTree?, + dirPath: String?, + filename: String?, + ) { + if (dirPath == null || filename == null || typeOfDatabase == null || typeOfTree == null) + return + val strategy = when (typeOfTree) { + TypeOfTree.AVL_TREE -> AVLStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) + TypeOfTree.BS_TREE -> BSStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) + TypeOfTree.RB_TREE -> RBStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) + } + repository = when (typeOfDatabase) { + TypeOfDatabase.Json -> JsonRepository( + strategy, + dirPath, + filename + ) as JsonRepository> +// TypeOfDatabase.Neo4j -> Neo4jRepo(strategy, dirPath) +// TypeOfDatabase.SQL -> SQLRepository(strategy, dirPath) + else -> JsonRepository( + strategy, + dirPath, + filename + ) as JsonRepository> + } + } + + fun getNamesOfTrees(): List { + return repository?.getNames() ?: listOf() + } + + fun loadTree(typeOfTree: TypeOfTree, name: String) { + tree = repository?.loadByName(name) + if (tree == null) { + tree = when (typeOfTree) { + TypeOfTree.RB_TREE -> RBTree() + TypeOfTree.AVL_TREE -> AVLTree() + TypeOfTree.BS_TREE -> BSTree() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/Editor.kt b/app/src/main/kotlin/app/Editor.kt new file mode 100644 index 0000000..4bf6872 --- /dev/null +++ b/app/src/main/kotlin/app/Editor.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import app.EditorController +import app.EditorScreen +import app.NodeDataGUI +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import trees.nodes.AbstractNode + +@Composable +fun > Editor( + viewModel: EditorController, +) { + + LaunchedEffect(Unit) { + withContext(Dispatchers.Default) { + viewModel.initTree() + } + } + + val cScope = rememberCoroutineScope { Dispatchers.Default } + + + Row { + Menu( + onAdd = { key, value -> cScope.launch { viewModel.add(key, value) } }, + onDelete = { cScope.launch { viewModel.delete(it) } }, + onContains = { cScope.launch { viewModel.contains(it) } }, + onSave = { cScope.launch { viewModel.saveTree() } } + ) + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color(245, 245, 245)) + .padding(20.dp) + ) { + MaterialTheme( + colorScheme = MaterialTheme.colorScheme.copy( + surface = Color.White, + ) + ) { + EditorScreen(viewModel) + } + + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Menu( + onAdd: (Int, String) -> Unit, + onDelete: (Int) -> Unit, + onContains: (Int) -> Unit, + onSave: () -> Unit, + + ) { + var keyString by remember { mutableStateOf("") } + var valueString by remember { mutableStateOf("") } + // Размещаем поля ввода в вертикальном столбце + Column( + Modifier.padding(16.dp).width(260.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + TextField( + value = keyString, + onValueChange = { + if (it.isEmpty() || it == "-" || it.toIntOrNull() != null) { + keyString = it + } + }, + label = { Text("Key") }, + modifier = Modifier.fillMaxWidth(), + ) + + TextField( + value = valueString, + onValueChange = { valueString = it }, + label = { Text("Value") }, + modifier = Modifier.fillMaxWidth(), + ) + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceAround + ) { + Button(onClick = { + onAdd(keyString.toInt(), valueString) + keyString = "" + valueString = "" + }) { + Icon(Icons.Default.Add, contentDescription = "Add") + } + Button(onClick = { + onContains(keyString.toInt()) + keyString = "" + valueString = "" + }) { + Icon(Icons.Default.Search, contentDescription = "Contains") + } + Button(onClick = { + onDelete(keyString.toInt()) + keyString = "" + valueString = "" + }) { + Icon(Icons.Default.Delete, contentDescription = "Delete") + } + } + Button( + onClick = onSave, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "Save" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/Controller.kt b/app/src/main/kotlin/composeApp/Controller.kt deleted file mode 100644 index e781b02..0000000 --- a/app/src/main/kotlin/composeApp/Controller.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -import androidx.compose.ui.awt.ComposeWindow -import com.google.gson.Gson -import repository.JsonRepository -import repository.serialization.SerializableValue -import repository.serialization.strategies.AVLStrategy -import java.awt.FileDialog -import java.io.File - -class Controller { - fun openFileDialog( - window: ComposeWindow, title: String, allowedExtensions: List, allowMultiSelection: Boolean = true - ): File? { - val fileDialog = FileDialog(window, title, FileDialog.LOAD) - fileDialog.isMultipleMode = allowMultiSelection - fileDialog.isVisible = true - fileDialog.setFilenameFilter { _, name -> name.endsWith(".jpg") } - return fileDialog.files.firstOrNull() - } - - fun getNamesOfTrees(dirPath: String?, filename: String?): List { - if (dirPath == null || filename == null) - return listOf() - val gson = Gson() - - fun drawableNodeToString(node: DrawableNode): SerializableValue { - return SerializableValue(gson.toJson(node)) - } - - fun jsonStringToDrawableNode(string: SerializableValue): DrawableNode { - return gson.fromJson(string.value, DrawableNode::class.java)!! - } - - val repo = JsonRepository(AVLStrategy(::drawableNodeToString, ::jsonStringToDrawableNode), dirPath) - return repo.getNames() - } - - fun loadTree(name: String) { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/composeApp/Editor.kt b/app/src/main/kotlin/composeApp/Editor.kt deleted file mode 100644 index b604e8f..0000000 --- a/app/src/main/kotlin/composeApp/Editor.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Search -import androidx.compose.material3.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp - -/* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 - */ - -@Composable -fun Editor( - -) { - Row { - Menu() - Box( - modifier = Modifier - .fillMaxSize() - .background(color = Color(245, 245, 245)) - .padding(20.dp) - ) { - MaterialTheme( - colorScheme = MaterialTheme.colorScheme.copy( - surface = Color.White, - ) - ) { -// EditorScreen() - } - - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun Menu( - -) { - val addState = remember { mutableStateOf("") } - val deleteState = remember { mutableStateOf("") } - // Размещаем поля ввода в вертикальном столбце - Column( - Modifier.padding(16.dp).width(260.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - TextField( - value = addState.value, - onValueChange = { addState.value = it }, - label = { Text("Key") }, - modifier = Modifier.fillMaxWidth(), - ) - - TextField( - value = deleteState.value, - onValueChange = { deleteState.value = it }, - label = { Text("Value") }, - modifier = Modifier.fillMaxWidth(), - ) - Row( - Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceAround - ) { - Button(onClick = {}) { - Icon(Icons.Default.Add, contentDescription = "Add") - } - Button(onClick = {}) { - Icon(Icons.Default.Search, contentDescription = "Search") - } - Button(onClick = {}) { - Icon(Icons.Default.Delete, contentDescription = "Delete") - } - } - } -} \ No newline at end of file From 2cb077d9946a4353bfe76c0f56f035a061b789b9 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 13:18:50 +0300 Subject: [PATCH 83/90] feat: simplify some cast expressions --- app/src/main/kotlin/app/Controller.kt | 36 +++++++++++++-------- app/src/main/kotlin/app/EditorController.kt | 14 ++++---- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 3de4933..c17b15c 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -12,17 +12,22 @@ import repository.serialization.TypeOfTree import repository.serialization.strategies.AVLStrategy import repository.serialization.strategies.BSStrategy import repository.serialization.strategies.RBStrategy +import repository.serialization.strategies.Serialization import trees.AVLTree import trees.AbstractTree import trees.BSTree import trees.RBTree +import trees.nodes.AbstractNode import java.awt.FileDialog import java.io.File -class Controller { +class Controller< + NodeType : AbstractNode, + TreeType : AbstractTree, + SerializationType : Serialization> { var repository: Repository>? = null - var tree: AbstractTree? = null + var tree: AbstractTree? = null fun openFileDialog( window: ComposeWindow, title: String, allowedExtensions: List, allowMultiSelection: Boolean = true ): File? { @@ -41,24 +46,20 @@ class Controller { ) { if (dirPath == null || filename == null || typeOfDatabase == null || typeOfTree == null) return - val strategy = when (typeOfTree) { - TypeOfTree.AVL_TREE -> AVLStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) - TypeOfTree.BS_TREE -> BSStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) - TypeOfTree.RB_TREE -> RBStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) - } + val strategy = getStrategy(typeOfTree) repository = when (typeOfDatabase) { TypeOfDatabase.Json -> JsonRepository( strategy, dirPath, filename - ) as JsonRepository> + ) as Repository> // TypeOfDatabase.Neo4j -> Neo4jRepo(strategy, dirPath) // TypeOfDatabase.SQL -> SQLRepository(strategy, dirPath) else -> JsonRepository( strategy, dirPath, filename - ) as JsonRepository> + ) as Repository> } } @@ -67,13 +68,22 @@ class Controller { } fun loadTree(typeOfTree: TypeOfTree, name: String) { - tree = repository?.loadByName(name) + tree = repository?.loadByName(name) as AbstractTree? if (tree == null) { tree = when (typeOfTree) { - TypeOfTree.RB_TREE -> RBTree() - TypeOfTree.AVL_TREE -> AVLTree() - TypeOfTree.BS_TREE -> BSTree() + TypeOfTree.RB_TREE -> RBTree() as AbstractTree + TypeOfTree.AVL_TREE -> AVLTree() as AbstractTree + TypeOfTree.BS_TREE -> BSTree() as AbstractTree } } } + + fun getStrategy(typeOfTree: TypeOfTree): SerializationType { + val strategy = when (typeOfTree) { + TypeOfTree.AVL_TREE -> AVLStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) + TypeOfTree.BS_TREE -> BSStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) + TypeOfTree.RB_TREE -> RBStrategy(NodeDataGUI::serialize, NodeDataGUI::deserialize) + } + return strategy as SerializationType + } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/EditorController.kt b/app/src/main/kotlin/app/EditorController.kt index 3870f43..9859cb9 100644 --- a/app/src/main/kotlin/app/EditorController.kt +++ b/app/src/main/kotlin/app/EditorController.kt @@ -21,7 +21,7 @@ import trees.nodes.RBNode class EditorController>( - private val tree: AbstractTree?, + private val tree: AbstractTree?, private val repository: Repository>?, private val name: String, ) { @@ -30,16 +30,16 @@ class EditorController>( private set fun initTree() { - drawableRoot = tree?.root?.let { toDrawable(it as NodeType, respectXY = true) } + drawableRoot = tree?.root?.let { toDrawable(it, respectXY = true) } } fun resetTree() { - drawableRoot = tree?.root?.let { toDrawable(it as NodeType) } + drawableRoot = tree?.root?.let { toDrawable(it) } } fun saveTree() { fun copyCoordinates(node: NodeType, drawableNode: ImDrawableNode?) { - if (node == null || drawableNode == null) { + if (drawableNode == null) { return } node.left?.let { copyCoordinates(it, drawableNode.left) } @@ -49,14 +49,12 @@ class EditorController>( } copyCoordinates(tree?.root as NodeType, drawableRoot) - if (tree != null) { - repository?.save(name, tree) - } + repository?.save(name, tree) } fun add(key: Int, value: String) { tree?.add(NodeDataGUI(KeyValue(key, value))) - drawableRoot = tree?.root?.let { toDrawable(it as NodeType) } + drawableRoot = tree?.root?.let { toDrawable(it) } } fun delete(key: Int) { From 9672f8841eeda13ce8b61cd6363c8336cc2bf4c6 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 20:47:48 +0300 Subject: [PATCH 84/90] feat: add some checks for correct opening tree --- app/src/main/kotlin/app/App.kt | 4 +- app/src/main/kotlin/app/EditorController.kt | 2 +- app/src/main/kotlin/app/MainActivity.kt | 63 +++++++++++---------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt index cf5667e..e65b7d8 100644 --- a/app/src/main/kotlin/app/App.kt +++ b/app/src/main/kotlin/app/App.kt @@ -39,6 +39,7 @@ fun OpenTree( onNameChanged: (String) -> Unit, onLoadTree: () -> Unit, onLoadDatabase: () -> Unit, + isEnabled: Boolean = false, ) { MaterialTheme { Scaffold(topBar = { @@ -58,7 +59,8 @@ fun OpenTree( DropDownTextFiled("Name of tree", listOfNames, onNameChanged, false) Button( onClick = onLoadTree, - modifier = Modifier.padding(horizontal = 10.dp) + modifier = Modifier.padding(horizontal = 10.dp), + enabled = isEnabled, ) { Text("Let's go!") } diff --git a/app/src/main/kotlin/app/EditorController.kt b/app/src/main/kotlin/app/EditorController.kt index 9859cb9..2bff4b0 100644 --- a/app/src/main/kotlin/app/EditorController.kt +++ b/app/src/main/kotlin/app/EditorController.kt @@ -26,7 +26,7 @@ class EditorController>( private val name: String, ) { - var drawableRoot: ImDrawableNode? by mutableStateOf(toDrawable(tree?.root as NodeType)) + var drawableRoot: ImDrawableNode? by mutableStateOf(toDrawable(tree?.root)) private set fun initTree() { diff --git a/app/src/main/kotlin/app/MainActivity.kt b/app/src/main/kotlin/app/MainActivity.kt index bb165a9..7681383 100644 --- a/app/src/main/kotlin/app/MainActivity.kt +++ b/app/src/main/kotlin/app/MainActivity.kt @@ -9,6 +9,7 @@ import Controller import Editor import androidx.compose.runtime.* import androidx.compose.ui.awt.ComposeWindow +import com.google.gson.JsonSyntaxException import repository.serialization.TypeOfTree import java.io.File @@ -19,19 +20,14 @@ enum class States { enum class TypeOfDatabase { - Neo4j, - Json, - SQL + Neo4j, Json, SQL } @Composable fun app(window: ComposeWindow) { - val typesOfDatabase = remember { mapOf( - "SQL" to TypeOfDatabase.SQL, - "Neo4j" to TypeOfDatabase.Neo4j, - ".json file" to TypeOfDatabase.Json + "SQL" to TypeOfDatabase.SQL, "Neo4j" to TypeOfDatabase.Neo4j, ".json file" to TypeOfDatabase.Json ) } @@ -45,19 +41,34 @@ fun app(window: ComposeWindow) { val listOfTrees = typesOfTrees.keys.toList() val listOfDatabase = typesOfDatabase.keys.toList() - var listNames by remember { mutableStateOf(listOf("test", "main")) } + var listNames by remember { mutableStateOf(listOf("")) } val typeOfTree = remember { mutableStateOf(TypeOfTree.BS_TREE) } val stringTypeOfDatabaseState = remember { mutableStateOf("") } val pathState = remember { mutableStateOf("") } - val typeOfDatabaseState = - remember { mutableStateOf(typesOfDatabase[stringTypeOfDatabaseState.value]) } + val typeOfDatabaseState = remember { mutableStateOf(typesOfDatabase[stringTypeOfDatabaseState.value]) } val state = remember { mutableStateOf(States.OPENING_TREE) } val controller by remember { mutableStateOf(Controller()) } val fileState = remember { mutableStateOf(null) } val nameState = remember { mutableStateOf("") } + + fun loadTrees() { + if (fileState.value?.name?.endsWith(".json") == true) { + try { + controller.loadDatabase( + typeOfDatabaseState.value, typeOfTree.value, fileState.value?.parent, fileState.value?.name + ) + listNames = controller.getNamesOfTrees() + } catch (_: JsonSyntaxException) { + fileState.value = null + } + } else { + fileState.value = null + } + } + when (state.value) { States.OPENING_TREE -> OpenTree( listOfDatabase = listOfDatabase, @@ -69,40 +80,30 @@ fun app(window: ComposeWindow) { onTypeOfDatabaseChanged = { newType -> stringTypeOfDatabaseState.value = newType - typeOfDatabaseState.value = - typesOfDatabase[stringTypeOfDatabaseState.value] + typeOfDatabaseState.value = typesOfDatabase[stringTypeOfDatabaseState.value] }, onPathChanged = { newPath -> pathState.value = newPath }, onFilePicked = { - fileState.value = - controller.openFileDialog( - window, - "Load a file", - listOf(".json"), - allowMultiSelection = false - ) - controller.loadDatabase( - typeOfDatabaseState.value, - typeOfTree.value, - fileState.value?.parent, - fileState.value?.name + fileState.value = controller.openFileDialog( + window, "Load a file", listOf(".json"), allowMultiSelection = false ) - listNames = controller.getNamesOfTrees() + loadTrees() }, onNameChanged = { newName -> nameState.value = newName }, - onTypeChanged = { newType -> typeOfTree.value = typesOfTrees[newType] }, + onTypeChanged = { newType -> + typeOfTree.value = typesOfTrees[newType] + loadTrees() + }, onLoadTree = { state.value = States.DRAW_TREE typeOfTree.value?.let { controller.loadTree(it, nameState.value) } }, onLoadDatabase = { controller.loadDatabase( - typeOfDatabaseState.value, - typeOfTree.value, - pathState.value, - fileState.value?.name + typeOfDatabaseState.value, typeOfTree.value, pathState.value, fileState.value?.name ) - } + }, + isEnabled = (fileState.value != null && nameState.value != "") ) else -> { From 97f923f0b9eb98d55a152dc1d22e89ecd0bbfd80 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 23:14:12 +0300 Subject: [PATCH 85/90] feat: add a button to return to the home screen --- app/src/main/kotlin/app/Editor.kt | 63 ++++++----- app/src/main/kotlin/app/EditorController.kt | 22 ++-- app/src/main/kotlin/app/MainActivity.kt | 101 +++++++++++------- .../{Controller.kt => OpeningController.kt} | 6 +- .../kotlin/app/{App.kt => OpeningScreen.kt} | 2 +- app/src/main/kotlin/app/graph/DrawableNode.kt | 26 ++--- app/src/main/kotlin/app/graph/GraphLine.kt | 17 ++- app/src/main/kotlin/app/graph/GraphNode.kt | 43 ++++---- app/src/main/kotlin/app/graph/GraphState.kt | 29 +++-- app/src/main/kotlin/app/graph/ScreenZoom.kt | 13 +++ app/src/main/kotlin/app/graph/TreeGraph.kt | 18 ++-- 11 files changed, 177 insertions(+), 163 deletions(-) rename app/src/main/kotlin/app/{Controller.kt => OpeningController.kt} (98%) rename app/src/main/kotlin/app/{App.kt => OpeningScreen.kt} (100%) create mode 100644 app/src/main/kotlin/app/graph/ScreenZoom.kt diff --git a/app/src/main/kotlin/app/Editor.kt b/app/src/main/kotlin/app/Editor.kt index 4bf6872..85e7a3f 100644 --- a/app/src/main/kotlin/app/Editor.kt +++ b/app/src/main/kotlin/app/Editor.kt @@ -3,11 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ +package app + import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Search import androidx.compose.material3.* import androidx.compose.runtime.* @@ -15,9 +18,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import app.EditorController -import app.EditorScreen -import app.NodeDataGUI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -25,37 +25,35 @@ import trees.nodes.AbstractNode @Composable fun > Editor( - viewModel: EditorController, + editorController: EditorController, + onGoHome: () -> Unit, ) { LaunchedEffect(Unit) { withContext(Dispatchers.Default) { - viewModel.initTree() + editorController.initTree() } } - val cScope = rememberCoroutineScope { Dispatchers.Default } + val coroutineScope = rememberCoroutineScope { Dispatchers.Default } Row { Menu( - onAdd = { key, value -> cScope.launch { viewModel.add(key, value) } }, - onDelete = { cScope.launch { viewModel.delete(it) } }, - onContains = { cScope.launch { viewModel.contains(it) } }, - onSave = { cScope.launch { viewModel.saveTree() } } + onAdd = { key, value -> coroutineScope.launch { editorController.add(key, value) } }, + onDelete = { coroutineScope.launch { editorController.delete(it) } }, + onContains = { coroutineScope.launch { editorController.contains(it) } }, + onSave = { coroutineScope.launch { editorController.saveTree() } }, + onGoHome = onGoHome, ) Box( modifier = Modifier .fillMaxSize() - .background(color = Color(245, 245, 245)) + .background(color = Color(240, 240, 240)) .padding(20.dp) ) { - MaterialTheme( - colorScheme = MaterialTheme.colorScheme.copy( - surface = Color.White, - ) - ) { - EditorScreen(viewModel) + MaterialTheme { + EditorScreen(editorController) } } @@ -69,8 +67,8 @@ fun Menu( onDelete: (Int) -> Unit, onContains: (Int) -> Unit, onSave: () -> Unit, - - ) { + onGoHome: () -> Unit +) { var keyString by remember { mutableStateOf("") } var valueString by remember { mutableStateOf("") } // Размещаем поля ввода в вертикальном столбце @@ -123,13 +121,26 @@ fun Menu( Icon(Icons.Default.Delete, contentDescription = "Delete") } } - Button( - onClick = onSave, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = "Save" - ) + Row( + Modifier.fillMaxWidth().padding(horizontal = 5.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(15.dp) + ) + { + Button( + onClick = onSave, + modifier = Modifier.weight(2f) + ) { + Text( + text = "Save" + ) + } + Button( + onClick = onGoHome, + modifier = Modifier.weight(1f) + ) { + Icon(Icons.Default.Home, contentDescription = "Go back") + } } } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/EditorController.kt b/app/src/main/kotlin/app/EditorController.kt index 2bff4b0..3a24e38 100644 --- a/app/src/main/kotlin/app/EditorController.kt +++ b/app/src/main/kotlin/app/EditorController.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import app.graph.DrawableNode -import app.graph.ImDrawableNode import repository.Repository import trees.AbstractTree import trees.KeyValue @@ -26,19 +25,15 @@ class EditorController>( private val name: String, ) { - var drawableRoot: ImDrawableNode? by mutableStateOf(toDrawable(tree?.root)) + var drawableRoot: DrawableNode? by mutableStateOf(toDrawable(tree?.root)) private set fun initTree() { - drawableRoot = tree?.root?.let { toDrawable(it, respectXY = true) } - } - - fun resetTree() { - drawableRoot = tree?.root?.let { toDrawable(it) } + drawableRoot = tree?.root?.let { toDrawable(it, savePosition = true) } } fun saveTree() { - fun copyCoordinates(node: NodeType, drawableNode: ImDrawableNode?) { + fun copyCoordinates(node: NodeType, drawableNode: DrawableNode?) { if (drawableNode == null) { return } @@ -58,14 +53,15 @@ class EditorController>( } fun delete(key: Int) { - val res = tree?.delete(NodeDataGUI(KeyValue(key, ""))) + tree?.delete(NodeDataGUI(KeyValue(key, ""))) + drawableRoot = tree?.root?.let { toDrawable(it) } } fun contains(key: Int) { val res = tree?.contains(NodeDataGUI(KeyValue(key, ""))) } - private fun toDrawable(root: NodeType?, respectXY: Boolean = false): ImDrawableNode? { + private fun toDrawable(root: NodeType?, savePosition: Boolean = false): DrawableNode? { if (root == null) { return null } @@ -85,8 +81,8 @@ class EditorController>( } } - drawableNode.x = if (respectXY) node.data.x else ((60.dp * 2 / 3) * resX) - drawableNode.y = if (respectXY) node.data.y else ((60.dp * 5 / 4) * curH) + drawableNode.x = if (savePosition) node.data.x else ((60.dp * 2 / 3) * resX) + drawableNode.y = if (savePosition) node.data.y else ((60.dp * 5 / 4) * curH) if (node is RBNode<*>) { drawableNode.color = when (node.color) { Color.RED -> androidx.compose.ui.graphics.Color.Red @@ -106,7 +102,7 @@ class EditorController>( return drawRoot } - fun dragNode(node: ImDrawableNode, dragAmount: DpOffset) { + fun dragNode(node: DrawableNode, dragAmount: DpOffset) { (node as? DrawableNode)?.let { node.x += dragAmount.x node.y += dragAmount.y diff --git a/app/src/main/kotlin/app/MainActivity.kt b/app/src/main/kotlin/app/MainActivity.kt index 7681383..acffd42 100644 --- a/app/src/main/kotlin/app/MainActivity.kt +++ b/app/src/main/kotlin/app/MainActivity.kt @@ -5,8 +5,6 @@ package app -import Controller -import Editor import androidx.compose.runtime.* import androidx.compose.ui.awt.ComposeWindow import com.google.gson.JsonSyntaxException @@ -49,7 +47,7 @@ fun app(window: ComposeWindow) { val typeOfDatabaseState = remember { mutableStateOf(typesOfDatabase[stringTypeOfDatabaseState.value]) } val state = remember { mutableStateOf(States.OPENING_TREE) } - val controller by remember { mutableStateOf(Controller()) } + val openingController by remember { mutableStateOf(OpeningController()) } val fileState = remember { mutableStateOf(null) } val nameState = remember { mutableStateOf("") } @@ -57,10 +55,10 @@ fun app(window: ComposeWindow) { fun loadTrees() { if (fileState.value?.name?.endsWith(".json") == true) { try { - controller.loadDatabase( + openingController.loadDatabase( typeOfDatabaseState.value, typeOfTree.value, fileState.value?.parent, fileState.value?.name ) - listNames = controller.getNamesOfTrees() + listNames = openingController.getNamesOfTrees() } catch (_: JsonSyntaxException) { fileState.value = null } @@ -69,47 +67,68 @@ fun app(window: ComposeWindow) { } } + fun resetStates() { + fileState.value = null + nameState.value = "" + typeOfDatabaseState.value = null + stringTypeOfDatabaseState.value = "" + typeOfTree.value = null + pathState.value = "" + } + when (state.value) { - States.OPENING_TREE -> OpenTree( - listOfDatabase = listOfDatabase, - listOfNames = listNames, - listOfTypes = listOfTrees, - - typeOfDatabaseState = typeOfDatabaseState, - file = fileState, - - onTypeOfDatabaseChanged = { newType -> - stringTypeOfDatabaseState.value = newType - typeOfDatabaseState.value = typesOfDatabase[stringTypeOfDatabaseState.value] - }, - onPathChanged = { newPath -> pathState.value = newPath }, - onFilePicked = { - fileState.value = controller.openFileDialog( - window, "Load a file", listOf(".json"), allowMultiSelection = false - ) - loadTrees() - }, - onNameChanged = { newName -> nameState.value = newName }, - onTypeChanged = { newType -> - typeOfTree.value = typesOfTrees[newType] - loadTrees() - }, - onLoadTree = { - state.value = States.DRAW_TREE - typeOfTree.value?.let { controller.loadTree(it, nameState.value) } - }, - onLoadDatabase = { - controller.loadDatabase( - typeOfDatabaseState.value, typeOfTree.value, pathState.value, fileState.value?.name - ) - }, - isEnabled = (fileState.value != null && nameState.value != "") - ) + States.OPENING_TREE -> { + window.setSize(700, 700) + OpenTree( + listOfDatabase = listOfDatabase, + listOfNames = listNames, + listOfTypes = listOfTrees, + + typeOfDatabaseState = typeOfDatabaseState, + file = fileState, + + onTypeOfDatabaseChanged = { newType -> + resetStates() + stringTypeOfDatabaseState.value = newType + typeOfDatabaseState.value = typesOfDatabase[stringTypeOfDatabaseState.value] + }, + onPathChanged = { newPath -> pathState.value = newPath }, + onFilePicked = { + fileState.value = openingController.openFileDialog( + window, "Load a file", listOf(".json"), allowMultiSelection = false + ) + loadTrees() + }, + onNameChanged = { newName -> nameState.value = newName }, + onTypeChanged = { newType -> + typeOfTree.value = typesOfTrees[newType] + loadTrees() + }, + onLoadTree = { + state.value = States.DRAW_TREE + typeOfTree.value?.let { openingController.loadTree(it, nameState.value) } + }, + onLoadDatabase = { + openingController.loadDatabase( + typeOfDatabaseState.value, typeOfTree.value, pathState.value, fileState.value?.name + ) + }, + isEnabled = (fileState.value != null && nameState.value != "") + ) + } else -> { window.setSize(1080, 800) Editor( - viewModel = EditorController(controller.tree, controller.repository, nameState.value) + editorController = EditorController( + openingController.tree, + openingController.repository, + nameState.value + ), + onGoHome = { + resetStates() + state.value = States.OPENING_TREE + } ) } } diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/OpeningController.kt similarity index 98% rename from app/src/main/kotlin/app/Controller.kt rename to app/src/main/kotlin/app/OpeningController.kt index c17b15c..b208135 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/OpeningController.kt @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +package app + import androidx.compose.ui.awt.ComposeWindow -import app.NodeDataGUI -import app.TypeOfDatabase import repository.JsonRepository import repository.Repository import repository.serialization.TypeOfTree @@ -21,7 +21,7 @@ import trees.nodes.AbstractNode import java.awt.FileDialog import java.io.File -class Controller< +class OpeningController< NodeType : AbstractNode, TreeType : AbstractTree, SerializationType : Serialization> { diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/OpeningScreen.kt similarity index 100% rename from app/src/main/kotlin/app/App.kt rename to app/src/main/kotlin/app/OpeningScreen.kt index e65b7d8..736a610 100644 --- a/app/src/main/kotlin/app/App.kt +++ b/app/src/main/kotlin/app/OpeningScreen.kt @@ -24,7 +24,6 @@ import java.io.File @Composable fun OpenTree( - listOfDatabase: List, listOfTypes: List, listOfNames: List, @@ -39,6 +38,7 @@ fun OpenTree( onNameChanged: (String) -> Unit, onLoadTree: () -> Unit, onLoadDatabase: () -> Unit, + isEnabled: Boolean = false, ) { MaterialTheme { diff --git a/app/src/main/kotlin/app/graph/DrawableNode.kt b/app/src/main/kotlin/app/graph/DrawableNode.kt index 10cb575..f169a2a 100644 --- a/app/src/main/kotlin/app/graph/DrawableNode.kt +++ b/app/src/main/kotlin/app/graph/DrawableNode.kt @@ -12,26 +12,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -interface ImDrawableNode { - val key: Int - val value: String - val left: ImDrawableNode? - val right: ImDrawableNode? - val color: Color? - val x: Dp - val y: Dp -} - class DrawableNode( - override val key: Int, - override var value: String, - override var left: DrawableNode? = null, - override var right: DrawableNode? = null, - override var color: Color? = null, + val key: Int, + var value: String, + var left: DrawableNode? = null, + var right: DrawableNode? = null, + var color: Color? = null, y: Dp = 0.dp, x: Dp = 0.dp, -) : ImDrawableNode { - override var x by mutableStateOf(x) - override var y by mutableStateOf(y) +) { + var x by mutableStateOf(x) + var y by mutableStateOf(y) } diff --git a/app/src/main/kotlin/app/graph/GraphLine.kt b/app/src/main/kotlin/app/graph/GraphLine.kt index f4d1df7..1b61165 100644 --- a/app/src/main/kotlin/app/graph/GraphLine.kt +++ b/app/src/main/kotlin/app/graph/GraphLine.kt @@ -12,30 +12,29 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp -import visualizer.editor.graph.ScreenScale @Composable fun GraphLine( modifier: Modifier = Modifier, - start: ImDrawableNode, - end: ImDrawableNode, + start: DrawableNode, + end: DrawableNode, nodeSize: Dp, sDragProvider: () -> Offset, - sScaleProvider: () -> ScreenScale + sScaleProvider: () -> ScreenZoom ) { Canvas(modifier = modifier.fillMaxSize()) { val drag = sDragProvider() val scale = sScaleProvider() drawLine( start = Offset( - ((start.x + nodeSize / 2).toPx() + drag.x) * scale.scale + scale.posRelXYScale.x, - ((start.y + nodeSize / 2).toPx() + drag.y) * scale.scale + scale.posRelXYScale.y, + ((start.x + nodeSize / 2).toPx() + drag.x) * scale.scale + scale.offset.x, + ((start.y + nodeSize / 2).toPx() + drag.y) * scale.scale + scale.offset.y, ), end = Offset( - ((end.x + nodeSize / 2).toPx() + drag.x) * scale.scale + scale.posRelXYScale.x, - ((end.y + nodeSize / 2).toPx() + drag.y) * scale.scale + scale.posRelXYScale.y, + ((end.x + nodeSize / 2).toPx() + drag.x) * scale.scale + scale.offset.x, + ((end.y + nodeSize / 2).toPx() + drag.y) * scale.scale + scale.offset.y, ), - strokeWidth = 2f * scale.scale, + strokeWidth = 1.5f * scale.scale, color = Color.Black ) } diff --git a/app/src/main/kotlin/app/graph/GraphNode.kt b/app/src/main/kotlin/app/graph/GraphNode.kt index dbfd9af..20caad5 100644 --- a/app/src/main/kotlin/app/graph/GraphNode.kt +++ b/app/src/main/kotlin/app/graph/GraphNode.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package visualizer.editor.graph +package app.graph import androidx.compose.foundation.* import androidx.compose.foundation.gestures.detectDragGestures @@ -20,63 +20,56 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.* import androidx.compose.ui.layout.* +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import app.graph.ImDrawableNode import kotlin.math.roundToInt @OptIn(ExperimentalFoundationApi::class) @Composable fun GraphNode( modifier: Modifier = Modifier, - node: ImDrawableNode, + node: DrawableNode, nodeSize: Dp, - onNodeDrag: (ImDrawableNode, DpOffset) -> Unit, + onNodeDrag: (DrawableNode, DpOffset) -> Unit, sDragProvider: () -> Offset, - sScaleProvider: () -> ScreenScale + sScaleProvider: () -> ScreenZoom ) { TooltipArea( - modifier = modifier.zIndex(5f) // nodes must be in the front of the screen, covering lines + modifier = modifier.zIndex(5f) .layout { measurable: Measurable, _: Constraints -> - // avoid recomposition of nodes by reading scale, drag and cords in layout stage - val placeable = measurable.measure( - // set fixed size = node size * scale Constraints.fixed( (nodeSize * sScaleProvider().scale).roundToPx(), (nodeSize * sScaleProvider().scale).roundToPx() ) ) - layout(placeable.width, placeable.height) { val drag = sDragProvider() val scale = sScaleProvider() - - // place node considering screen drag and scale placeable.placeRelative( - ((node.x.toPx() + drag.x) * scale.scale + scale.posRelXYScale.x).roundToInt(), - ((node.y.toPx() + drag.y) * scale.scale + scale.posRelXYScale.y).roundToInt(), + ((node.x.toPx() + drag.x) * scale.scale + scale.offset.x).roundToInt(), + ((node.y.toPx() + drag.y) * scale.scale + scale.offset.y).roundToInt(), ) } }, tooltip = { Surface( shape = MaterialTheme.shapes.medium, - shadowElevation = 3.dp ) { Text( text = "Key: ${node.key}\nValue: ${node.value}", - modifier = Modifier.padding(13.dp) + modifier = Modifier.padding(15.dp) ) } }, tooltipPlacement = TooltipPlacement.CursorPoint( - offset = DpOffset(0.dp, 16.dp) + offset = DpOffset(0.dp, (-100).dp) ), - delayMillis = 800, + delayMillis = 600, ) { Box(modifier = modifier .fillMaxSize() @@ -85,21 +78,19 @@ fun GraphNode( shape = CircleShape ) .pointerInput(node) { - detectDragGestures { change, dragAmount -> + detectDragGestures { change, offset -> change.consume() - val scale = sScaleProvider().scale onNodeDrag( node, DpOffset( - dragAmount.x.toDp() / scale, - dragAmount.y.toDp() / scale + offset.x.toDp() / scale, + offset.y.toDp() / scale ), ) } } ) { - NodeText( modifier = Modifier.align(Alignment.Center), text = node.key.toString(), @@ -118,7 +109,11 @@ fun NodeText( val scale = scaleProvider() Text( modifier = modifier, - text = if (text.length > 4) text.substring(0, 4) + ".." else text, + text = if (text.length > 4) text.substring(0, 5) + ".." else text, color = MaterialTheme.colorScheme.onPrimary, + style = TextStyle( + fontSize = MaterialTheme.typography.bodyMedium.fontSize * scale, + lineHeight = MaterialTheme.typography.bodyMedium.lineHeight * scale + ) ) } diff --git a/app/src/main/kotlin/app/graph/GraphState.kt b/app/src/main/kotlin/app/graph/GraphState.kt index 4f42507..8fb6f3f 100644 --- a/app/src/main/kotlin/app/graph/GraphState.kt +++ b/app/src/main/kotlin/app/graph/GraphState.kt @@ -3,57 +3,52 @@ * SPDX-License-Identifier: Apache-2.0 */ -package visualizer.editor.graph +package app.graph import androidx.compose.runtime.* import androidx.compose.ui.geometry.Offset import kotlin.math.max import kotlin.math.min -data class ScreenScale( - val scale: Float, - val posRelXYScale: Offset -) - class GraphState { - var screenScale by mutableStateOf(ScreenScale(1f, Offset(0f, 0f))) + var screenZoom by mutableStateOf(ScreenZoom(1f, Offset(0f, 0f))) private set var screenDrag by mutableStateOf(Offset(0f, 0f)) private set fun handleScroll(scrollDelta: Offset, scrollPosition: Offset) { - screenDrag += Offset(-scrollDelta.x / screenScale.scale * 25, 0f) + screenDrag += Offset(-scrollDelta.x / screenZoom.scale * 25, 0f) - val prevScale = screenScale.scale + val prevScale = screenZoom.scale val newScale = min( max( - screenScale.scale - scrollDelta.y / 20, + screenZoom.scale - scrollDelta.y / 20, 0.1f ), 2f ) val relScale = newScale / prevScale - screenScale = ScreenScale( + screenZoom = ScreenZoom( scale = newScale, - posRelXYScale = Offset( - screenScale.posRelXYScale.x * relScale + scrollPosition.x * (1 - relScale), - screenScale.posRelXYScale.y * relScale + scrollPosition.y * (1 - relScale) + offset = Offset( + screenZoom.offset.x * relScale + scrollPosition.x * (1 - relScale), + screenZoom.offset.y * relScale + scrollPosition.y * (1 - relScale) ) ) } fun handleScreenDrag(dragAmount: Offset) { screenDrag += Offset( - dragAmount.x / screenScale.scale, - dragAmount.y / screenScale.scale + dragAmount.x / screenZoom.scale, + dragAmount.y / screenZoom.scale ) } fun resetGraphView(dragX: Float = 0f, dragY: Float = 0f) { screenDrag = Offset(dragX, dragY) - screenScale = ScreenScale(1f, Offset(0f, 0f)) + screenZoom = ScreenZoom(1f, Offset(0f, 0f)) } } diff --git a/app/src/main/kotlin/app/graph/ScreenZoom.kt b/app/src/main/kotlin/app/graph/ScreenZoom.kt new file mode 100644 index 0000000..fe5bac1 --- /dev/null +++ b/app/src/main/kotlin/app/graph/ScreenZoom.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023 teemEight + * SPDX-License-Identifier: Apache-2.0 + */ + +package app.graph + +import androidx.compose.ui.geometry.Offset + +data class ScreenZoom( + val scale: Float, + val offset: Offset +) \ No newline at end of file diff --git a/app/src/main/kotlin/app/graph/TreeGraph.kt b/app/src/main/kotlin/app/graph/TreeGraph.kt index ef3a112..a4941a3 100644 --- a/app/src/main/kotlin/app/graph/TreeGraph.kt +++ b/app/src/main/kotlin/app/graph/TreeGraph.kt @@ -25,18 +25,14 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import visualizer.editor.graph.GraphNode -import visualizer.editor.graph.GraphState -import visualizer.editor.graph.ScreenScale -import visualizer.editor.graph.rememberGraphState @OptIn(ExperimentalComposeUiApi::class) @Composable fun TreeGraph( - root: ImDrawableNode, + root: DrawableNode, nodeSize: Dp, - onNodeDrag: (ImDrawableNode, DpOffset) -> Unit, + onNodeDrag: (DrawableNode, DpOffset) -> Unit, graphState: GraphState = rememberGraphState() ) { val currentDensity = LocalDensity.current @@ -77,7 +73,7 @@ fun TreeGraph( nodeSize = nodeSize, onNodeDrag = onNodeDrag, sDragProvider = { graphState.screenDrag }, - sScaleProvider = { graphState.screenScale } + sScaleProvider = { graphState.screenZoom } ) TextButton( @@ -91,12 +87,12 @@ fun TreeGraph( @Composable fun drawTree( - node: ImDrawableNode?, - parent: ImDrawableNode? = null, + node: DrawableNode?, + parent: DrawableNode? = null, nodeSize: Dp, - onNodeDrag: (ImDrawableNode, DpOffset) -> Unit, + onNodeDrag: (DrawableNode, DpOffset) -> Unit, sDragProvider: () -> Offset, - sScaleProvider: () -> ScreenScale + sScaleProvider: () -> ScreenZoom ) { node?.let { parent?.let { parent -> From c99838f828c5cf52da1fa6fa22209b790d13e0d8 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 23:26:44 +0300 Subject: [PATCH 86/90] fix: fix name of text fields --- app/src/main/kotlin/app/EditorController.kt | 32 ++++++++++--------- app/src/main/kotlin/app/OpeningScreen.kt | 8 ++--- app/src/main/kotlin/app/graph/DrawableNode.kt | 1 - 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/src/main/kotlin/app/EditorController.kt b/app/src/main/kotlin/app/EditorController.kt index 3a24e38..1ff01dc 100644 --- a/app/src/main/kotlin/app/EditorController.kt +++ b/app/src/main/kotlin/app/EditorController.kt @@ -25,50 +25,52 @@ class EditorController>( private val name: String, ) { - var drawableRoot: DrawableNode? by mutableStateOf(toDrawable(tree?.root)) + var drawableRoot: DrawableNode? by mutableStateOf(toDrawableNode(tree?.root)) private set fun initTree() { - drawableRoot = tree?.root?.let { toDrawable(it, savePosition = true) } + drawableRoot = tree?.root?.let { toDrawableNode(it, savePosition = true) } } fun saveTree() { - fun copyCoordinates(node: NodeType, drawableNode: DrawableNode?) { + fun saveCoordinates(node: NodeType, drawableNode: DrawableNode?) { if (drawableNode == null) { return } - node.left?.let { copyCoordinates(it, drawableNode.left) } + node.left?.let { saveCoordinates(it, drawableNode.left) } node.data.x = drawableNode.x node.data.y = drawableNode.y - node.right?.let { copyCoordinates(it, drawableNode.right) } + node.right?.let { saveCoordinates(it, drawableNode.right) } } - copyCoordinates(tree?.root as NodeType, drawableRoot) - repository?.save(name, tree) + tree?.root?.let { saveCoordinates(it, drawableRoot) } + if (tree != null) { + repository?.save(name, tree) + } } fun add(key: Int, value: String) { tree?.add(NodeDataGUI(KeyValue(key, value))) - drawableRoot = tree?.root?.let { toDrawable(it) } + drawableRoot = tree?.root?.let { toDrawableNode(it) } } fun delete(key: Int) { tree?.delete(NodeDataGUI(KeyValue(key, ""))) - drawableRoot = tree?.root?.let { toDrawable(it) } + drawableRoot = tree?.root?.let { toDrawableNode(it) } } fun contains(key: Int) { val res = tree?.contains(NodeDataGUI(KeyValue(key, ""))) } - private fun toDrawable(root: NodeType?, savePosition: Boolean = false): DrawableNode? { + private fun toDrawableNode(root: NodeType?, savePosition: Boolean = false): DrawableNode? { if (root == null) { return null } val drawRoot = DrawableNode(root.data.data.key, root.data.data.value) - fun calcCoordinates( + fun calculateCoordinates( node: NodeType, drawableNode: DrawableNode, offsetX: Int, @@ -77,7 +79,7 @@ class EditorController>( var resX = offsetX node.left?.let { left -> drawableNode.left = DrawableNode(left.data.data.key, left.data.data.value).also { drawLeft -> - resX = calcCoordinates(left, drawLeft, offsetX, curH + 1) + 1 + resX = calculateCoordinates(left, drawLeft, offsetX, curH + 1) + 1 } } @@ -92,18 +94,18 @@ class EditorController>( node.right?.let { right -> drawableNode.right = DrawableNode(right.data.data.key, right.data.data.value).also { drawRight -> - resX = calcCoordinates(right, drawRight, resX + 1, curH + 1) + resX = calculateCoordinates(right, drawRight, resX + 1, curH + 1) } } return resX } - calcCoordinates(root as NodeType, drawRoot, 0, 0) + calculateCoordinates(root, drawRoot, 0, 0) return drawRoot } fun dragNode(node: DrawableNode, dragAmount: DpOffset) { - (node as? DrawableNode)?.let { + (node).let { node.x += dragAmount.x node.y += dragAmount.y } diff --git a/app/src/main/kotlin/app/OpeningScreen.kt b/app/src/main/kotlin/app/OpeningScreen.kt index 736a610..253b8d1 100644 --- a/app/src/main/kotlin/app/OpeningScreen.kt +++ b/app/src/main/kotlin/app/OpeningScreen.kt @@ -43,7 +43,7 @@ fun OpenTree( ) { MaterialTheme { Scaffold(topBar = { - TopAppBar(title = { Text("Выберите тип дерева") }) + TopAppBar(title = { Text("Tree viewer by teemEight") }) }) { Column(Modifier.padding(10.dp)) { DropDownTextFiled("Type of database", listOfDatabase, onTypeOfDatabaseChanged) @@ -83,7 +83,7 @@ fun FilePicker( onClick = onFilePicked ) { Text( - text = "Выберите файл", + text = "Choose File", ) } Spacer(modifier = Modifier.width(8.dp)) @@ -107,13 +107,13 @@ fun PathToStorage( onPathChanged(it) path = it }, - label = { Text(text = "Введите путь к базе") }, + label = { Text(text = "Path to database") }, modifier = Modifier.fillMaxWidth() ) Button( onClick = onLoadDatabase ) { - Text("Загрузить") + Text("Load") } } } diff --git a/app/src/main/kotlin/app/graph/DrawableNode.kt b/app/src/main/kotlin/app/graph/DrawableNode.kt index f169a2a..7112034 100644 --- a/app/src/main/kotlin/app/graph/DrawableNode.kt +++ b/app/src/main/kotlin/app/graph/DrawableNode.kt @@ -23,5 +23,4 @@ class DrawableNode( ) { var x by mutableStateOf(x) var y by mutableStateOf(y) - } From f276c79b75741e20d947a2792e2871c83b5b51f7 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 23:36:19 +0300 Subject: [PATCH 87/90] feat: update CONTRIBUTING.md, DOCS.md, LICENSE and README.md --- CONTRIBUTING.md | 53 +++++++++++++++++++++--------------------- DOCS.md | 19 +++++++-------- LICENSE.txt => LICENSE | 0 README.md | 2 +- 4 files changed, 35 insertions(+), 39 deletions(-) rename LICENSE.txt => LICENSE (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf4ddac..20c1ee1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,40 +1,39 @@ -# Внесение правок +# Making edits -## Основные советы +## Basic Tips -1. Не используйте merge, только rebase (для сохранения линейной истории коммитов) -2. Не менять чужие ветки без крайней необходимости -3. Перепроверьте историю коммитов перед созданием пулл реквеста -4. **Перепроверьте, что вы в правильной ветке**, никогда не коммитьте напрямую в main +1. Don't use merge, only rebase (to keep a linear commit history) +2. Do not change other people's branches unless absolutely necessary +3. Recheck your commit history before creating a pull request +4. **Check you're on the right branch**, never commit directly in main -## Правила добавления коммитов +## Rules for adding commits -Коммиты добавляются в соответствии с conventional commits. Т.е +Commits are added according to conventional commits. Those `(): `. -Поле `` должно принимать одно из этих значений: +The `` field must take one of these values: -* `feat` для добавления новой функциональности -* `fix` для исправления бага в программе -* `refactor` для рефакторинга кода, например, переименования переменной -* `test` для добавления тестов, их рефакторинга -* `struct` для изменений связанных с изменением структуры проекта (НО НЕ КОДА), например изменение - расположения папок -* `ci` для различных задач ci/cd +* `feat` to add new functionality +* `fix` to fix a bug in the program +* `refactor` for code refactoring, such as renaming a variable +* `test` to add tests, refactor them +* `struct` for changes related to a change in the structure of the project (BUT NOT CODE), for example, changing + folder locations +* `ci` for various ci/cd tasks -Поле `` содержит суть изменений в повелительном наклонении настоящего времени на английском языке без точки в -конце, первое слово - глагол с маленькой буквы. -Примеры: +The `` field contains the gist of the changes in the present imperative in English without the dot in +at the end, the first word is a verb with a small letter. +Examples: -* Хорошо: "feat: Add module for future BST implementations" -* Плохо: "Added module for future BST implementations." +* Good: "feat: Add module for future BST implementations" +* Bad: "Added module for future BST implementations." -## Правила для пулл реквестов +## Rules for pull requests -**НЕ ТЫКАТЬ НА ЗЕЛЕНУЮ КНОПОЧКУ `REBASE AND MERGE` БЕЗ РЕВЬЮ** +**Forbidden** to merge your pull request into the branch yourself. -**Запрещено** сливать свой пулл реквест в ветку самостоятельно. +If you click on the green button, then **make sure** that it says `REBASE AND MERGE` -Если тыкаете на зеленую кнопочку, то **убедитесь**, что на ней написано `REBASE AND MERGE` - -Ревью происходит в виде комметариев к пулл реквестам, обсуждения в чате команды и личном общении. \ No newline at end of file +The review takes place in the form of comments to pull requests, discussions in the team chat and personal +communication. \ No newline at end of file diff --git a/DOCS.md b/DOCS.md index b4776e4..46adcd2 100644 --- a/DOCS.md +++ b/DOCS.md @@ -12,10 +12,10 @@ Any `Comparable` data can be stored in trees. We also provide access to the `KeyValue` class, which allows you to store a key-value pair in the nodes of the tree. ```kotlin -import app.trees.trees.AVLTree -import app.trees.trees.BSTree -import app.trees.trees.RBTree -import app.trees.KeyValue +import lib.trees.AVLTree +import lib.trees.RBTree +import lib.trees.BSTree +import lib.trees.KeyValue val alvTree = AVLTree() // instantiate empty AVL tree val bsTree = BSTree() // instantiate empty simple tree @@ -58,15 +58,15 @@ avlTree.root?.right?.left?.data // 20 ## Storing Trees -`teemEight` provides ~~`JsonRepository`, `SqlRepository` and~~ `Neo4jRepository` to save & load trees. +`teemEight` provides `JsonRepository`, `SqlRepository` and `Neo4jRepository` to save & load trees. Each instance of repository is used to store exactly 1 tree type. To store different tree types several repositories can be instantiated. Repository must be provided with `Serialization` which describes how to serialize & deserialize any particular type of tree. -`bstrees` is shipped with `AVLStrategy`, ~~`RBStrategy` and `SimpleStrategy`~~ to serialize & deserialize AVL trees, -~~Red-black trees and Simple BSTs~~ respectively. As these strategies don't know anything about the data type stored in +`bstrees` is shipped with `AVLStrategy`, `RBStrategy` and `SimpleStrategy` to serialize & deserialize AVL trees, +Red-black trees and Simple BSTs respectively. As these strategies don't know anything about the data type stored in trees' nodes, user must provide `serializeData` and `deserializeData` functions to them. Different tree types can be stored in the same database (directory) by creating several repositories and passing them @@ -84,9 +84,6 @@ Before using this, you must have [Docker](https://www.docker.com/) (also see [do 4. Change the password 5. You got this -Please note that storing 1 tree type (RB, AVL, Simple) with different data type in the same database (directory) is not -supported. - #### Example ```kotlin @@ -102,7 +99,7 @@ fun serializeInt(data: Int) = SerializableValue(data.toString()) fun deserializeInt(data: SerializableValue) = data.value.toInt() val avlRepo = Neo4jRepo(AVLStrategy(::serializeInt, ::deserializeInt), conf) -// !!! storing AVLTree and AVLTree in the same db is unsupported + val tree = AVLTree() val randomizer = Random(42) diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/README.md b/README.md index 6c34770..c646b5d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ or `neo4j` databases. - [x] Neo4j - [x] Sqlite - [x] json -- [ ] GUI +- [x] GUI ## Building From 8d4c82dd1d57220f59719478c8d3c728fe2e4fd8 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 23:46:55 +0300 Subject: [PATCH 88/90] feat: update README.md, add tips for using viewer --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c646b5d..a668993 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,19 @@ To build this project run ## How to use -See official [documentation](/DOCS.md) +If you need access to trees, see official [documentation](/DOCS.md) + +To run our tree viewer + +```bash +./gradlew run +``` + +The viewer is currently only able to open trees stored in a json file. +Create an empty .json file and open it in the application, enter any name for the tree +and it will be created. You can drag and drop tree nodes, add and remove values, +save the state of the tree for later loading. If there are available trees in the .json file, they will be in the +dropdown list. ## Feedback From cf276973bb114b448c3f4c9ae97cd35a0f2b7f06 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Tue, 2 May 2023 23:50:51 +0300 Subject: [PATCH 89/90] fix: fix bug with empty key field --- app/src/main/kotlin/app/Editor.kt | 6 +++--- app/src/main/kotlin/app/graph/DrawableNode.kt | 4 ++-- app/src/main/kotlin/app/graph/GraphLine.kt | 4 ++-- app/src/main/kotlin/app/graph/GraphNode.kt | 4 ++-- app/src/main/kotlin/app/graph/GraphState.kt | 4 ++-- app/src/main/kotlin/app/graph/ScreenZoom.kt | 4 ++-- app/src/main/kotlin/app/graph/TreeGraph.kt | 4 ++-- .../main/kotlin/repository/serialization/Serializable.kt | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/app/Editor.kt b/app/src/main/kotlin/app/Editor.kt index 85e7a3f..db17834 100644 --- a/app/src/main/kotlin/app/Editor.kt +++ b/app/src/main/kotlin/app/Editor.kt @@ -100,21 +100,21 @@ fun Menu( horizontalArrangement = Arrangement.SpaceAround ) { Button(onClick = { - onAdd(keyString.toInt(), valueString) + if (keyString.isNotEmpty()) onAdd(keyString.toInt(), valueString) keyString = "" valueString = "" }) { Icon(Icons.Default.Add, contentDescription = "Add") } Button(onClick = { - onContains(keyString.toInt()) + if (keyString.isNotEmpty()) onContains(keyString.toInt()) keyString = "" valueString = "" }) { Icon(Icons.Default.Search, contentDescription = "Contains") } Button(onClick = { - onDelete(keyString.toInt()) + if (keyString.isNotEmpty()) onDelete(keyString.toInt()) keyString = "" valueString = "" }) { diff --git a/app/src/main/kotlin/app/graph/DrawableNode.kt b/app/src/main/kotlin/app/graph/DrawableNode.kt index 7112034..66efef8 100644 --- a/app/src/main/kotlin/app/graph/DrawableNode.kt +++ b/app/src/main/kotlin/app/graph/DrawableNode.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package app.graph diff --git a/app/src/main/kotlin/app/graph/GraphLine.kt b/app/src/main/kotlin/app/graph/GraphLine.kt index 1b61165..53bae82 100644 --- a/app/src/main/kotlin/app/graph/GraphLine.kt +++ b/app/src/main/kotlin/app/graph/GraphLine.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package app.graph diff --git a/app/src/main/kotlin/app/graph/GraphNode.kt b/app/src/main/kotlin/app/graph/GraphNode.kt index 20caad5..fc47d39 100644 --- a/app/src/main/kotlin/app/graph/GraphNode.kt +++ b/app/src/main/kotlin/app/graph/GraphNode.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package app.graph diff --git a/app/src/main/kotlin/app/graph/GraphState.kt b/app/src/main/kotlin/app/graph/GraphState.kt index 8fb6f3f..e195544 100644 --- a/app/src/main/kotlin/app/graph/GraphState.kt +++ b/app/src/main/kotlin/app/graph/GraphState.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package app.graph diff --git a/app/src/main/kotlin/app/graph/ScreenZoom.kt b/app/src/main/kotlin/app/graph/ScreenZoom.kt index fe5bac1..18a082c 100644 --- a/app/src/main/kotlin/app/graph/ScreenZoom.kt +++ b/app/src/main/kotlin/app/graph/ScreenZoom.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package app.graph diff --git a/app/src/main/kotlin/app/graph/TreeGraph.kt b/app/src/main/kotlin/app/graph/TreeGraph.kt index a4941a3..004893f 100644 --- a/app/src/main/kotlin/app/graph/TreeGraph.kt +++ b/app/src/main/kotlin/app/graph/TreeGraph.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package app.graph diff --git a/lib/src/main/kotlin/repository/serialization/Serializable.kt b/lib/src/main/kotlin/repository/serialization/Serializable.kt index 6d87aad..b3b8074 100644 --- a/lib/src/main/kotlin/repository/serialization/Serializable.kt +++ b/lib/src/main/kotlin/repository/serialization/Serializable.kt @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023 teemEight - * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2023 teem-4 + * SPDX-License-Identifier: MIT */ package repository.serialization From 0b7e4823721303ac2e37e1d89c12e28c5162c610 Mon Sep 17 00:00:00 2001 From: RoketFlame <72881638+RoketFlame@users.noreply.github.com> Date: Wed, 3 May 2023 20:20:45 +0300 Subject: [PATCH 90/90] fix: Change licence of stolen files --- app/src/main/kotlin/Main.kt | 23 ------------------ app/src/main/kotlin/app/graph/DrawableNode.kt | 23 ++++++++++++++++-- app/src/main/kotlin/app/graph/GraphLine.kt | 24 ++++++++++++++++--- app/src/main/kotlin/app/graph/GraphNode.kt | 23 ++++++++++++++++-- app/src/main/kotlin/app/graph/GraphState.kt | 23 ++++++++++++++++-- app/src/main/kotlin/app/graph/ScreenZoom.kt | 23 ++++++++++++++++-- app/src/main/kotlin/app/graph/TreeGraph.kt | 23 ++++++++++++++++-- .../repository/serialization/Serializable.kt | 2 +- 8 files changed, 127 insertions(+), 37 deletions(-) diff --git a/app/src/main/kotlin/Main.kt b/app/src/main/kotlin/Main.kt index a1695ad..42e49de 100644 --- a/app/src/main/kotlin/Main.kt +++ b/app/src/main/kotlin/Main.kt @@ -26,27 +26,4 @@ fun main() = application { window.minimumSize = Dimension(700, 700) app(window) } -// val username = "neo4j" -// val password = "isabel-except-toronto-monaco-never-5754" // insert password to database here -// val conf = Configuration.Builder() -// .uri("bolt://localhost") -// .credentials(username, password) -// .build() -// -// fun serializeInt(data: Int) = SerializableValue(data.toString()) -// -// fun deserializeInt(data: SerializableValue) = data.value.toInt() -// -// val avlRepo = Neo4jRepo(AVLStrategy(::serializeInt, ::deserializeInt), conf) -//// !!! storing AVLTree and AVLTree in the same db is unsupported -// -// val tree = AVLTree() -// val randomizer = Random(42) -// val lst = List(15) { randomizer.nextInt(1000) } -// lst.forEach { tree.add(it) } -// avlRepo.save("test", tree) -// val testTree = avlRepo.loadByName("test") -// println(testTree.preOrder()) // output pre-order traversal of tree -// avlRepo.save("wow", tree) -// println(avlRepo.getNames()) } \ No newline at end of file diff --git a/app/src/main/kotlin/app/graph/DrawableNode.kt b/app/src/main/kotlin/app/graph/DrawableNode.kt index 66efef8..7a5023f 100644 --- a/app/src/main/kotlin/app/graph/DrawableNode.kt +++ b/app/src/main/kotlin/app/graph/DrawableNode.kt @@ -1,6 +1,25 @@ /* - * Copyright (c) 2023 teem-4 - * SPDX-License-Identifier: MIT + MIT License + +Copyright (c) 2023-present Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ package app.graph diff --git a/app/src/main/kotlin/app/graph/GraphLine.kt b/app/src/main/kotlin/app/graph/GraphLine.kt index 53bae82..3d06eee 100644 --- a/app/src/main/kotlin/app/graph/GraphLine.kt +++ b/app/src/main/kotlin/app/graph/GraphLine.kt @@ -1,8 +1,26 @@ /* - * Copyright (c) 2023 teem-4 - * SPDX-License-Identifier: MIT - */ + MIT License + +Copyright (c) 2023-present Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ package app.graph import androidx.compose.foundation.Canvas diff --git a/app/src/main/kotlin/app/graph/GraphNode.kt b/app/src/main/kotlin/app/graph/GraphNode.kt index fc47d39..6344ae7 100644 --- a/app/src/main/kotlin/app/graph/GraphNode.kt +++ b/app/src/main/kotlin/app/graph/GraphNode.kt @@ -1,6 +1,25 @@ /* - * Copyright (c) 2023 teem-4 - * SPDX-License-Identifier: MIT + MIT License + +Copyright (c) 2023-present Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ package app.graph diff --git a/app/src/main/kotlin/app/graph/GraphState.kt b/app/src/main/kotlin/app/graph/GraphState.kt index e195544..b8e5e37 100644 --- a/app/src/main/kotlin/app/graph/GraphState.kt +++ b/app/src/main/kotlin/app/graph/GraphState.kt @@ -1,6 +1,25 @@ /* - * Copyright (c) 2023 teem-4 - * SPDX-License-Identifier: MIT + MIT License + +Copyright (c) 2023-present Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ package app.graph diff --git a/app/src/main/kotlin/app/graph/ScreenZoom.kt b/app/src/main/kotlin/app/graph/ScreenZoom.kt index 18a082c..bfc888f 100644 --- a/app/src/main/kotlin/app/graph/ScreenZoom.kt +++ b/app/src/main/kotlin/app/graph/ScreenZoom.kt @@ -1,6 +1,25 @@ /* - * Copyright (c) 2023 teem-4 - * SPDX-License-Identifier: MIT + MIT License + +Copyright (c) 2023-present Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ package app.graph diff --git a/app/src/main/kotlin/app/graph/TreeGraph.kt b/app/src/main/kotlin/app/graph/TreeGraph.kt index 004893f..070d1ae 100644 --- a/app/src/main/kotlin/app/graph/TreeGraph.kt +++ b/app/src/main/kotlin/app/graph/TreeGraph.kt @@ -1,6 +1,25 @@ /* - * Copyright (c) 2023 teem-4 - * SPDX-License-Identifier: MIT + MIT License + +Copyright (c) 2023-present Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ package app.graph diff --git a/lib/src/main/kotlin/repository/serialization/Serializable.kt b/lib/src/main/kotlin/repository/serialization/Serializable.kt index b3b8074..89d048f 100644 --- a/lib/src/main/kotlin/repository/serialization/Serializable.kt +++ b/lib/src/main/kotlin/repository/serialization/Serializable.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 teem-4 + * Copyright (c) 2023 Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim * SPDX-License-Identifier: MIT */