From c684c27004e185e4de9fe2cd94005f12b57a33db Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 23 Mar 2023 05:28:46 +0000 Subject: [PATCH 001/118] Setting up GitHub Classroom Feedback From 2440aca76a502d4d881b185ccdf79803a475a23e Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Mon, 27 Mar 2023 21:02:40 +0300 Subject: [PATCH 002/118] chore: Add .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b6985c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build From 3a6e826c4d09b58c6014776f17e98d28971cb62a Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Mon, 27 Mar 2023 21:03:43 +0300 Subject: [PATCH 003/118] chore: Set up project structure --- .gitattributes | 9 + .idea/.gitignore | 8 + .idea/.name | 1 + .idea/compiler.xml | 6 + .idea/gradle.xml | 19 ++ .idea/jarRepositories.xml | 20 ++ .idea/kotlinc.xml | 6 + .idea/misc.xml | 8 + app/build.gradle.kts | 32 +++ app/src/main/kotlin/app/App.kt | 5 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 +++++++++++++++++++++++ gradlew.bat | 92 +++++++++ settings.gradle.kts | 12 ++ trees/build.gradle.kts | 23 +++ 16 files changed, 491 insertions(+) create mode 100644 .gitattributes create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/misc.xml create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/kotlin/app/App.kt create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts create mode 100644 trees/build.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/.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/.idea/.name b/.idea/.name new file mode 100644 index 0000000..9086501 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +trees-3 \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..e58d3e4 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..9a809d6 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..fdc392f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..0fc3113 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0f221d8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..4198206 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,32 @@ +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.2") + + implementation(project(":trees")) +} + +application { + // Define the main class for the application. + mainClass.set("app.AppKt") +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt new file mode 100644 index 0000000..912d243 --- /dev/null +++ b/app/src/main/kotlin/app/App.kt @@ -0,0 +1,5 @@ +package app + +fun main() { + print("Hello World!") +} 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 100755 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..e36016e --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,12 @@ +/* + * 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-3" +include("app",) +include("trees") diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts new file mode 100644 index 0000000..0a4ec88 --- /dev/null +++ b/trees/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + id("org.jetbrains.kotlin.jvm") version "1.8.10" +} + +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") +} + + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} \ No newline at end of file From 551bf1b236a5dd6db7ddcc9f55de9a654ea51be3 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Mon, 27 Mar 2023 21:30:51 +0300 Subject: [PATCH 004/118] fix(repo): Delete .idea dir and add proper .gitignore --- .gitignore | 184 +++++++++++++++++++++++++++++++++++++- .idea/.gitignore | 8 -- .idea/.name | 1 - .idea/compiler.xml | 6 -- .idea/gradle.xml | 19 ---- .idea/jarRepositories.xml | 20 ----- .idea/kotlinc.xml | 6 -- .idea/misc.xml | 8 -- 8 files changed, 181 insertions(+), 71 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/gradle.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/misc.xml diff --git a/.gitignore b/.gitignore index 1b6985c..aa756d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,183 @@ -# Ignore Gradle project-specific cache directory +# Created by https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=kotlin,java,gradle,intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Kotlin ### +# Compiled class file + +# Log file + +# BlueJ files + +# Mobile Tools for Java (J2ME) + +# Package Files # + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml + +### Gradle ### .gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof -# Ignore Gradle build output directory -build +# End of https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij 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 diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 9086501..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -trees-3 \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index e58d3e4..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 9a809d6..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index fdc392f..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 0fc3113..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0f221d8..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file From 4a156c03c193fc3ee8d6a7f4f13b8bb111c558c9 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 28 Mar 2023 00:09:46 +0300 Subject: [PATCH 005/118] feat(repo): add general project structure --- README.md | 3 ++ trees/src/main/kotlin/bst/AVLTree.kt | 4 ++ trees/src/main/kotlin/bst/BinarySearchTree.kt | 42 ++++++++++++++++ trees/src/main/kotlin/bst/Main.kt | 9 ++++ trees/src/main/kotlin/bst/RedBlackTree.kt | 48 +++++++++++++++++++ trees/src/main/kotlin/bst/Tree.kt | 9 ++++ trees/src/main/kotlin/bst/nodes/AVLNode.kt | 4 ++ trees/src/main/kotlin/bst/nodes/BSTNode.kt | 9 ++++ trees/src/main/kotlin/bst/nodes/RBTNode.kt | 28 +++++++++++ trees/src/main/kotlin/bst/nodes/TreeNode.kt | 9 ++++ 10 files changed, 165 insertions(+) create mode 100644 README.md create mode 100644 trees/src/main/kotlin/bst/AVLTree.kt create mode 100644 trees/src/main/kotlin/bst/BinarySearchTree.kt create mode 100644 trees/src/main/kotlin/bst/Main.kt create mode 100644 trees/src/main/kotlin/bst/RedBlackTree.kt create mode 100644 trees/src/main/kotlin/bst/Tree.kt create mode 100644 trees/src/main/kotlin/bst/nodes/AVLNode.kt create mode 100644 trees/src/main/kotlin/bst/nodes/BSTNode.kt create mode 100644 trees/src/main/kotlin/bst/nodes/RBTNode.kt create mode 100644 trees/src/main/kotlin/bst/nodes/TreeNode.kt diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd585dc --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Binary Search Tree, Red-Black Tree, and AVLTree + +This project implements three different types of self-balancing binary trees in Kotlin: Binary Search Tree (BST), Red-Black Tree (RBT), and AVLTree. \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt new file mode 100644 index 0000000..57bc43a --- /dev/null +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -0,0 +1,4 @@ +package bst + +class AVLTree { +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt new file mode 100644 index 0000000..b7cb335 --- /dev/null +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -0,0 +1,42 @@ +package bst + +import bst.nodes.BSTNode + +class BinarySearchTree, V>: Tree { + var rootNode: BSTNode? = null + override fun insert(key: K, value: V) { + //add element + //make element as a root, if root is null + //if root is not null it adds element to left or right branch + //if left or right is not null - it checks it, and adds it if right or left is null + TODO("Not yet implemented") + } + + override fun remove(key: K, value: V) { + //remove node by key + TODO("Not yet implemented") + } + + override fun find(key: K): V { + //this method gives element by key + TODO("Not yet implemented") + } + + override fun clear() { + //clear tree + TODO("Not yet implemented") + } + + fun symmetricalTreeTraversal(){ + + } +} + + +fun main(){ + val test_tree = BinarySearchTree() + test_tree.rootNode = BSTNode(123, "erefe") + val test_1 = mutableListOf(12341, 324) + test_tree.rootNode!!.left = BSTNode(123, "world_0") + test_tree.rootNode!!.right = BSTNode(123, "world_1") +} diff --git a/trees/src/main/kotlin/bst/Main.kt b/trees/src/main/kotlin/bst/Main.kt new file mode 100644 index 0000000..a43768b --- /dev/null +++ b/trees/src/main/kotlin/bst/Main.kt @@ -0,0 +1,9 @@ +package bst + +fun main(args: Array) { + println("Hello World!") + + // 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 diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt new file mode 100644 index 0000000..23b6b04 --- /dev/null +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -0,0 +1,48 @@ +package bst + +import bst.nodes.RBTNode + +class RedBlackTree, V>: Tree { + + override fun find(key: K): V { + TODO("Not yet implemented") + } + + override fun clear() { + TODO("Not yet implemented") + } + override fun insert(key: K, value: V) { + TODO() + } + + private fun insertNode(node: RBTNode): RBTNode { + TODO() + } + + override fun remove(key: K, value: V) { + + } + private fun removeNode(node: RBTNode): RBTNode { + TODO() + } + + private fun moveRedLeft(node: RBTNode): RBTNode { + TODO() + } + + private fun moveRedRight(node: RBTNode): RBTNode { + TODO() + } + + private fun rotateRight(node: RBTNode) { + TODO() + } + + private fun rotateLeft(node: RBTNode) { + TODO() + } + + private fun balance(node: RBTNode) { + TODO() + } +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt new file mode 100644 index 0000000..15f8258 --- /dev/null +++ b/trees/src/main/kotlin/bst/Tree.kt @@ -0,0 +1,9 @@ +package bst + +interface Tree, V> { + fun insert(key: K, value: V) + fun remove(key: K, value: V) + fun find(key: K): V + fun clear() +} + diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt new file mode 100644 index 0000000..599775e --- /dev/null +++ b/trees/src/main/kotlin/bst/nodes/AVLNode.kt @@ -0,0 +1,4 @@ +package bst.nodes + +class AVLNode { +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt new file mode 100644 index 0000000..80b84f6 --- /dev/null +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -0,0 +1,9 @@ +package bst.nodes + +class BSTNode, V> ( + override val key: K, + override var value: V, +): TreeNode> { + override var left: BSTNode? = null + override var right: BSTNode? = null +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt new file mode 100644 index 0000000..2a939d6 --- /dev/null +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -0,0 +1,28 @@ +package bst.nodes + +enum class Color{ + BLACK, + RED +} + +// хз насчет data class, мб че-то заходим в самой ноде делать +class RBTNode, V> ( + override val key: K, + override var value: V, +): TreeNode> { + override var left: RBTNode? = null + override var right: RBTNode? = null + var color = Color.BLACK + + internal fun isBlack() { + TODO() + } + internal fun isRed() { + TODO() + } + internal fun flipColors() { + TODO() + } + + +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/TreeNode.kt b/trees/src/main/kotlin/bst/nodes/TreeNode.kt new file mode 100644 index 0000000..0b66bdb --- /dev/null +++ b/trees/src/main/kotlin/bst/nodes/TreeNode.kt @@ -0,0 +1,9 @@ +package bst.nodes + +// а надо ли нам тогда value? хер знает +interface TreeNode, V, SpecNode : TreeNode> { + val key: K + var value: V + var left: SpecNode? + var right: SpecNode? +} \ No newline at end of file From d64f79bbdee2a56a2f18448f3b41fc702b910236 Mon Sep 17 00:00:00 2001 From: Nikita Lukonenko <68463602+SurfaceYellowDuck@users.noreply.github.com> Date: Tue, 28 Mar 2023 09:37:44 +0300 Subject: [PATCH 006/118] Create linter action linter action have to run on every push and pull_request. --- .github/workflows/lint_code.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/lint_code.yml diff --git a/.github/workflows/lint_code.yml b/.github/workflows/lint_code.yml new file mode 100644 index 0000000..eb4a205 --- /dev/null +++ b/.github/workflows/lint_code.yml @@ -0,0 +1,22 @@ +name: Lint Codebase + +on: + push: + pull_request: + +jobs: + run-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Lint Code Base + uses: github/super-linter@v4 + env: + VALIDATE_ALL_CODEBASE: false + DEFAULT_BRANCH: "main" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + From f77fa40c220de992d0a7bb3b5e74c979a7f006fd Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 28 Mar 2023 13:50:27 +0300 Subject: [PATCH 007/118] feat: add BalancingTree class --- trees/src/main/kotlin/bst/BalancingTree.kt | 10 ++++++++++ trees/src/main/kotlin/bst/BinarySearchTree.kt | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 trees/src/main/kotlin/bst/BalancingTree.kt diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt new file mode 100644 index 0000000..8a12d8b --- /dev/null +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -0,0 +1,10 @@ +package bst + +abstract class BalancingTree, V>: BinarySearchTree() { + protected fun rotationRight() { + TODO() + } + protected fun rotationLeft() { + TODO() + } +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt index b7cb335..e4bbc47 100644 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -2,7 +2,7 @@ package bst import bst.nodes.BSTNode -class BinarySearchTree, V>: Tree { +open class BinarySearchTree, V>: Tree { var rootNode: BSTNode? = null override fun insert(key: K, value: V) { //add element From 04961ce926e8d622b50529d64c4d8125b6416a23 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 28 Mar 2023 21:04:58 +0300 Subject: [PATCH 008/118] add insert method and private recursiveInsert method to BinarySearchTree class --- .idea/.gitignore | 8 ++++ .idea/compiler.xml | 6 +++ .idea/jarRepositories.xml | 20 +++++++++ .idea/kotlinc.xml | 6 +++ .idea/misc.xml | 10 +++++ .idea/vcs.xml | 6 +++ trees/src/main/kotlin/bst/BinarySearchTree.kt | 41 ++++++++++++++++--- trees/src/main/kotlin/bst/nodes/TreeNode.kt | 2 +- 8 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml 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/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..e58d3e4 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..fdc392f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..0fc3113 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..12d1c78 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt index b7cb335..750d01d 100644 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -5,11 +5,38 @@ import bst.nodes.BSTNode class BinarySearchTree, V>: Tree { var rootNode: BSTNode? = null override fun insert(key: K, value: V) { + val newNode = BSTNode(key, value) + if (this.rootNode === null){ + this.rootNode = newNode + return + } + else{ + insertRecursive(this.rootNode!!, newNode) + } //add element //make element as a root, if root is null //if root is not null it adds element to left or right branch //if left or right is not null - it checks it, and adds it if right or left is null - TODO("Not yet implemented") +// TODO("Not yet implemented") + } + + private fun insertRecursive(currentNode: BSTNode, newNode: BSTNode){ + if(newNode.key < currentNode.key){ + if(currentNode.left?.key === null){ + currentNode.left = newNode + } + else{ + insertRecursive(currentNode.left!!, newNode) + } + } + if(newNode.key > currentNode.key){ + if(currentNode.right?.key === null){ + currentNode.right = newNode + } + else{ + insertRecursive(currentNode.right!!, newNode) + } + } } override fun remove(key: K, value: V) { @@ -35,8 +62,12 @@ class BinarySearchTree, V>: Tree { fun main(){ val test_tree = BinarySearchTree() - test_tree.rootNode = BSTNode(123, "erefe") - val test_1 = mutableListOf(12341, 324) - test_tree.rootNode!!.left = BSTNode(123, "world_0") - test_tree.rootNode!!.right = BSTNode(123, "world_1") + test_tree.insert(234, "afaraf") + test_tree.insert(235, "afaraf") + test_tree.insert(232, "afaraf") + println("afadf") +// test_tree.rootNode = BSTNode(123, "erefe") +// val test_1 = mutableListOf(12341, 324) +// test_tree.rootNode!!.left = BSTNode(123, "world_0") +// test_tree.rootNode!!.right = BSTNode(123, "world_1") } diff --git a/trees/src/main/kotlin/bst/nodes/TreeNode.kt b/trees/src/main/kotlin/bst/nodes/TreeNode.kt index 0b66bdb..ca24ac2 100644 --- a/trees/src/main/kotlin/bst/nodes/TreeNode.kt +++ b/trees/src/main/kotlin/bst/nodes/TreeNode.kt @@ -6,4 +6,4 @@ interface TreeNode, V, SpecNode : TreeNode> { var value: V var left: SpecNode? var right: SpecNode? -} \ No newline at end of file +} From 1c092716a8de3a7c9461a508a6c35d56e5ed096c Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 28 Mar 2023 22:13:26 +0300 Subject: [PATCH 009/118] add find method and private search method is BinarySearchTree class --- trees/src/main/kotlin/bst/BinarySearchTree.kt | 32 +++++++++++++++---- trees/src/main/kotlin/bst/RedBlackTree.kt | 2 +- trees/src/main/kotlin/bst/Tree.kt | 2 +- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt index 750d01d..784b600 100644 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -17,7 +17,6 @@ class BinarySearchTree, V>: Tree { //make element as a root, if root is null //if root is not null it adds element to left or right branch //if left or right is not null - it checks it, and adds it if right or left is null -// TODO("Not yet implemented") } private fun insertRecursive(currentNode: BSTNode, newNode: BSTNode){ @@ -44,19 +43,39 @@ class BinarySearchTree, V>: Tree { TODO("Not yet implemented") } - override fun find(key: K): V { + override fun find(key: K): Boolean { + return this.search(this.rootNode, key) != null +// return this.search(this.rootNode, key) //this method gives element by key - TODO("Not yet implemented") } + private fun search(currentNode: BSTNode?, key: K): BSTNode? { - override fun clear() { - //clear tree - TODO("Not yet implemented") + if (currentNode==null){ + return null + } + if (currentNode.key == key) { + return currentNode + } + + if (currentNode.key < key) { + return search(currentNode.right, key) + } + if (currentNode.key > key) { + return search(currentNode.left, key) + } + else{ + return null + } +// return currentNode } fun symmetricalTreeTraversal(){ } + + override fun clear() { + TODO("Not yet implemented") + } } @@ -65,6 +84,7 @@ fun main(){ test_tree.insert(234, "afaraf") test_tree.insert(235, "afaraf") test_tree.insert(232, "afaraf") + val res = test_tree.find(239) println("afadf") // test_tree.rootNode = BSTNode(123, "erefe") // val test_1 = mutableListOf(12341, 324) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 23b6b04..706d27d 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -4,7 +4,7 @@ import bst.nodes.RBTNode class RedBlackTree, V>: Tree { - override fun find(key: K): V { + override fun find(key: K): Boolean{ TODO("Not yet implemented") } diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt index 15f8258..0f86073 100644 --- a/trees/src/main/kotlin/bst/Tree.kt +++ b/trees/src/main/kotlin/bst/Tree.kt @@ -3,7 +3,7 @@ package bst interface Tree, V> { fun insert(key: K, value: V) fun remove(key: K, value: V) - fun find(key: K): V + fun find(key: K): Boolean fun clear() } From 1c58d50215ca905090bbfc7efd0544e01a0df96e Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 00:58:24 +0300 Subject: [PATCH 010/118] feat: implement rotations in BalancingTree --- trees/src/main/kotlin/bst/BalancingTree.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 8a12d8b..99ceedf 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -1,10 +1,18 @@ package bst +import bst.nodes.TreeNode + abstract class BalancingTree, V>: BinarySearchTree() { - protected fun rotationRight() { - TODO() + protected fun > rotateLeft(node: SpecNode): SpecNode { + val right = node.right + node.right = right?.left + right?.left = node + return right!! } - protected fun rotationLeft() { - TODO() + protected fun > rotateRight(node: SpecNode): SpecNode { + val left = node.left + node.left = left?.right + left?.right = node + return left!! } } \ No newline at end of file From 095a1f3b9528d82b56af5d6b79d146a4d5652406 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 01:19:13 +0300 Subject: [PATCH 011/118] feat: implement insert method --- trees/src/main/kotlin/bst/RedBlackTree.kt | 98 +++++++++++++++++----- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 17 +--- 2 files changed, 77 insertions(+), 38 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 23b6b04..31b21a9 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -2,7 +2,12 @@ package bst import bst.nodes.RBTNode -class RedBlackTree, V>: Tree { +class RedBlackTree, V>: BalancingTree() { + private var root: RBTNode? = null + + private fun isRed(node: RBTNode?): Boolean { + return node?.red == true + } override fun find(key: K): V { TODO("Not yet implemented") @@ -12,37 +17,86 @@ class RedBlackTree, V>: Tree { TODO("Not yet implemented") } override fun insert(key: K, value: V) { - TODO() + insertNode(key, value) } - private fun insertNode(node: RBTNode): RBTNode { - TODO() - } + private fun insertNode(key: K, value: V): RBTNode { + if (root == null) { + root = RBTNode(key, value, true) + return root!! + } - override fun remove(key: K, value: V) { + var node = root + var parent: RBTNode? = null - } - private fun removeNode(node: RBTNode): RBTNode { - TODO() - } + while (node != null) { + parent = node - private fun moveRedLeft(node: RBTNode): RBTNode { - TODO() - } + when { + key < node.key -> node = node.left + key > node.key -> node = node.right + else -> { + node.value = value + return node + } + } + } - private fun moveRedRight(node: RBTNode): RBTNode { - TODO() - } + val newNode = RBTNode(key, value, false) + if (key < parent!!.key) { + parent.left = newNode + } else { + parent.right = newNode + } - private fun rotateRight(node: RBTNode) { - TODO() - } + var current = newNode + while (isRed(current) && isRed(current!!.left)) { + var sibling = current.right + if (isRed(sibling)) { + current.red = true + sibling!!.red = false + current = rotateLeft(current) - private fun rotateLeft(node: RBTNode) { - TODO() + current.red = current.left!!.red + current.left!!.red = true + + sibling = current.right + } + if (!isRed(sibling?.left) && !isRed(sibling?.right)) { + sibling!!.red = true + current = parent!! + } else { + if (!isRed(sibling?.right)) { + sibling!!.left?.red = false + sibling.red = true + current = rotateRight(sibling) + + current.red = current.right!!.red + current.right!!.red = true + + + sibling = current.right + } + sibling!!.red = current.red + current.red = true + sibling.right?.red = true + current = rotateLeft(current) + + current.red = current.left!!.red + current.left!!.red = true + + break + } + } + + root!!.red = true + return current } - private fun balance(node: RBTNode) { + override fun remove(key: K, value: V) { + + } + private fun removeNode(node: RBTNode): RBTNode { TODO() } } \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 2a939d6..ae56b2d 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -1,28 +1,13 @@ package bst.nodes -enum class Color{ - BLACK, - RED -} // хз насчет data class, мб че-то заходим в самой ноде делать class RBTNode, V> ( override val key: K, override var value: V, + var red: Boolean = true, ): TreeNode> { override var left: RBTNode? = null override var right: RBTNode? = null - var color = Color.BLACK - - internal fun isBlack() { - TODO() - } - internal fun isRed() { - TODO() - } - internal fun flipColors() { - TODO() - } - } \ No newline at end of file From 4ce4b0b90933ef43edefbca513a61f1fe686bd53 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 17:46:15 +0300 Subject: [PATCH 012/118] refactor: remove overriding find and clear method as we are inheriting them --- trees/src/main/kotlin/bst/RedBlackTree.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 31b21a9..9af8106 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -9,13 +9,6 @@ class RedBlackTree, V>: BalancingTree() { return node?.red == true } - override fun find(key: K): V { - TODO("Not yet implemented") - } - - override fun clear() { - TODO("Not yet implemented") - } override fun insert(key: K, value: V) { insertNode(key, value) } From 81095ba789177d7436abdfb0b7a196f456914b97 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 19:41:06 +0300 Subject: [PATCH 013/118] fix: remove redundant value parameter from remove method --- trees/src/main/kotlin/bst/BinarySearchTree.kt | 2 +- trees/src/main/kotlin/bst/RedBlackTree.kt | 2 +- trees/src/main/kotlin/bst/Tree.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt index e4bbc47..a9667a1 100644 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -12,7 +12,7 @@ open class BinarySearchTree, V>: Tree { TODO("Not yet implemented") } - override fun remove(key: K, value: V) { + override fun remove(key: K) { //remove node by key TODO("Not yet implemented") } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 9af8106..dda38d0 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -86,7 +86,7 @@ class RedBlackTree, V>: BalancingTree() { return current } - override fun remove(key: K, value: V) { + override fun remove(key: K) { } private fun removeNode(node: RBTNode): RBTNode { diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt index 15f8258..6a254ab 100644 --- a/trees/src/main/kotlin/bst/Tree.kt +++ b/trees/src/main/kotlin/bst/Tree.kt @@ -2,7 +2,7 @@ package bst interface Tree, V> { fun insert(key: K, value: V) - fun remove(key: K, value: V) + fun remove(key: K) fun find(key: K): V fun clear() } From 5779c31cf7fb897d37cc6a30079dbf3f3f00b102 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 19:43:37 +0300 Subject: [PATCH 014/118] feat: test template for RBT --- .gitignore | 2 +- trees/src/test/kotlin/bst/ValidRBTTest.kt | 30 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 trees/src/test/kotlin/bst/ValidRBTTest.kt diff --git a/.gitignore b/.gitignore index aa756d4..e877c32 100644 --- a/.gitignore +++ b/.gitignore @@ -180,4 +180,4 @@ gradle-app.setting # Java heap dump *.hprof -# End of https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij +# End of https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij \ No newline at end of file diff --git a/trees/src/test/kotlin/bst/ValidRBTTest.kt b/trees/src/test/kotlin/bst/ValidRBTTest.kt new file mode 100644 index 0000000..753e394 --- /dev/null +++ b/trees/src/test/kotlin/bst/ValidRBTTest.kt @@ -0,0 +1,30 @@ +package bst + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class ValidRBTTest { + + @Test + fun testInsert() { + val tree = RedBlackTree() + tree.insert(1, "One") + tree.insert(2, "Two") + tree.insert(3, "Three") + assertEquals("One", tree.find(1)) + assertEquals("Two", tree.find(2)) + assertEquals("Three", tree.find(3)) + } + + @Test + fun testDelete() { + val tree = RedBlackTree() + tree.insert(1, "One") + tree.insert(2, "Two") + tree.insert(3, "Three") + tree.remove(2) + assertEquals(null, tree.find(2)) + assertEquals("One", tree.find(1)) + assertEquals("Three", tree.find(3)) + } +} From 18d2f20c7b991234c81a02555e00e9d052db2a6d Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 20:27:32 +0300 Subject: [PATCH 015/118] refactor: major refactoring according to linter --- settings.gradle.kts | 2 +- trees/build.gradle.kts | 3 +-- trees/src/main/kotlin/bst/AVLTree.kt | 3 +-- trees/src/main/kotlin/bst/BalancingTree.kt | 4 ++-- trees/src/main/kotlin/bst/BinarySearchTree.kt | 23 +++++++++---------- trees/src/main/kotlin/bst/Main.kt | 2 +- trees/src/main/kotlin/bst/RedBlackTree.kt | 12 +++++----- trees/src/main/kotlin/bst/Tree.kt | 3 +-- trees/src/main/kotlin/bst/nodes/AVLNode.kt | 3 +-- trees/src/main/kotlin/bst/nodes/BSTNode.kt | 8 +++---- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 12 ++++------ trees/src/main/kotlin/bst/nodes/TreeNode.kt | 4 ++-- 12 files changed, 36 insertions(+), 43 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index e36016e..fbf2455 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,5 +8,5 @@ */ rootProject.name = "trees-3" -include("app",) +include("app") include("trees") diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 0a4ec88..2d18279 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -16,8 +16,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.1") } - tasks.named("test") { // Use JUnit Platform for unit tests. useJUnitPlatform() -} \ No newline at end of file +} diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index 57bc43a..bf777b6 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -1,4 +1,3 @@ package bst -class AVLTree { -} \ No newline at end of file +class AVLTree diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 99ceedf..3f5c453 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -2,7 +2,7 @@ package bst import bst.nodes.TreeNode -abstract class BalancingTree, V>: BinarySearchTree() { +abstract class BalancingTree, V> : BinarySearchTree() { protected fun > rotateLeft(node: SpecNode): SpecNode { val right = node.right node.right = right?.left @@ -15,4 +15,4 @@ abstract class BalancingTree, V>: BinarySearchTree() { left?.right = node return left!! } -} \ No newline at end of file +} diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt index a9667a1..51549e8 100644 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -2,38 +2,37 @@ package bst import bst.nodes.BSTNode -open class BinarySearchTree, V>: Tree { +open class BinarySearchTree, V> : Tree { var rootNode: BSTNode? = null override fun insert(key: K, value: V) { - //add element - //make element as a root, if root is null - //if root is not null it adds element to left or right branch - //if left or right is not null - it checks it, and adds it if right or left is null + // add element + // make element as a root, if root is null + // if root is not null it adds element to left or right branch + // if left or right is not null - it checks it, and adds it if right or left is null TODO("Not yet implemented") } override fun remove(key: K) { - //remove node by key + // remove node by key TODO("Not yet implemented") } override fun find(key: K): V { - //this method gives element by key + // this method gives element by key TODO("Not yet implemented") } override fun clear() { - //clear tree + // clear tree TODO("Not yet implemented") } - fun symmetricalTreeTraversal(){ - + fun symmetricalTreeTraversal() { + TODO() } } - -fun main(){ +fun main() { val test_tree = BinarySearchTree() test_tree.rootNode = BSTNode(123, "erefe") val test_1 = mutableListOf(12341, 324) diff --git a/trees/src/main/kotlin/bst/Main.kt b/trees/src/main/kotlin/bst/Main.kt index a43768b..52f8104 100644 --- a/trees/src/main/kotlin/bst/Main.kt +++ b/trees/src/main/kotlin/bst/Main.kt @@ -6,4 +6,4 @@ fun main(args: Array) { // 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 +} diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index dda38d0..7221a06 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -2,7 +2,7 @@ package bst import bst.nodes.RBTNode -class RedBlackTree, V>: BalancingTree() { +class RedBlackTree, V> : BalancingTree() { private var root: RBTNode? = null private fun isRed(node: RBTNode?): Boolean { @@ -43,7 +43,7 @@ class RedBlackTree, V>: BalancingTree() { } var current = newNode - while (isRed(current) && isRed(current!!.left)) { + while (isRed(current) && isRed(current.left)) { var sibling = current.right if (isRed(sibling)) { current.red = true @@ -57,7 +57,7 @@ class RedBlackTree, V>: BalancingTree() { } if (!isRed(sibling?.left) && !isRed(sibling?.right)) { sibling!!.red = true - current = parent!! + current = parent } else { if (!isRed(sibling?.right)) { sibling!!.left?.red = false @@ -67,7 +67,6 @@ class RedBlackTree, V>: BalancingTree() { current.red = current.right!!.red current.right!!.red = true - sibling = current.right } sibling!!.red = current.red @@ -87,9 +86,10 @@ class RedBlackTree, V>: BalancingTree() { } override fun remove(key: K) { - + TODO() } + private fun removeNode(node: RBTNode): RBTNode { TODO() } -} \ No newline at end of file +} diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt index 6a254ab..ddaa1fc 100644 --- a/trees/src/main/kotlin/bst/Tree.kt +++ b/trees/src/main/kotlin/bst/Tree.kt @@ -1,9 +1,8 @@ package bst -interface Tree, V> { +interface Tree, V> { fun insert(key: K, value: V) fun remove(key: K) fun find(key: K): V fun clear() } - diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt index 599775e..5c0cf98 100644 --- a/trees/src/main/kotlin/bst/nodes/AVLNode.kt +++ b/trees/src/main/kotlin/bst/nodes/AVLNode.kt @@ -1,4 +1,3 @@ package bst.nodes -class AVLNode { -} \ No newline at end of file +class AVLNode \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt index 80b84f6..f949369 100644 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -1,9 +1,9 @@ package bst.nodes -class BSTNode, V> ( +class BSTNode, V> ( override val key: K, - override var value: V, -): TreeNode> { + override var value: V +) : TreeNode> { override var left: BSTNode? = null override var right: BSTNode? = null -} \ No newline at end of file +} diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index ae56b2d..9ba24a4 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -1,13 +1,11 @@ package bst.nodes - // хз насчет data class, мб че-то заходим в самой ноде делать -class RBTNode, V> ( +class RBTNode, V> ( override val key: K, override var value: V, - var red: Boolean = true, -): TreeNode> { - override var left: RBTNode? = null + var red: Boolean = true +) : TreeNode> { + override var left : RBTNode? = null override var right: RBTNode? = null - -} \ No newline at end of file +} diff --git a/trees/src/main/kotlin/bst/nodes/TreeNode.kt b/trees/src/main/kotlin/bst/nodes/TreeNode.kt index 0b66bdb..3454cc6 100644 --- a/trees/src/main/kotlin/bst/nodes/TreeNode.kt +++ b/trees/src/main/kotlin/bst/nodes/TreeNode.kt @@ -1,9 +1,9 @@ package bst.nodes // а надо ли нам тогда value? хер знает -interface TreeNode, V, SpecNode : TreeNode> { +interface TreeNode, V, SpecNode : TreeNode> { val key: K var value: V var left: SpecNode? var right: SpecNode? -} \ No newline at end of file +} From bec99006eed479a07b5cacfb8a1e73008f38234e Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 20:32:45 +0300 Subject: [PATCH 016/118] refactor: minor refactoring --- trees/src/main/kotlin/bst/nodes/AVLNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt index 5c0cf98..993d2cf 100644 --- a/trees/src/main/kotlin/bst/nodes/AVLNode.kt +++ b/trees/src/main/kotlin/bst/nodes/AVLNode.kt @@ -1,3 +1,3 @@ package bst.nodes -class AVLNode \ No newline at end of file +class AVLNode diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 9ba24a4..f5bb485 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -6,6 +6,6 @@ class RBTNode, V> ( override var value: V, var red: Boolean = true ) : TreeNode> { - override var left : RBTNode? = null + override var left: RBTNode? = null override var right: RBTNode? = null } From d70caabd30e57cea759295f35017a576c4bd3cb5 Mon Sep 17 00:00:00 2001 From: Danil P <58734659+ancavar@users.noreply.github.com> Date: Wed, 29 Mar 2023 20:43:51 +0300 Subject: [PATCH 017/118] Revert "Red black" --- .gitattributes | 9 - .gitignore | 183 ------------- README.md | 3 - app/build.gradle.kts | 32 --- app/src/main/kotlin/app/App.kt | 5 - gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 244 ------------------ gradlew.bat | 92 ------- settings.gradle.kts | 12 - trees/build.gradle.kts | 22 -- trees/src/main/kotlin/bst/AVLTree.kt | 3 - trees/src/main/kotlin/bst/BalancingTree.kt | 18 -- trees/src/main/kotlin/bst/BinarySearchTree.kt | 41 --- trees/src/main/kotlin/bst/Main.kt | 9 - trees/src/main/kotlin/bst/RedBlackTree.kt | 95 ------- trees/src/main/kotlin/bst/Tree.kt | 8 - trees/src/main/kotlin/bst/nodes/AVLNode.kt | 3 - trees/src/main/kotlin/bst/nodes/BSTNode.kt | 9 - trees/src/main/kotlin/bst/nodes/RBTNode.kt | 11 - trees/src/main/kotlin/bst/nodes/TreeNode.kt | 9 - trees/src/test/kotlin/bst/ValidRBTTest.kt | 30 --- 22 files changed, 844 deletions(-) delete mode 100644 .gitattributes delete mode 100644 .gitignore delete mode 100644 README.md delete mode 100644 app/build.gradle.kts delete mode 100644 app/src/main/kotlin/app/App.kt delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 settings.gradle.kts delete mode 100644 trees/build.gradle.kts delete mode 100644 trees/src/main/kotlin/bst/AVLTree.kt delete mode 100644 trees/src/main/kotlin/bst/BalancingTree.kt delete mode 100644 trees/src/main/kotlin/bst/BinarySearchTree.kt delete mode 100644 trees/src/main/kotlin/bst/Main.kt delete mode 100644 trees/src/main/kotlin/bst/RedBlackTree.kt delete mode 100644 trees/src/main/kotlin/bst/Tree.kt delete mode 100644 trees/src/main/kotlin/bst/nodes/AVLNode.kt delete mode 100644 trees/src/main/kotlin/bst/nodes/BSTNode.kt delete mode 100644 trees/src/main/kotlin/bst/nodes/RBTNode.kt delete mode 100644 trees/src/main/kotlin/bst/nodes/TreeNode.kt delete mode 100644 trees/src/test/kotlin/bst/ValidRBTTest.kt diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 097f9f9..0000000 --- a/.gitattributes +++ /dev/null @@ -1,9 +0,0 @@ -# -# 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/.gitignore b/.gitignore deleted file mode 100644 index e877c32..0000000 --- a/.gitignore +++ /dev/null @@ -1,183 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij -# Edit at https://www.toptal.com/developers/gitignore?templates=kotlin,java,gradle,intellij - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint -.idea/**/sonarlint/ - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator-enh.xml -.idea/**/markdown-navigator/ - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 -.idea/$CACHE_FILE$ - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream -.idea/codestream.xml - -# Azure Toolkit for IntelliJ plugin -# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij -.idea/**/azureSettings.xml - -### Java ### -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - -### Kotlin ### -# Compiled class file - -# Log file - -# BlueJ files - -# Mobile Tools for Java (J2ME) - -# Package Files # - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml - -### Gradle ### -.gradle -**/build/ -!src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Avoid ignore Gradle wrappper properties -!gradle-wrapper.properties - -# Cache of project -.gradletasknamecache - -# Eclipse Gradle plugin generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) -.classpath - -### Gradle Patch ### -# Java heap dump -*.hprof - -# End of https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index fd585dc..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Binary Search Tree, Red-Black Tree, and AVLTree - -This project implements three different types of self-balancing binary trees in Kotlin: Binary Search Tree (BST), Red-Black Tree (RBT), and AVLTree. \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts deleted file mode 100644 index 4198206..0000000 --- a/app/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -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.2") - - implementation(project(":trees")) -} - -application { - // Define the main class for the application. - mainClass.set("app.AppKt") -} - -tasks.named("test") { - // Use JUnit Platform for unit tests. - useJUnitPlatform() -} diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt deleted file mode 100644 index 912d243..0000000 --- a/app/src/main/kotlin/app/App.kt +++ /dev/null @@ -1,5 +0,0 @@ -package app - -fun main() { - print("Hello World!") -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index ccebba7710deaf9f98673a68957ea02138b60d0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61608 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index bdc9a83..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100755 index 79a61d4..0000000 --- a/gradlew +++ /dev/null @@ -1,244 +0,0 @@ -#!/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 deleted file mode 100644 index 93e3f59..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,92 +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=. -@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 deleted file mode 100644 index fbf2455..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,12 +0,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-3" -include("app") -include("trees") diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts deleted file mode 100644 index 2d18279..0000000 --- a/trees/build.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. - id("org.jetbrains.kotlin.jvm") version "1.8.10" -} - -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") -} - -tasks.named("test") { - // Use JUnit Platform for unit tests. - useJUnitPlatform() -} diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt deleted file mode 100644 index bf777b6..0000000 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ /dev/null @@ -1,3 +0,0 @@ -package bst - -class AVLTree diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt deleted file mode 100644 index 3f5c453..0000000 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ /dev/null @@ -1,18 +0,0 @@ -package bst - -import bst.nodes.TreeNode - -abstract class BalancingTree, V> : BinarySearchTree() { - protected fun > rotateLeft(node: SpecNode): SpecNode { - val right = node.right - node.right = right?.left - right?.left = node - return right!! - } - protected fun > rotateRight(node: SpecNode): SpecNode { - val left = node.left - node.left = left?.right - left?.right = node - return left!! - } -} diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt deleted file mode 100644 index 51549e8..0000000 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ /dev/null @@ -1,41 +0,0 @@ -package bst - -import bst.nodes.BSTNode - -open class BinarySearchTree, V> : Tree { - var rootNode: BSTNode? = null - override fun insert(key: K, value: V) { - // add element - // make element as a root, if root is null - // if root is not null it adds element to left or right branch - // if left or right is not null - it checks it, and adds it if right or left is null - TODO("Not yet implemented") - } - - override fun remove(key: K) { - // remove node by key - TODO("Not yet implemented") - } - - override fun find(key: K): V { - // this method gives element by key - TODO("Not yet implemented") - } - - override fun clear() { - // clear tree - TODO("Not yet implemented") - } - - fun symmetricalTreeTraversal() { - TODO() - } -} - -fun main() { - val test_tree = BinarySearchTree() - test_tree.rootNode = BSTNode(123, "erefe") - val test_1 = mutableListOf(12341, 324) - test_tree.rootNode!!.left = BSTNode(123, "world_0") - test_tree.rootNode!!.right = BSTNode(123, "world_1") -} diff --git a/trees/src/main/kotlin/bst/Main.kt b/trees/src/main/kotlin/bst/Main.kt deleted file mode 100644 index 52f8104..0000000 --- a/trees/src/main/kotlin/bst/Main.kt +++ /dev/null @@ -1,9 +0,0 @@ -package bst - -fun main(args: Array) { - println("Hello World!") - - // 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()}") -} diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt deleted file mode 100644 index 7221a06..0000000 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ /dev/null @@ -1,95 +0,0 @@ -package bst - -import bst.nodes.RBTNode - -class RedBlackTree, V> : BalancingTree() { - private var root: RBTNode? = null - - private fun isRed(node: RBTNode?): Boolean { - return node?.red == true - } - - override fun insert(key: K, value: V) { - insertNode(key, value) - } - - private fun insertNode(key: K, value: V): RBTNode { - if (root == null) { - root = RBTNode(key, value, true) - return root!! - } - - var node = root - var parent: RBTNode? = null - - while (node != null) { - parent = node - - when { - key < node.key -> node = node.left - key > node.key -> node = node.right - else -> { - node.value = value - return node - } - } - } - - val newNode = RBTNode(key, value, false) - if (key < parent!!.key) { - parent.left = newNode - } else { - parent.right = newNode - } - - var current = newNode - while (isRed(current) && isRed(current.left)) { - var sibling = current.right - if (isRed(sibling)) { - current.red = true - sibling!!.red = false - current = rotateLeft(current) - - current.red = current.left!!.red - current.left!!.red = true - - sibling = current.right - } - if (!isRed(sibling?.left) && !isRed(sibling?.right)) { - sibling!!.red = true - current = parent - } else { - if (!isRed(sibling?.right)) { - sibling!!.left?.red = false - sibling.red = true - current = rotateRight(sibling) - - current.red = current.right!!.red - current.right!!.red = true - - sibling = current.right - } - sibling!!.red = current.red - current.red = true - sibling.right?.red = true - current = rotateLeft(current) - - current.red = current.left!!.red - current.left!!.red = true - - break - } - } - - root!!.red = true - return current - } - - override fun remove(key: K) { - TODO() - } - - private fun removeNode(node: RBTNode): RBTNode { - TODO() - } -} diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt deleted file mode 100644 index ddaa1fc..0000000 --- a/trees/src/main/kotlin/bst/Tree.kt +++ /dev/null @@ -1,8 +0,0 @@ -package bst - -interface Tree, V> { - fun insert(key: K, value: V) - fun remove(key: K) - fun find(key: K): V - fun clear() -} diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt deleted file mode 100644 index 993d2cf..0000000 --- a/trees/src/main/kotlin/bst/nodes/AVLNode.kt +++ /dev/null @@ -1,3 +0,0 @@ -package bst.nodes - -class AVLNode diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt deleted file mode 100644 index f949369..0000000 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ /dev/null @@ -1,9 +0,0 @@ -package bst.nodes - -class BSTNode, V> ( - override val key: K, - override var value: V -) : TreeNode> { - override var left: BSTNode? = null - override var right: BSTNode? = null -} diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt deleted file mode 100644 index f5bb485..0000000 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ /dev/null @@ -1,11 +0,0 @@ -package bst.nodes - -// хз насчет data class, мб че-то заходим в самой ноде делать -class RBTNode, V> ( - override val key: K, - override var value: V, - var red: Boolean = true -) : TreeNode> { - override var left: RBTNode? = null - override var right: RBTNode? = null -} diff --git a/trees/src/main/kotlin/bst/nodes/TreeNode.kt b/trees/src/main/kotlin/bst/nodes/TreeNode.kt deleted file mode 100644 index 3454cc6..0000000 --- a/trees/src/main/kotlin/bst/nodes/TreeNode.kt +++ /dev/null @@ -1,9 +0,0 @@ -package bst.nodes - -// а надо ли нам тогда value? хер знает -interface TreeNode, V, SpecNode : TreeNode> { - val key: K - var value: V - var left: SpecNode? - var right: SpecNode? -} diff --git a/trees/src/test/kotlin/bst/ValidRBTTest.kt b/trees/src/test/kotlin/bst/ValidRBTTest.kt deleted file mode 100644 index 753e394..0000000 --- a/trees/src/test/kotlin/bst/ValidRBTTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package bst - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class ValidRBTTest { - - @Test - fun testInsert() { - val tree = RedBlackTree() - tree.insert(1, "One") - tree.insert(2, "Two") - tree.insert(3, "Three") - assertEquals("One", tree.find(1)) - assertEquals("Two", tree.find(2)) - assertEquals("Three", tree.find(3)) - } - - @Test - fun testDelete() { - val tree = RedBlackTree() - tree.insert(1, "One") - tree.insert(2, "Two") - tree.insert(3, "Three") - tree.remove(2) - assertEquals(null, tree.find(2)) - assertEquals("One", tree.find(1)) - assertEquals("Three", tree.find(3)) - } -} From 5314ceb665e772df1a0181b34f7d2741c023a75a Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 21:48:52 +0300 Subject: [PATCH 018/118] fix: remove value parameter from Tree interface --- trees/src/main/kotlin/bst/Tree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt index 64f4acf..61b69c4 100644 --- a/trees/src/main/kotlin/bst/Tree.kt +++ b/trees/src/main/kotlin/bst/Tree.kt @@ -2,7 +2,7 @@ package bst interface Tree, V> { fun insert(key: K, value: V) - fun remove(key: K, value: V) + fun remove(key: K) fun find(key: K): Boolean fun clear() } From 2006ec6efcab1d16df55d6bb6c45524cc1b1e11a Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 29 Mar 2023 22:44:39 +0300 Subject: [PATCH 019/118] fix: change search function to protected and so that it can be used from any derived tree --- trees/src/main/kotlin/bst/BinarySearchTree.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt index 10a2ec2..81f1b6a 100644 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ b/trees/src/main/kotlin/bst/BinarySearchTree.kt @@ -1,9 +1,10 @@ package bst import bst.nodes.BSTNode +import bst.nodes.TreeNode open class BinarySearchTree, V> : Tree { - var rootNode: BSTNode? = null + private var rootNode: BSTNode? = null override fun insert(key: K, value: V) { val newNode = BSTNode(key, value) if (this.rootNode === null){ @@ -43,13 +44,13 @@ open class BinarySearchTree, V> : Tree { TODO("Not yet implemented") } override fun find(key: K): Boolean { - return this.search(this.rootNode, key) != null + return search(rootNode, key) != null // return this.search(this.rootNode, key) // this method gives element by key } - private fun search(currentNode: BSTNode?, key: K): BSTNode? { - if (currentNode==null){ + protected fun , K : Comparable> search (currentNode: SpecNode?, key: K): SpecNode? { + if (currentNode == null){ return null } if (currentNode.key == key) { @@ -82,7 +83,7 @@ fun main() { test_tree.insert(234, "afaraf") test_tree.insert(235, "afaraf") test_tree.insert(232, "afaraf") - val res = test_tree.find(239) + val res = test_tree.find(235) println("afadf") // test_tree.rootNode = BSTNode(123, "erefe") // val test_1 = mutableListOf(12341, 324) From 391e531b059a3b9ea935c0358848f439bf6c2070 Mon Sep 17 00:00:00 2001 From: Nikita Lukonenko <68463602+SurfaceYellowDuck@users.noreply.github.com> Date: Fri, 31 Mar 2023 22:51:46 +0300 Subject: [PATCH 020/118] Create README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a69989c --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Trees library. +> An open source library library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. +## 🖍 Used technology +![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) +![Junit](https://img.shields.io/badge/-Junit-525A162?&style=for-the-badge) +## :package: How to use +Firstly you need clone repository: + +```sh +https://github.com/spbu-coding-2022/trees-3.git +``` + From cf8e5450264d4878807aaaa25abbc23ef21866e6 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin <88817918+nemakin@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:40:12 +0300 Subject: [PATCH 021/118] Implement AVL tree (#6) * feat: Add AVL tree with own BST implementation --- .idea/misc.xml | 4 +- trees/build.gradle.kts | 2 +- trees/src/main/kotlin/bst/AVLTree.kt | 96 ++++++++++++++++++- trees/src/main/kotlin/bst/AbstractBST.kt | 84 ++++++++++++++++ trees/src/main/kotlin/bst/BSTree.kt | 7 ++ trees/src/main/kotlin/bst/BalancingTree.kt | 8 +- trees/src/main/kotlin/bst/BinarySearchTree.kt | 92 ------------------ trees/src/main/kotlin/bst/Main.kt | 9 -- trees/src/main/kotlin/bst/RedBlackTree.kt | 18 ++-- trees/src/main/kotlin/bst/Tree.kt | 3 +- trees/src/main/kotlin/bst/nodes/AVLNode.kt | 7 +- trees/src/main/kotlin/bst/nodes/BSTNode.kt | 11 +-- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 9 ++ trees/src/main/kotlin/bst/nodes/RBTNode.kt | 12 +-- trees/src/main/kotlin/bst/nodes/TreeNode.kt | 9 -- 15 files changed, 226 insertions(+), 145 deletions(-) create mode 100644 trees/src/main/kotlin/bst/AbstractBST.kt create mode 100644 trees/src/main/kotlin/bst/BSTree.kt delete mode 100644 trees/src/main/kotlin/bst/BinarySearchTree.kt delete mode 100644 trees/src/main/kotlin/bst/Main.kt create mode 100644 trees/src/main/kotlin/bst/nodes/BinaryNode.kt delete mode 100644 trees/src/main/kotlin/bst/nodes/TreeNode.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 12d1c78..0f221d8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,5 @@ - - - + \ No newline at end of file diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 2d18279..854781c 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -13,7 +13,7 @@ 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") } tasks.named("test") { diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index bf777b6..2907c2f 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -1,3 +1,97 @@ +//GPL-3.0-or-later +// +//This file is part of Trees-3. +// +//Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. +// +//Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. +// +//You should have obtained a copy of the GNU General Public License with this program. If it is not, see . +// Copyright (C) <2023> + package bst -class AVLTree +import bst.nodes.AVLNode +import kotlin.math.max + +class AVLTree, V> : BalancingTree>() { + override fun initNode(key: K, value: V): AVLNode = AVLNode(key, value) + + override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { + if (node == null) return initNode(key, value) + if (key < node.key) { + node.left = insertNode(node.left, key, value) + } else if (key > node.key) { + node.right = insertNode(node.right, key, value) + } else { + node.value = value + } + updateHeight(node) + return balance(node) + } + + private fun getHeight(node: AVLNode?): Int { + return node?.height ?: -1 + } + private fun updateHeight(node: AVLNode) { + node.height = max(getHeight(node.left), getHeight(node.right)) + 1 + } + private fun getBalanceFactor(node: AVLNode?): Int = when (node) { + null -> 0 + else -> getHeight(node.right) - getHeight(node.left) + } + + private fun balance(node: AVLNode): AVLNode { + return when (getBalanceFactor(node)) { + -2 -> { + if (getBalanceFactor(node.left) == 1) { + node.left = rotateLeft(node.left!!) + } + return rotateRight(node) + } + 2 -> { + if (getBalanceFactor(node.right) == -1) { + node.right = rotateRight(node.right!!) + } + return rotateLeft(node) + } + else -> node + } + } + + override fun removeNode(node: AVLNode?, key: K): AVLNode? { + if (node == null) return null + if (key < node.key) { + node.left = removeNode(node.left, key) + } else if (key > node.key) { + node.right = removeNode(node.right, key) + } else { + if (node.left == null) { + return node.right + } else if (node.right == null) { + return node.left + } else { + val tmp: AVLNode = findMax(node.left)!! + node.key = tmp.key + node.value = tmp.value + node.left = removeNode(node.left, tmp.key) + } + } + + updateHeight(node) + return balance(node) + } + + override fun rotateRight(node: AVLNode): AVLNode { + val tmp = super.rotateRight(node) + updateHeight(node) + updateHeight(tmp) + return tmp + } + override fun rotateLeft(node: AVLNode): AVLNode { + val tmp = super.rotateLeft(node) + updateHeight(node) + updateHeight(tmp) + return tmp + } +} diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt new file mode 100644 index 0000000..5a6a961 --- /dev/null +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -0,0 +1,84 @@ +package bst + +import bst.nodes.BinaryNode + +abstract class AbstractBST, V, Self: BinaryNode> : Tree { + internal var rootNode: Self? = null + // factory method + protected abstract fun initNode(key: K, value: V): Self + + override fun insert(key: K, value: V) { + rootNode = insertNode(rootNode, key, value) + } + protected open fun insertNode(node: Self?, key: K, value: V): Self { + if (node == null) return initNode(key, value) + if (key < node.key) { + node.left = insertNode(node.left, key, value) + } else if (key > node.key) { + node.right = insertNode(node.right, key, value) + } else { + node.value = value + } + return node + } + + override fun remove(key: K) { + rootNode = removeNode(rootNode, key) + } + protected open fun removeNode(node: Self?, key: K): Self? { + if (node == null) return null + if (key < node.key) { + node.left = removeNode(node.left, key) + } else if (key > node.key) { + node.right = removeNode(node.right, key) + } else { + if (node.left == null) { + return node.right + } else if (node.right == null) { + return node.left + } else { + val tmp: Self = findMax(node.left)!! + node.key = tmp.key + node.value = tmp.value + node.left = removeNode(node.left, tmp.key) + } + } + return node + } + + override fun find(key: K): V? = findNode(rootNode, key) + private fun findNode(node: Self?, key: K): V? { + return if (node == null) { + null + } else if (key == node.key) { + node.value + } else { + if (key < node.key) + findNode(node.left, key) + else + findNode(node.right, key) + } + } + + protected fun findMax(node: Self?): Self? = when { + node == null -> null + node.right == null -> node + else -> findMax(node.right) + } + + // for debug purposes only + fun printTree() = println(diagram(rootNode)) + private fun diagram(node: Self?, + top: String = "", + root: String = "", + bottom: String = ""): String { + return node?.let { + if (node.left == null && node.right == null) { + "$root${node.value}\n" + } else { + diagram(node.right, "$top ", "$top┌──", "$top│ ") + + root + "${node.value}\n" + diagram(node.left, "$bottom│ ", "$bottom└──", "$bottom ") + } + } ?: "${root}null\n" + } +} diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt new file mode 100644 index 0000000..e4b87ee --- /dev/null +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -0,0 +1,7 @@ +package bst + +import bst.nodes.BSTNode + +class BSTree, V> : AbstractBST>() { + override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) +} diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 3f5c453..4bf1b49 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -1,15 +1,15 @@ package bst -import bst.nodes.TreeNode +import bst.nodes.BinaryNode -abstract class BalancingTree, V> : BinarySearchTree() { - protected fun > rotateLeft(node: SpecNode): SpecNode { +abstract class BalancingTree, V, Self: BinaryNode>: AbstractBST() { + protected open fun rotateLeft(node: Self): Self { val right = node.right node.right = right?.left right?.left = node return right!! } - protected fun > rotateRight(node: SpecNode): SpecNode { + protected open fun rotateRight(node: Self): Self { val left = node.left node.left = left?.right left?.right = node diff --git a/trees/src/main/kotlin/bst/BinarySearchTree.kt b/trees/src/main/kotlin/bst/BinarySearchTree.kt deleted file mode 100644 index 81f1b6a..0000000 --- a/trees/src/main/kotlin/bst/BinarySearchTree.kt +++ /dev/null @@ -1,92 +0,0 @@ -package bst - -import bst.nodes.BSTNode -import bst.nodes.TreeNode - -open class BinarySearchTree, V> : Tree { - private var rootNode: BSTNode? = null - override fun insert(key: K, value: V) { - val newNode = BSTNode(key, value) - if (this.rootNode === null){ - this.rootNode = newNode - return - } - else{ - insertRecursive(this.rootNode!!, newNode) - } - //add element - //make element as a root, if root is null - //if root is not null it adds element to left or right branch - //if left or right is not null - it checks it, and adds it if right or left is null - } - - private fun insertRecursive(currentNode: BSTNode, newNode: BSTNode){ - if(newNode.key < currentNode.key){ - if(currentNode.left?.key === null){ - currentNode.left = newNode - } - else{ - insertRecursive(currentNode.left!!, newNode) - } - } - if(newNode.key > currentNode.key){ - if(currentNode.right?.key === null){ - currentNode.right = newNode - } - else{ - insertRecursive(currentNode.right!!, newNode) - } - } - } - - override fun remove(key: K) { - // remove node by key - TODO("Not yet implemented") - } - override fun find(key: K): Boolean { - return search(rootNode, key) != null - // return this.search(this.rootNode, key) - // this method gives element by key - } - - protected fun , K : Comparable> search (currentNode: SpecNode?, key: K): SpecNode? { - if (currentNode == null){ - return null - } - if (currentNode.key == key) { - return currentNode - } - - if (currentNode.key < key) { - return search(currentNode.right, key) - } - if (currentNode.key > key) { - return search(currentNode.left, key) - } - else{ - return null - } -// return currentNode - } - - fun symmetricalTreeTraversal() { - TODO() - } - - override fun clear() { - TODO("Not yet implemented") - } -} - -fun main() { - val test_tree = BinarySearchTree() - test_tree.insert(234, "afaraf") - test_tree.insert(235, "afaraf") - test_tree.insert(232, "afaraf") - val res = test_tree.find(235) - println("afadf") -// test_tree.rootNode = BSTNode(123, "erefe") -// val test_1 = mutableListOf(12341, 324) -// test_tree.rootNode!!.left = BSTNode(123, "world_0") -// test_tree.rootNode!!.right = BSTNode(123, "world_1") -} diff --git a/trees/src/main/kotlin/bst/Main.kt b/trees/src/main/kotlin/bst/Main.kt deleted file mode 100644 index 52f8104..0000000 --- a/trees/src/main/kotlin/bst/Main.kt +++ /dev/null @@ -1,9 +0,0 @@ -package bst - -fun main(args: Array) { - println("Hello World!") - - // 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()}") -} diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 7221a06..cf29370 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -2,9 +2,7 @@ package bst import bst.nodes.RBTNode -class RedBlackTree, V> : BalancingTree() { - private var root: RBTNode? = null - +class RedBlackTree, V> : BalancingTree>() { private fun isRed(node: RBTNode?): Boolean { return node?.red == true } @@ -14,12 +12,12 @@ class RedBlackTree, V> : BalancingTree() { } private fun insertNode(key: K, value: V): RBTNode { - if (root == null) { - root = RBTNode(key, value, true) - return root!! + if (rootNode == null) { + rootNode = RBTNode(key, value, true) + return rootNode!! } - var node = root + var node = rootNode var parent: RBTNode? = null while (node != null) { @@ -81,7 +79,7 @@ class RedBlackTree, V> : BalancingTree() { } } - root!!.red = true + rootNode!!.red = true return current } @@ -92,4 +90,8 @@ class RedBlackTree, V> : BalancingTree() { private fun removeNode(node: RBTNode): RBTNode { TODO() } + + override fun initNode(key: K, value: V): RBTNode { + TODO("Not yet implemented") + } } diff --git a/trees/src/main/kotlin/bst/Tree.kt b/trees/src/main/kotlin/bst/Tree.kt index 61b69c4..737c37d 100644 --- a/trees/src/main/kotlin/bst/Tree.kt +++ b/trees/src/main/kotlin/bst/Tree.kt @@ -3,6 +3,5 @@ package bst interface Tree, V> { fun insert(key: K, value: V) fun remove(key: K) - fun find(key: K): Boolean - fun clear() + fun find(key: K): V? } diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt index 993d2cf..27ab4ec 100644 --- a/trees/src/main/kotlin/bst/nodes/AVLNode.kt +++ b/trees/src/main/kotlin/bst/nodes/AVLNode.kt @@ -1,3 +1,8 @@ package bst.nodes -class AVLNode +class AVLNode, V>( + key: K, + value: V +) : BinaryNode>(key, value) { + var height: Int = 0 +} diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt index f949369..2780431 100644 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -1,9 +1,6 @@ package bst.nodes -class BSTNode, V> ( - override val key: K, - override var value: V -) : TreeNode> { - override var left: BSTNode? = null - override var right: BSTNode? = null -} +class BSTNode, V>( + key: K, + value: V +) : BinaryNode>(key, value) diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt new file mode 100644 index 0000000..0bc9754 --- /dev/null +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -0,0 +1,9 @@ +package bst.nodes + +abstract class BinaryNode, V, Self: BinaryNode>( + var key: K, + var value: V +) { + var left: Self? = null + var right: Self? = null +} diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index f5bb485..8ff67c4 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -1,11 +1,7 @@ package bst.nodes -// хз насчет data class, мб че-то заходим в самой ноде делать -class RBTNode, V> ( - override val key: K, - override var value: V, +class RBTNode, V> ( + key: K, + value: V, var red: Boolean = true -) : TreeNode> { - override var left: RBTNode? = null - override var right: RBTNode? = null -} +): BinaryNode>(key, value) diff --git a/trees/src/main/kotlin/bst/nodes/TreeNode.kt b/trees/src/main/kotlin/bst/nodes/TreeNode.kt deleted file mode 100644 index 3454cc6..0000000 --- a/trees/src/main/kotlin/bst/nodes/TreeNode.kt +++ /dev/null @@ -1,9 +0,0 @@ -package bst.nodes - -// а надо ли нам тогда value? хер знает -interface TreeNode, V, SpecNode : TreeNode> { - val key: K - var value: V - var left: SpecNode? - var right: SpecNode? -} From ac0da2cc80a947e7f4a8762b53d1bc78af3959ec Mon Sep 17 00:00:00 2001 From: Danil P <58734659+ancavar@users.noreply.github.com> Date: Tue, 4 Apr 2023 23:43:42 +0300 Subject: [PATCH 022/118] feat: implement better red-black tree methods and tests for it * Create LICENSE * fix: change insert method * fix: change tests * repo: add CI * feat: implement remove method and test for it --- .github/workflows/test.yml | 36 ++ .idea/compiler.xml | 6 - LICENSE | 674 +++++++++++++++++++++ trees/src/main/kotlin/bst/RedBlackTree.kt | 236 ++++++-- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 4 +- trees/src/test/kotlin/bst/ValidRBTTest.kt | 31 +- 6 files changed, 906 insertions(+), 81 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .idea/compiler.xml create mode 100644 LICENSE diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a776eea --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Test with coverage +on: + push: + pull_request: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Build with Gradle + run: ./gradlew build + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle + - name: Run test + run: | + ./gradlew test + - uses: actions/upload-artifact@v2 + with: + name: test-and-coverage-reports + path: build/reports + - name: Cleanup Gradle Cache + + # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. + # Restoring these files from a GitHub Actions cache might cause problems for future builds. + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index e58d3e4..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index cf29370..ca3d160 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -11,84 +11,198 @@ class RedBlackTree, V> : BalancingTree>() insertNode(key, value) } - private fun insertNode(key: K, value: V): RBTNode { + private fun insertNode(key: K, value: V) { if (rootNode == null) { - rootNode = RBTNode(key, value, true) - return rootNode!! - } + /* Empty tree case */ + rootNode = RBTNode(key, value, false) + return + } else { + val head = RBTNode(key, value) // False tree root + + var grandparent: RBTNode? = null // Grandparent + var t: RBTNode = head // Parent + var parent: RBTNode? = null // Iterator + + t.right = rootNode + var q: RBTNode? = t.right // Parent + var dir = false // false - left, true - right + var last = false + + // Search down the tree + while (true) { + if (q == null) { + // Insert new node at the bottom + q = RBTNode(key, value) + if (dir) parent?.right = q else parent?.left = q + } else if (isRed(q.left) && isRed(q.right)) { + // Color flip + q.red = true + q.left!!.red = false + q.right!!.red = false + } + // Fix red violation + if (isRed(q) && isRed(parent)) { + val dir2 = t.child(true) == grandparent// === or == hmmm + if (dir2) { + if (q == parent!!.child(last)) { + t.right = rotate(grandparent!!, !last) + } else { + t.right = doubleRotate(grandparent!!, !last) + + } + } + else { + if (q == parent!!.child(last)) { + t.left = rotate(grandparent!!, !last) + } else { + t.left = doubleRotate(grandparent!!, !last) + + } + } + } - var node = rootNode - var parent: RBTNode? = null + // Stop if found + if (q.key == key) { + q.value = value + break + } - while (node != null) { - parent = node + last = dir + dir = q.key < key - when { - key < node.key -> node = node.left - key > node.key -> node = node.right - else -> { - node.value = value - return node + // Update helpers + if (grandparent != null) { + t = grandparent } + + grandparent = parent + parent = q + q = q.child(dir) } - } - val newNode = RBTNode(key, value, false) - if (key < parent!!.key) { - parent.left = newNode - } else { - parent.right = newNode + // Update root + root = head.right } - - var current = newNode - while (isRed(current) && isRed(current.left)) { - var sibling = current.right - if (isRed(sibling)) { - current.red = true - sibling!!.red = false - current = rotateLeft(current) - - current.red = current.left!!.red - current.left!!.red = true - - sibling = current.right + } + override fun remove(key: K) { + removeNode(key) + } + private fun removeNode(key: K): Int { + if (root != null) { + val head = RBTNode(key, "" as V) // False tree root + var q: RBTNode? = head + var p: RBTNode? = null + var g: RBTNode? // Helpers + var f: RBTNode? = null /* Found item */ + var dir = true + + + q?.right = root + + /* + Search and push a red node down + to fix red violations as we go + */ + while (q!!.child(dir) != null) { + val last = dir + + /* Move the helpers down */ + g = p + p = q + q = q.child(dir) + dir = q!!.key < key + + /* + Save the node with matching data and keep + going; we'll do removal tasks at the end + */ + if (q.key == key) + f = q + + /* Push the red node down with rotations and color flips */ + if (!isRed(q) && !isRed(q.child(dir))) { + if (isRed(q.child(!dir))) { + if (last) { + p.right = rotate(q, dir) + } else { + p.left = rotate(q, dir) + } + p = p.child(last) + } + else if (!isRed(q.child(!dir))) { + val s = p.child(!last) + + if (s != null) { + if (!isRed(s.child(!last)) && !isRed(s.child(last))) { + /* Color flip */ + p.red = false + s.red = true + q.red = true + } else { + val dir2 = (g!!.right == p) + + if (isRed(s.child(last))) + if (dir2) { + g.right = doubleRotate(p, last) + } else { + g.left = doubleRotate(p, last) + } + else if (isRed(s.child(!last))) + if (dir2) { + g.right = rotate(p, last) + } else { + g.left = rotate(p, last) + } + /* Ensure correct coloring */ + q.red = true + g.child(dir2)!!.red = true + g.child(dir2)!!.left!!.red = false + g.child(dir2)!!.right!!.red = false + } + } + } + } } - if (!isRed(sibling?.left) && !isRed(sibling?.right)) { - sibling!!.red = true - current = parent - } else { - if (!isRed(sibling?.right)) { - sibling!!.left?.red = false - sibling.red = true - current = rotateRight(sibling) - - current.red = current.right!!.red - current.right!!.red = true - - sibling = current.right + + /* Replace and remove the saved node */ + if (f != null) { + if (p!!.right == q) { + p.right = q.child(q.left == null) } - sibling!!.red = current.red - current.red = true - sibling.right?.red = true - current = rotateLeft(current) + else { + p.left = q.child(q.left == null) + } + } - current.red = current.left!!.red - current.left!!.red = true + /* Update the root (it may be different) */ + root = head.child(true) - break - } + /* Make the root black for simplified logic */ + if (root != null) + root!!.red = false } - - rootNode!!.red = true - return current + return 1 } - override fun remove(key: K) { - TODO() + private fun rotate(node: RBTNode, dir: Boolean): RBTNode { + val save: RBTNode = if (dir) { + rotateRight(node) + } else { + rotateLeft(node) + } + node.red = true + save.red = false + return save } - private fun removeNode(node: RBTNode): RBTNode { - TODO() + private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { + if (dir) { + node.left = rotate(node.left!!, false) + } + else { + node.right = rotate(node.right!!, true) + } + return rotate(node, dir) } override fun initNode(key: K, value: V): RBTNode { diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 8ff67c4..5a071a5 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -4,4 +4,6 @@ class RBTNode, V> ( key: K, value: V, var red: Boolean = true -): BinaryNode>(key, value) +): BinaryNode>(key, value) { + internal fun child(f: Boolean) = if (f) right else left +} diff --git a/trees/src/test/kotlin/bst/ValidRBTTest.kt b/trees/src/test/kotlin/bst/ValidRBTTest.kt index 753e394..9589523 100644 --- a/trees/src/test/kotlin/bst/ValidRBTTest.kt +++ b/trees/src/test/kotlin/bst/ValidRBTTest.kt @@ -8,23 +8,28 @@ class ValidRBTTest { @Test fun testInsert() { val tree = RedBlackTree() - tree.insert(1, "One") - tree.insert(2, "Two") - tree.insert(3, "Three") - assertEquals("One", tree.find(1)) - assertEquals("Two", tree.find(2)) - assertEquals("Three", tree.find(3)) + tree.insert(19, "S") + tree.insert(5, "E") + tree.insert(1, "A") + tree.insert(18, "R") + assertEquals(true, tree.find(5)) + assertEquals(true, tree.find(1)) + assertEquals(false, tree.find(10)) } @Test fun testDelete() { val tree = RedBlackTree() - tree.insert(1, "One") - tree.insert(2, "Two") - tree.insert(3, "Three") - tree.remove(2) - assertEquals(null, tree.find(2)) - assertEquals("One", tree.find(1)) - assertEquals("Three", tree.find(3)) + tree.insert(19, "S") + tree.insert(5, "E") + tree.insert(1, "A") + tree.insert(18, "R") + tree.remove(1) + tree.remove(19) + assertEquals("R", tree.find(18)) + assertEquals(null, tree.find(1)) + tree.remove(18) + assertEquals("E'", tree.find(5)) + assertEquals(null, tree.find(18)) } } From 54913cefc0a0c70994ec7ce67dba6ae68d3aecfa Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 5 Apr 2023 01:37:28 +0300 Subject: [PATCH 023/118] fix: band-aid fix for removal --- trees/src/main/kotlin/bst/RedBlackTree.kt | 78 +++++++++++++---------- trees/src/test/kotlin/bst/ValidRBTTest.kt | 8 +-- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index ca3d160..a0354c2 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -81,23 +81,25 @@ class RedBlackTree, V> : BalancingTree>() } // Update root - root = head.right + rootNode = head.right } + rootNode!!.red = false } override fun remove(key: K) { removeNode(key) } private fun removeNode(key: K): Int { - if (root != null) { + if (rootNode != null) { val head = RBTNode(key, "" as V) // False tree root var q: RBTNode? = head - var p: RBTNode? = null - var g: RBTNode? // Helpers + var parent: RBTNode? = null + var grandparent: RBTNode? // Helpers var f: RBTNode? = null /* Found item */ var dir = true + var del: Boolean = true - q?.right = root + q?.right = rootNode /* Search and push a red node down @@ -107,57 +109,57 @@ class RedBlackTree, V> : BalancingTree>() val last = dir /* Move the helpers down */ - g = p - p = q - q = q.child(dir) + grandparent = parent + parent = q + q = parent.child(dir) dir = q!!.key < key /* - Save the node with matching data and keep + Save parent of the node with matching data and keep going; we'll do removal tasks at the end */ - if (q.key == key) - f = q - + if (q.key == key) { + f = parent + del = last + } /* Push the red node down with rotations and color flips */ if (!isRed(q) && !isRed(q.child(dir))) { if (isRed(q.child(!dir))) { if (last) { - p.right = rotate(q, dir) + parent.right = rotate(q, dir) } else { - p.left = rotate(q, dir) + parent.left = rotate(q, dir) } - p = p.child(last) - } - else if (!isRed(q.child(!dir))) { - val s = p.child(!last) + parent = parent.child(last) + } else if (!isRed(q.child(!dir))) { + val s = parent.child(!last) if (s != null) { if (!isRed(s.child(!last)) && !isRed(s.child(last))) { /* Color flip */ - p.red = false + parent.red = false s.red = true q.red = true } else { - val dir2 = (g!!.right == p) + val dir2 = (grandparent!!.right == parent) - if (isRed(s.child(last))) + if (isRed(s.child(last))) { if (dir2) { - g.right = doubleRotate(p, last) + grandparent.right = doubleRotate(parent, last) } else { - g.left = doubleRotate(p, last) + grandparent.left = doubleRotate(parent, last) } - else if (isRed(s.child(!last))) + } else if (isRed(s.child(!last))) if (dir2) { - g.right = rotate(p, last) + grandparent.right = rotate(parent, last) } else { - g.left = rotate(p, last) + grandparent.left = rotate(parent, last) } /* Ensure correct coloring */ q.red = true - g.child(dir2)!!.red = true - g.child(dir2)!!.left!!.red = false - g.child(dir2)!!.right!!.red = false + grandparent.child(dir2)!!.red = true + grandparent.child(dir2)!!.left!!.red = false + grandparent.child(dir2)!!.right!!.red = false } } } @@ -166,20 +168,26 @@ class RedBlackTree, V> : BalancingTree>() /* Replace and remove the saved node */ if (f != null) { - if (p!!.right == q) { - p.right = q.child(q.left == null) + if (del) { + f.right = q + } + else { + f.left = q + } + if (parent!!.right == q) { + parent.right = q.child(q.left == null) } else { - p.left = q.child(q.left == null) + parent.left = q.child(q.left == null) } } /* Update the root (it may be different) */ - root = head.child(true) + rootNode = head.child(true) /* Make the root black for simplified logic */ - if (root != null) - root!!.red = false + if (rootNode != null) + rootNode!!.red = false } return 1 } diff --git a/trees/src/test/kotlin/bst/ValidRBTTest.kt b/trees/src/test/kotlin/bst/ValidRBTTest.kt index 9589523..9c27e31 100644 --- a/trees/src/test/kotlin/bst/ValidRBTTest.kt +++ b/trees/src/test/kotlin/bst/ValidRBTTest.kt @@ -12,9 +12,9 @@ class ValidRBTTest { tree.insert(5, "E") tree.insert(1, "A") tree.insert(18, "R") - assertEquals(true, tree.find(5)) - assertEquals(true, tree.find(1)) - assertEquals(false, tree.find(10)) + assertEquals("E", tree.find(5)) + assertEquals("A", tree.find(1)) + assertEquals(null, tree.find(10)) } @Test @@ -29,7 +29,7 @@ class ValidRBTTest { assertEquals("R", tree.find(18)) assertEquals(null, tree.find(1)) tree.remove(18) - assertEquals("E'", tree.find(5)) + assertEquals("E", tree.find(5)) assertEquals(null, tree.find(18)) } } From ab4e10ded291710ef2e256ba82404965179083c0 Mon Sep 17 00:00:00 2001 From: Nikita Lukonenko <68463602+SurfaceYellowDuck@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:29:53 +0300 Subject: [PATCH 024/118] Delete .idea directory --- .idea/.gitignore | 8 -------- .idea/jarRepositories.xml | 20 -------------------- .idea/kotlinc.xml | 6 ------ .idea/misc.xml | 8 -------- .idea/vcs.xml | 6 ------ 5 files changed, 48 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml 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 diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index fdc392f..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 0fc3113..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0f221d8..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From c95cf57ce72ce7d2f7ef8da8ce97bc5493c8ff92 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Thu, 6 Apr 2023 18:25:34 +0300 Subject: [PATCH 025/118] add readme from main --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd585dc..2d58d66 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ -# Binary Search Tree, Red-Black Tree, and AVLTree +# Trees library. +> An open source library library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. +## 🖍 Used technology +![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) +![Junit](https://img.shields.io/badge/-Junit-525A162?&style=for-the-badge) +## :package: How to use +Firstly you need clone repository: -This project implements three different types of self-balancing binary trees in Kotlin: Binary Search Tree (BST), Red-Black Tree (RBT), and AVLTree. \ No newline at end of file +```sh +https://github.com/spbu-coding-2022/trees-3.git +``` From 18fcb150cf44930f45455c7f265c6ee252455f49 Mon Sep 17 00:00:00 2001 From: Nikita Lukonenko <68463602+SurfaceYellowDuck@users.noreply.github.com> Date: Thu, 6 Apr 2023 19:27:43 +0300 Subject: [PATCH 026/118] resolve merge conflict(added missing files) --- {trees => app}/.DS_Store | Bin 6148 -> 6148 bytes app/build.gradle.kts | 32 +++ {trees => app}/src/.DS_Store | Bin 6148 -> 6148 bytes app/src/main/.DS_Store | Bin 0 -> 6148 bytes app/src/main/kotlin/.DS_Store | Bin 0 -> 6148 bytes gradle/.DS_Store | Bin 0 -> 6148 bytes gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 +++++++++++++++++++++++ gradlew.bat | 92 +++++++++ settings.gradle.kts | 12 ++ trees/src/test/.DS_Store | Bin 6148 -> 6148 bytes trees/src/test/kotlin/.DS_Store | Bin 6148 -> 6148 bytes 13 files changed, 386 insertions(+) rename {trees => app}/.DS_Store (95%) create mode 100644 app/build.gradle.kts rename {trees => app}/src/.DS_Store (92%) create mode 100644 app/src/main/.DS_Store create mode 100644 app/src/main/kotlin/.DS_Store create mode 100644 gradle/.DS_Store create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/trees/.DS_Store b/app/.DS_Store similarity index 95% rename from trees/.DS_Store rename to app/.DS_Store index 23d31436068d889adf3b1ec257a294454651cbae..87c7697165a8298384e1bd2e62af01a35fb01b1d 100644 GIT binary patch delta 37 ncmZoMXffDe&&2D`z`&sL9||V>Fl}TvG|*8nG&5ez!Ym2^CV&oc delta 37 pcmZoMXffDe&&2D("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/trees/src/.DS_Store b/app/src/.DS_Store similarity index 92% rename from trees/src/.DS_Store rename to app/src/.DS_Store index 214955dcf8075d445c039a8b81a7bb3f2b27b516..744af33e97b29a8e72c4f0bed021c03dd780e9d4 100644 GIT binary patch delta 63 zcmZoMXfc@J&&a$nU^gQp^JE?-UG`jtM21X;yvZ3%jqHX7Itqqn#*;rVIl{y@TQHwz Nnb@GQnVsV=KL8px5sd%< delta 100 zcmZoMXfc@J&&aYdU^gQp%VZuVUHuY~4ipy@^Pff!Q~iok_B9!%hYOggXse05t%xPzeha9KI2nCtZ<(@e~n-x<>@bSuF2^ zSOzQ6?D&ri(B5sr5G14!!}9*oM{$h0_n}0+{!(m^O-5#~--1ybP10ubT@;F?jm@%E zwyM^x_b4+j_0#FZ^~cwAbs=ROENnlxj0Rc1wtXU#)Q^(ESS3W^07GuBq9l}=E2l{~ zQMsO;uqsxiU#rh%omR79x4P|l!=4=;pxtSA=ktoSv%7zE-XF$KiF{FS4FbPUC0hm; z@QTLLnx4IJ63gTP{fnGMEF&|(3@`)h!hkv7tm?Wfh}XdkFay8F0G$suDxqUA*QkyT z98?Q{SVFfFwCT5za%_W+!CWKGpa`9cs8fY$F@#P>zir|igSkeX4#G4a!aP}+4n?S^ zDzY5}k8DIw1lL1lcc|8}`W@_uoDyi1msCTF& p6qjrKN;( literal 0 HcmV?d00001 diff --git a/app/src/main/kotlin/.DS_Store b/app/src/main/kotlin/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4873712bcd5ed43ba69a430b8224cac32042ecce GIT binary patch literal 6148 zcmeHKO-sW-5Z!I7O({YT3OxqA7Hw@Qh?iLF!K)EHsMN$34aRKghdq=+&iaS^CH@_K zv%3+BdhsAwW?=SBW@k3c+pwDvLdedv-w>iAgm9pQxe|tNgyu=VNX~llKuK$;+b~uk zo@hCi|G)t4U0wuYD26BT(*7M{X6IdtF2qKKv+>aG@@qJZqw%C(f0MaJJcdbrB_fHS4Hp(jV(w z-%L0qr_`%d=kr#hUUM7m=A!1#_xE78n(f7+Sci8{#FtNi7=|GkAev zt9kNANvx7Pc(Uj$ijf&$2AF}>Wx$?zPI+}pmgko zE~ICWM`nN-SZ08FKS+h{|Kp$ge;vdFGr$b2CIhn2^}8L^X7AQ7RnlGSLT{iX6qg$O kOo5@VV#Lx_T!m_Z-z5X+SxgPW1K}S5O#=_iz@IYk0d;0l%K!iX literal 0 HcmV?d00001 diff --git a/gradle/.DS_Store b/gradle/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6d74a8a66e3ac9a2ae76d03ea04342774a32b691 GIT binary patch literal 6148 zcmeHK%}T>S5T32orijpkLXQhxi`E}R@e*S_cr~I2m73V1!I%wcY7V84v%Zi|;_K+l z?pDz1O+;lT%zl&kNwQzUZUz8^H|(teGyuRsC9KqO_(Uj9IwckBnL!l#8Y%Q4JB-y; z7*A!h<2N!u@2&;~WDr1#$J_f;Ax7x4*h5`;(Sp`gc32G3FxJ^mVH!t+yxo2lm6_`7 zT+OLD^Uj5TqY6I{^5MV>vNJk6Rw@oBb`YLK{i54g+*e5+L`gr>1yQe$Dd(qA(o=<} zhDmRrYkd=N>Q24eXpTmkJKHU}w!SfL$-H| literal 0 HcmV?d00001 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 100755 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..fbf2455 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,12 @@ +/* + * 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-3" +include("app") +include("trees") diff --git a/trees/src/test/.DS_Store b/trees/src/test/.DS_Store index 8ff8a67d86a115f796e988fd582e6fd026484a22..8928e9fc1ec7001f279804729b40923c49c94191 100644 GIT binary patch delta 21 ccmZoMXffDuf{DY_z)VNM$k=G}1tt?w07?-CHvj+t delta 21 ccmZoMXffDuf{DY#*g{9a$jox{1tt?w07`uZMF0Q* diff --git a/trees/src/test/kotlin/.DS_Store b/trees/src/test/kotlin/.DS_Store index 50a18b75ce6f860b570c197e1438262f86bde143..ffbb7f5be6822db33d5d6147075e37b515c44754 100644 GIT binary patch delta 21 ccmZoMXffEZhl#_~z)VNM$k=G}5he#w07*FpD*ylh delta 21 ccmZoMXffEZhl#_)*g{9a$jox{5he#w07<0=IRF3v From 9b79a9fb7c511a56d3d7d795f2820fa8fdf6ece0 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Fri, 7 Apr 2023 17:52:28 +0300 Subject: [PATCH 027/118] feat: Add test on tree creation and write auxiliary methods for futher testing --- trees/src/test/kotlin/bst/AvlTest.kt | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 trees/src/test/kotlin/bst/AvlTest.kt diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt new file mode 100644 index 0000000..0f0402a --- /dev/null +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -0,0 +1,42 @@ +package bst + +import bst.nodes.AVLNode +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.math.max + +class AvlTreeTest { + private fun, V> isAvl(node: AVLNode?): Boolean { + if (node == null) return true + return isAvl(node.left) && + isAvl(node.right) && + (subtreeHeight(node.left) - subtreeHeight(node.right) in -1..1) + } + private fun , V> subtreeHeight(node: AVLNode?): Int { + if (node == null) return 0 + return max(subtreeHeight(node.left), subtreeHeight(node.right)) + 1 + } + private fun, V> countNodes(node: AVLNode?): Int { + if (node == null) return 0 + return countNodes(node.left) + countNodes(node.right) + 1 + } + + private lateinit var tree: AVLTree + private val values = IntArray(1000) { it + 1 } + + + @BeforeEach + fun initializeObjects() { + tree = AVLTree() + values.shuffle() + } + + @Test + fun `Empty tree`() { + assertNull(tree.rootNode) + assertEquals(0, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } +} + From 2da358ff17c7cd7d82b36614033c93ae0394d0b0 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Fri, 7 Apr 2023 17:55:08 +0300 Subject: [PATCH 028/118] feat: Add basic tests on insertions --- trees/src/test/kotlin/bst/AvlTest.kt | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 0f0402a..8df4510 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -3,6 +3,7 @@ package bst import bst.nodes.AVLNode import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import kotlin.math.max @@ -38,5 +39,38 @@ class AvlTreeTest { assertEquals(0, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } + + @Nested + inner class `Insertion tests` { + @Test + fun `Single insertion`() { + tree.insert(1, 1) + assertEquals(1, tree.rootNode?.key) + assertEquals(1, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + + @Test + fun `Bigger insertion`() { + tree.insert(2, 2) + tree.insert(3, 3) + tree.insert(1, 1) + + assertEquals(2, tree.rootNode?.key) + assertEquals(3, tree.rootNode?.right?.key) + assertEquals(1, tree.rootNode?.left?.key) + + assertEquals(3, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + + @Test + fun `Same key inserted twice`() { + tree.insert(1, 1) + tree.insert(1, 2) + assertEquals(1, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + } } From 29fd243349ba5b2c3f9d97a36284930b89c0c908 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Fri, 7 Apr 2023 17:57:21 +0300 Subject: [PATCH 029/118] feat: Add tests on insertions causing rotations --- trees/src/test/kotlin/bst/AvlTest.kt | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 8df4510..d7739f8 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -64,6 +64,63 @@ class AvlTreeTest { assertTrue(isAvl(tree.rootNode)) } + @Test + fun `Left-left case`() { + tree.insert(3, 3) + tree.insert(2, 2) + tree.insert(1, 1) + + assertEquals(2, tree.rootNode?.key) + assertEquals(1, tree.rootNode?.left?.key) + assertEquals(3, tree.rootNode?.right?.key) + + assertEquals(3, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + + @Test + fun `Left-right case`() { + tree.insert(5, 5) + tree.insert(3, 3) + tree.insert(4, 4) + + assertEquals(4, tree.rootNode?.key) + assertEquals(3, tree.rootNode?.left?.key) + assertEquals(5, tree.rootNode?.right?.key) + + assertEquals(3, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + + @Test + fun `Right-right case`() { + tree.insert(3, 3) + tree.insert(4, 4) + tree.insert(5, 5) + + assertEquals(4, tree.rootNode?.key) + assertEquals(3, tree.rootNode?.left?.key) + assertEquals(5, tree.rootNode?.right?.key) + + assertEquals(3, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + + } + + @Test + fun `Right-left case`() { + tree.insert(3, 3) + tree.insert(5, 5) + tree.insert(4, 4) + + assertEquals(4, tree.rootNode?.key) + assertEquals(3, tree.rootNode?.left?.key) + assertEquals(5, tree.rootNode?.right?.key) + + assertEquals(3, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + @Test fun `Same key inserted twice`() { tree.insert(1, 1) @@ -71,6 +128,13 @@ class AvlTreeTest { assertEquals(1, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } + + @Test + fun `Multiple insertions`() { + values.forEach{ tree.insert(it, it) } + assertEquals(1000, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } } } From 0bb9a28f676b9db7d284ed4680772627b5faffb4 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Fri, 7 Apr 2023 17:58:17 +0300 Subject: [PATCH 030/118] feat: Add tests on finding elements --- trees/src/test/kotlin/bst/AvlTest.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index d7739f8..c81b997 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -136,5 +136,19 @@ class AvlTreeTest { assertTrue(isAvl(tree.rootNode)) } } + @Nested + inner class `Find tests` { + @Test + fun `Find test`() { + values.forEach{ tree.insert(it, it) } + values.forEach{ assertEquals(it, tree.find(it)) } + } + + @Test + fun `Find by non-existing key`() { + values.forEach{ tree.insert(it, it) } + assertNull(tree.find(-1)) + } + } } From 97f5e9c908bc31467a3368cce928972a786b8ba6 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Fri, 7 Apr 2023 17:59:33 +0300 Subject: [PATCH 031/118] feat: Add some tests on deletions --- trees/src/test/kotlin/bst/AvlTest.kt | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index c81b997..02993b2 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -136,6 +136,7 @@ class AvlTreeTest { assertTrue(isAvl(tree.rootNode)) } } + @Nested inner class `Find tests` { @Test @@ -150,5 +151,34 @@ class AvlTreeTest { assertNull(tree.find(-1)) } } + + @Nested + inner class `Deletion tests` { + @Test + fun `Single deletion`() { + tree.insert(1, 1) + tree.remove(1) + assertEquals(0, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + + @Test + fun `Non-existent key deletion`() { + tree.insert(3, 3) + tree.insert(4, 4) + tree.insert(1, 1) + tree.remove(-1) + } + + @Test + fun `Multiple deletions`() { + values.forEach{ tree.insert(it, it) } + values.take(500).forEach{ tree.remove(it) } + assertEquals(500, countNodes(tree.rootNode)) + assertTrue(isAvl(tree.rootNode)) + } + + //TODO: Add tests on deletions that cause rotations + } } From de7d352bdd10dc89bed49044bb934a0892ae4bea Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 15 Apr 2023 16:01:38 +0300 Subject: [PATCH 032/118] fix: incorrect deletion by not copying node key and value --- trees/src/main/kotlin/bst/RedBlackTree.kt | 30 +++++++++-------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index a0354c2..ce9d8e2 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -4,7 +4,7 @@ import bst.nodes.RBTNode class RedBlackTree, V> : BalancingTree>() { private fun isRed(node: RBTNode?): Boolean { - return node?.red == true + return node != null && node.red } override fun insert(key: K, value: V) { @@ -14,10 +14,11 @@ class RedBlackTree, V> : BalancingTree>() private fun insertNode(key: K, value: V) { if (rootNode == null) { /* Empty tree case */ - rootNode = RBTNode(key, value, false) + rootNode = initNode(key, value) + rootNode!!.red = false return } else { - val head = RBTNode(key, value) // False tree root + val head = initNode(key, value) // False tree root var grandparent: RBTNode? = null // Grandparent var t: RBTNode = head // Parent @@ -32,7 +33,7 @@ class RedBlackTree, V> : BalancingTree>() while (true) { if (q == null) { // Insert new node at the bottom - q = RBTNode(key, value) + q = initNode(key, value) if (dir) parent?.right = q else parent?.left = q } else if (isRed(q.left) && isRed(q.right)) { // Color flip @@ -90,15 +91,13 @@ class RedBlackTree, V> : BalancingTree>() } private fun removeNode(key: K): Int { if (rootNode != null) { - val head = RBTNode(key, "" as V) // False tree root + val head = initNode(key, "" as V) // False tree root var q: RBTNode? = head var parent: RBTNode? = null var grandparent: RBTNode? // Helpers - var f: RBTNode? = null /* Found item */ + var f: RBTNode? = null /* Found item's parent */ var dir = true - var del: Boolean = true - q?.right = rootNode /* @@ -119,8 +118,7 @@ class RedBlackTree, V> : BalancingTree>() going; we'll do removal tasks at the end */ if (q.key == key) { - f = parent - del = last + f = q } /* Push the red node down with rotations and color flips */ if (!isRed(q) && !isRed(q.child(dir))) { @@ -168,12 +166,8 @@ class RedBlackTree, V> : BalancingTree>() /* Replace and remove the saved node */ if (f != null) { - if (del) { - f.right = q - } - else { - f.left = q - } + f.key = q.key + f.value = q.value if (parent!!.right == q) { parent.right = q.child(q.left == null) } @@ -213,7 +207,5 @@ class RedBlackTree, V> : BalancingTree>() return rotate(node, dir) } - override fun initNode(key: K, value: V): RBTNode { - TODO("Not yet implemented") - } + override fun initNode(key: K, value: V): RBTNode = RBTNode(key, value) } From a9cd189ca48694be9bd02f316337f826ac12f8d9 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 15 Apr 2023 16:02:57 +0300 Subject: [PATCH 033/118] feat: more quality tests --- trees/src/test/kotlin/bst/RbtTest.kt | 168 ++++++++++++++++++++++ trees/src/test/kotlin/bst/ValidRBTTest.kt | 35 ----- 2 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 trees/src/test/kotlin/bst/RbtTest.kt delete mode 100644 trees/src/test/kotlin/bst/ValidRBTTest.kt diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt new file mode 100644 index 0000000..b6705a5 --- /dev/null +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -0,0 +1,168 @@ +package bst + +import bst.nodes.RBTNode +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + + +class RbtTest { + /* + This function makes sure that a tree is indeed red-black by walking over + every node and checking invariants. If it returns 0 that means + the tree is invalid red-black tree, and any other number is the black height + of the entire tree. + */ + private fun , V>isRbt(root: RBTNode?): Int { + if (root == null) { + return 1 + } + val left = root.left + val right = root.right + if (isRed(root)) { + if (isRed(left) && isRed(right)) { + return 0 + } + } + val leftHeight = isRbt(left) + val rightHeight = isRbt(right) + + // Invalid binary search tree + if ((left != null && left.key >= root.key) || + (right != null && right.key <= root.key)) { + return 0 + } + + + // Black height mismatch + if (leftHeight != 0 && rightHeight != 0 && leftHeight != rightHeight) { + return 0 + } + + // Counting black links + return if (leftHeight != 0 && rightHeight != 0) { + if (isRed(root)) leftHeight else leftHeight + 1 + } else { + 0 + } + + } + + private fun , V> isRed(node: RBTNode?): Boolean { + return node != null && node.red + } + + private fun , V> countNodes(node: RBTNode?): Int { + if (node == null) return 0 + return countNodes(node.left) + countNodes(node.right) + 1 + } + + private lateinit var tree: RedBlackTree + private val values = IntArray(1_000_000) { it + 1 } + + @BeforeEach + fun initializeObjects() { + tree = RedBlackTree() + } + + @Test + fun `Empty tree`() { + assertNull(tree.rootNode) + assertEquals(0, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + + @Test + fun `Check invariants after each action`() { + values.take(1000).forEach{ + tree.insert(it, it.toString()) + assertNotEquals(0, isRbt(tree.rootNode)) + } + values.take(500).forEach{ + tree.remove(it) + assertNotEquals(0, isRbt(tree.rootNode)) + } + assertEquals(500, countNodes(tree.rootNode)) + + } + + @Nested + inner class InsertionTests { + @Test + fun `Single insertion`() { + tree.insert(1, "A") + assertEquals(1, tree.rootNode!!.key) + assertEquals(1, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + + @Test + fun `Bigger insertion`() { + tree.insert(19, "S") + tree.insert(5, "E") + tree.insert(1, "A") + tree.insert(18, "R") + assertEquals("E", tree.find(tree.rootNode!!.key)) + assertEquals("A", tree.find(tree.rootNode!!.left!!.key)) + assertEquals(null, tree.find(10)) + + assertEquals(4, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + + @Test + fun `Same key inserted twice`() { + tree.insert(1, "A") + tree.insert(1, "B") + assertEquals(1, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + assertEquals("B", tree.find(1)) + } + + } + + @Nested + inner class DeleteTests { + @Test + fun `Single deletion`() { + tree.insert(2, "B") + tree.remove(2) + assertEquals(0, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + + @Test + fun `Non-existent key deletion`() { + tree.insert(2, "B") + tree.remove(3) + assertEquals(1, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + + @Test + fun `Multiple deletions`() { + tree.insert(19, "S") + tree.insert(5, "E") + tree.insert(1, "A") + tree.insert(18, "R") + tree.remove(1) + tree.remove(19) + assertEquals("R", tree. find(18)) + assertEquals(null, tree.find(1)) + tree.remove(18) + assertEquals("E", tree.find(5)) + assertEquals(null, tree.find(18)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + + @Test + fun `Many deletions`() { + values.shuffle() + values.forEach{ tree.insert(it, it.toString()) } + values.take(500_000).forEach{ tree.remove(it) } + assertEquals(500_000, countNodes(tree.rootNode)) + assertNotEquals(0, isRbt(tree.rootNode)) + } + } +} diff --git a/trees/src/test/kotlin/bst/ValidRBTTest.kt b/trees/src/test/kotlin/bst/ValidRBTTest.kt deleted file mode 100644 index 9c27e31..0000000 --- a/trees/src/test/kotlin/bst/ValidRBTTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package bst - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class ValidRBTTest { - - @Test - fun testInsert() { - val tree = RedBlackTree() - tree.insert(19, "S") - tree.insert(5, "E") - tree.insert(1, "A") - tree.insert(18, "R") - assertEquals("E", tree.find(5)) - assertEquals("A", tree.find(1)) - assertEquals(null, tree.find(10)) - } - - @Test - fun testDelete() { - val tree = RedBlackTree() - tree.insert(19, "S") - tree.insert(5, "E") - tree.insert(1, "A") - tree.insert(18, "R") - tree.remove(1) - tree.remove(19) - assertEquals("R", tree.find(18)) - assertEquals(null, tree.find(1)) - tree.remove(18) - assertEquals("E", tree.find(5)) - assertEquals(null, tree.find(18)) - } -} From 40c6bf02cd7b913a6d92e654cf49b0c7f35ed421 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 15 Apr 2023 17:09:12 +0300 Subject: [PATCH 034/118] ci: implement JaCoCo --- .github/workflows/test.yml | 19 +++++++++++++------ trees/build.gradle.kts | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a776eea..df860ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,10 @@ -name: Test with coverage +name: test with coverage on: push: pull_request: jobs: - test: + build: + name: Build and Run Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -12,7 +13,7 @@ jobs: - name: Build with Gradle run: ./gradlew build - name: Cache Gradle packages - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -23,14 +24,20 @@ jobs: - name: Run test run: | ./gradlew test - - uses: actions/upload-artifact@v2 + - name: JaCoCo Coverage Report + env: + report_path: trees/build/jacoco/report.csv + run: | + awk -F"," '{ instructions += $4 + $5; covered += $5; branches += $6 + $7; branches_covered +=$7 } END { print "Instructions covered:", covered"/"instructions, "--", 100*covered/instructions"%"; print "Branches covered:", branches_covered"/"branches, "--", 100*branches_covered/branches"%" }' $report_path + - uses: actions/upload-artifact@v3 with: name: test-and-coverage-reports - path: build/reports + path: trees/build/reports + retention-days: 365 - name: Cleanup Gradle Cache # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. # Restoring these files from a GitHub Actions cache might cause problems for future builds. run: | rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties \ No newline at end of file + rm -f ~/.gradle/caches/modules-2/gc.properties diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 854781c..d2746f5 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -1,6 +1,7 @@ plugins { // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. id("org.jetbrains.kotlin.jvm") version "1.8.10" + id("jacoco") } repositories { @@ -16,7 +17,19 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") } -tasks.named("test") { +tasks.test { // Use JUnit Platform for unit tests. + finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run useJUnitPlatform() } + +tasks.jacocoTestReport { + dependsOn(tasks.test) // tests are required to run before generating the report + reports { + xml.required.set(false) + html.required.set(true) + html.outputLocation.set(layout.buildDirectory.dir("reports/jacoco")) + csv.required.set(true) + csv.outputLocation.set(layout.buildDirectory.file("jacoco/report.csv")) + } +} From eb5bd20416bb4be9f6b442efc8f5c537d307c70e Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 15 Apr 2023 21:22:57 +0300 Subject: [PATCH 035/118] feat: add tests for BST --- trees/src/test/kotlin/bst/BstTest.kt | 58 ++++++++++++++++++++++++++++ trees/src/test/kotlin/bst/RbtTest.kt | 4 +- 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 trees/src/test/kotlin/bst/BstTest.kt diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt new file mode 100644 index 0000000..5a0b668 --- /dev/null +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -0,0 +1,58 @@ +package bst + + +import bst.nodes.BSTNode +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class BstTest { + private fun , V> isBst(root: BSTNode?): Boolean { + if (root == null) { + return true + } + val left = root.left + val right = root.right + if ((left != null && left.key >= root.key) || + (right != null && right.key <= root.key)) { + return false + } + return isBst(left) && isBst(right) + } + + private fun , V> countNodes(node: BSTNode?): Int { + if (node == null) return 0 + return countNodes(node.left) + countNodes(node.right) + 1 + } + + private lateinit var tree: BSTree + + @BeforeEach + fun initializeObjects() { + tree = BSTree() + } + + @Test + fun `Empty tree`() { + assertNull(tree.rootNode) + assertEquals(0, countNodes(tree.rootNode)) + assertTrue(isBst(tree.rootNode)) + } + + @Test + fun `Check invariants after each action`() { + val values = IntArray(1000){ it + 1 } + values.shuffle() + values.forEach { + tree.insert(it, it) + assertTrue(isBst(tree.rootNode)) + } + values.take(500).forEach { + tree.remove(it) + assertTrue(isBst(tree.rootNode)) + } + values.takeLast(500).forEach { + assertNotEquals(null, tree.find(it)) + } + } +} diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index b6705a5..faf3fbf 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -75,11 +75,11 @@ class RbtTest { @Test fun `Check invariants after each action`() { - values.take(1000).forEach{ + values.take(1000).forEach { tree.insert(it, it.toString()) assertNotEquals(0, isRbt(tree.rootNode)) } - values.take(500).forEach{ + values.take(500).forEach { tree.remove(it) assertNotEquals(0, isRbt(tree.rootNode)) } From 67f1beb3032b4c38a4f7a40307bed4ecf47a01b5 Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 17 Apr 2023 23:11:17 +0300 Subject: [PATCH 036/118] fix: change assertion calls to safe --- trees/src/main/kotlin/bst/BalancingTree.kt | 16 +++--- trees/src/main/kotlin/bst/RedBlackTree.kt | 57 ++++++++++------------ trees/src/test/kotlin/bst/RbtTest.kt | 6 +-- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 4bf1b49..696d1cb 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -4,15 +4,15 @@ import bst.nodes.BinaryNode abstract class BalancingTree, V, Self: BinaryNode>: AbstractBST() { protected open fun rotateLeft(node: Self): Self { - val right = node.right - node.right = right?.left - right?.left = node - return right!! + val right = node.right ?: throw NullPointerException() + node.right = right.left + right.left = node + return right } protected open fun rotateRight(node: Self): Self { - val left = node.left - node.left = left?.right - left?.right = node - return left!! + val left = node.left ?: throw NullPointerException() + node.left = left.right + left.right = node + return left } } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index ce9d8e2..ef78a5c 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -15,7 +15,7 @@ class RedBlackTree, V> : BalancingTree>() if (rootNode == null) { /* Empty tree case */ rootNode = initNode(key, value) - rootNode!!.red = false + rootNode?.red = false return } else { val head = initNode(key, value) // False tree root @@ -38,25 +38,25 @@ class RedBlackTree, V> : BalancingTree>() } else if (isRed(q.left) && isRed(q.right)) { // Color flip q.red = true - q.left!!.red = false - q.right!!.red = false + q.left?.red = false + q.right?.red = false } // Fix red violation - if (isRed(q) && isRed(parent)) { + if (isRed(q) && isRed(parent) && grandparent != null) { val dir2 = t.child(true) == grandparent// === or == hmmm if (dir2) { - if (q == parent!!.child(last)) { - t.right = rotate(grandparent!!, !last) + if (q == parent?.child(last)) { + t.right = rotate(grandparent, !last) } else { - t.right = doubleRotate(grandparent!!, !last) + t.right = doubleRotate(grandparent, !last) } } else { - if (q == parent!!.child(last)) { - t.left = rotate(grandparent!!, !last) + if (q == parent?.child(last)) { + t.left = rotate(grandparent, !last) } else { - t.left = doubleRotate(grandparent!!, !last) + t.left = doubleRotate(grandparent, !last) } } @@ -72,10 +72,7 @@ class RedBlackTree, V> : BalancingTree>() dir = q.key < key // Update helpers - if (grandparent != null) { - t = grandparent - } - + t = grandparent ?: t grandparent = parent parent = q q = q.child(dir) @@ -84,7 +81,7 @@ class RedBlackTree, V> : BalancingTree>() // Update root rootNode = head.right } - rootNode!!.red = false + rootNode?.red = false } override fun remove(key: K) { removeNode(key) @@ -92,26 +89,26 @@ class RedBlackTree, V> : BalancingTree>() private fun removeNode(key: K): Int { if (rootNode != null) { val head = initNode(key, "" as V) // False tree root - var q: RBTNode? = head + var q: RBTNode = head var parent: RBTNode? = null var grandparent: RBTNode? // Helpers var f: RBTNode? = null /* Found item's parent */ var dir = true - q?.right = rootNode + q.right = rootNode /* Search and push a red node down to fix red violations as we go */ - while (q!!.child(dir) != null) { + while (q.child(dir) != null) { val last = dir /* Move the helpers down */ grandparent = parent parent = q - q = parent.child(dir) - dir = q!!.key < key + q = parent.child(dir) ?: throw IllegalStateException("Parent node cannot be null") + dir = q.key < key /* Save parent of the node with matching data and keep @@ -139,7 +136,8 @@ class RedBlackTree, V> : BalancingTree>() s.red = true q.red = true } else { - val dir2 = (grandparent!!.right == parent) + val dir2 = (grandparent?.right ?: + throw IllegalStateException("Grandparent node cannot be null")) == parent if (isRed(s.child(last))) { if (dir2) { @@ -155,9 +153,9 @@ class RedBlackTree, V> : BalancingTree>() } /* Ensure correct coloring */ q.red = true - grandparent.child(dir2)!!.red = true - grandparent.child(dir2)!!.left!!.red = false - grandparent.child(dir2)!!.right!!.red = false + grandparent.child(dir2)?.red = true + grandparent.child(dir2)?.left?.red = false + grandparent.child(dir2)?.right?.red = false } } } @@ -168,11 +166,11 @@ class RedBlackTree, V> : BalancingTree>() if (f != null) { f.key = q.key f.value = q.value - if (parent!!.right == q) { + if (parent?.right == q) { parent.right = q.child(q.left == null) } else { - parent.left = q.child(q.left == null) + parent?.left = q.child(q.left == null) } } @@ -180,8 +178,7 @@ class RedBlackTree, V> : BalancingTree>() rootNode = head.child(true) /* Make the root black for simplified logic */ - if (rootNode != null) - rootNode!!.red = false + rootNode?.red = false } return 1 } @@ -199,10 +196,10 @@ class RedBlackTree, V> : BalancingTree>() private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { if (dir) { - node.left = rotate(node.left!!, false) + node.left = node.left?.let { rotate(it, false) } } else { - node.right = rotate(node.right!!, true) + node.right = node.right?.let { rotate(it, true) } } return rotate(node, dir) } diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index faf3fbf..e1c030a 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -92,7 +92,7 @@ class RbtTest { @Test fun `Single insertion`() { tree.insert(1, "A") - assertEquals(1, tree.rootNode!!.key) + assertEquals(1, tree.rootNode?.key) assertEquals(1, countNodes(tree.rootNode)) assertNotEquals(0, isRbt(tree.rootNode)) } @@ -103,8 +103,8 @@ class RbtTest { tree.insert(5, "E") tree.insert(1, "A") tree.insert(18, "R") - assertEquals("E", tree.find(tree.rootNode!!.key)) - assertEquals("A", tree.find(tree.rootNode!!.left!!.key)) + assertEquals("E", tree.rootNode?.let { tree.find(it.key) }) + assertEquals("A", tree.rootNode?.left?.let { tree.find(it.key) }) assertEquals(null, tree.find(10)) assertEquals(4, countNodes(tree.rootNode)) From 209197e7320dc6affb7a0ab05def7b34d12a221d Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 18 Apr 2023 21:41:59 +0300 Subject: [PATCH 037/118] fix: new faster linter, remove gradle test --- .github/workflows/{lint_code.yml => linter.yml} | 3 ++- .github/workflows/test.yml | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) rename .github/workflows/{lint_code.yml => linter.yml} (81%) diff --git a/.github/workflows/lint_code.yml b/.github/workflows/linter.yml similarity index 81% rename from .github/workflows/lint_code.yml rename to .github/workflows/linter.yml index eb4a205..92c0355 100644 --- a/.github/workflows/lint_code.yml +++ b/.github/workflows/linter.yml @@ -14,8 +14,9 @@ jobs: fetch-depth: 0 - name: Lint Code Base - uses: github/super-linter@v4 + uses: github/super-linter/slim@v5 env: + VALIDATE_KOTLIN_ANDROID: false VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: "main" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df860ac..718985c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,9 +21,6 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle - - name: Run test - run: | - ./gradlew test - name: JaCoCo Coverage Report env: report_path: trees/build/jacoco/report.csv From b811a9d4f68802c73c999d173345a4133ead6f23 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 18 Apr 2023 22:33:30 +0300 Subject: [PATCH 038/118] =?UTF-8?q?refa=D1=81tor:=20general=20refactoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/linter.yml | 2 + trees/src/main/kotlin/bst/AVLTree.kt | 16 ++++---- trees/src/main/kotlin/bst/AbstractBST.kt | 12 ++++-- trees/src/main/kotlin/bst/BSTree.kt | 2 +- trees/src/main/kotlin/bst/BalancingTree.kt | 6 +-- trees/src/main/kotlin/bst/RedBlackTree.kt | 25 +++++------- trees/src/main/kotlin/bst/nodes/AVLNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/BSTNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 6 +-- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 4 +- trees/src/test/kotlin/bst/AvlTest.kt | 40 +++++++++---------- trees/src/test/kotlin/bst/BstTest.kt | 11 ++--- trees/src/test/kotlin/bst/RbtTest.kt | 20 ++++------ 13 files changed, 73 insertions(+), 75 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 92c0355..bd49cee 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -16,6 +16,8 @@ jobs: - name: Lint Code Base uses: github/super-linter/slim@v5 env: + VALIDATE_JSCPD: false + VALIDATE_MARKDOWN: false VALIDATE_KOTLIN_ANDROID: false VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: "main" diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index 2907c2f..e1c9bd0 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -1,12 +1,12 @@ -//GPL-3.0-or-later +// GPL-3.0-or-later // -//This file is part of Trees-3. +// This file is part of Trees-3. // -//Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. +// Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. // -//Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. +// Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. // -//You should have obtained a copy of the GNU General Public License with this program. If it is not, see . +// You should have obtained a copy of the GNU General Public License with this program. If it is not, see . // Copyright (C) <2023> package bst @@ -14,7 +14,7 @@ package bst import bst.nodes.AVLNode import kotlin.math.max -class AVLTree, V> : BalancingTree>() { +class AVLTree, V> : BalancingTree>() { override fun initNode(key: K, value: V): AVLNode = AVLNode(key, value) override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { @@ -45,13 +45,13 @@ class AVLTree, V> : BalancingTree>() { return when (getBalanceFactor(node)) { -2 -> { if (getBalanceFactor(node.left) == 1) { - node.left = rotateLeft(node.left!!) + node.left = node.left?.let { rotateLeft(it) } } return rotateRight(node) } 2 -> { if (getBalanceFactor(node.right) == -1) { - node.right = rotateRight(node.right!!) + node.right = node.right?.let { rotateRight(it) } } return rotateLeft(node) } diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 5a6a961..adbb845 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -2,8 +2,9 @@ package bst import bst.nodes.BinaryNode -abstract class AbstractBST, V, Self: BinaryNode> : Tree { +abstract class AbstractBST, V, Self : BinaryNode> : Tree { internal var rootNode: Self? = null + // factory method protected abstract fun initNode(key: K, value: V): Self @@ -53,10 +54,11 @@ abstract class AbstractBST, V, Self: BinaryNode> : } else if (key == node.key) { node.value } else { - if (key < node.key) + if (key < node.key) { findNode(node.left, key) - else + } else { findNode(node.right, key) + } } } @@ -66,6 +68,8 @@ abstract class AbstractBST, V, Self: BinaryNode> : else -> findMax(node.right) } + /* + // for debug purposes only fun printTree() = println(diagram(rootNode)) private fun diagram(node: Self?, @@ -81,4 +85,6 @@ abstract class AbstractBST, V, Self: BinaryNode> : } } ?: "${root}null\n" } + + */ } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index e4b87ee..d294f74 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -2,6 +2,6 @@ package bst import bst.nodes.BSTNode -class BSTree, V> : AbstractBST>() { +class BSTree, V> : AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) } diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 696d1cb..f9df490 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -2,15 +2,15 @@ package bst import bst.nodes.BinaryNode -abstract class BalancingTree, V, Self: BinaryNode>: AbstractBST() { +abstract class BalancingTree, V, Self : BinaryNode> : AbstractBST() { protected open fun rotateLeft(node: Self): Self { - val right = node.right ?: throw NullPointerException() + val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") node.right = right.left right.left = node return right } protected open fun rotateRight(node: Self): Self { - val left = node.left ?: throw NullPointerException() + val left = node.left ?: throw NullPointerException("Node's left child cannot be null") node.left = left.right left.right = node return left diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index ef78a5c..c7bdcbc 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -43,21 +43,18 @@ class RedBlackTree, V> : BalancingTree>() } // Fix red violation if (isRed(q) && isRed(parent) && grandparent != null) { - val dir2 = t.child(true) == grandparent// === or == hmmm + val dir2 = t.child(true) == grandparent if (dir2) { if (q == parent?.child(last)) { t.right = rotate(grandparent, !last) } else { t.right = doubleRotate(grandparent, !last) - } - } - else { + } else { if (q == parent?.child(last)) { t.left = rotate(grandparent, !last) } else { t.left = doubleRotate(grandparent, !last) - } } } @@ -91,7 +88,7 @@ class RedBlackTree, V> : BalancingTree>() val head = initNode(key, "" as V) // False tree root var q: RBTNode = head var parent: RBTNode? = null - var grandparent: RBTNode? // Helpers + var grandparent: RBTNode? var f: RBTNode? = null /* Found item's parent */ var dir = true @@ -136,8 +133,7 @@ class RedBlackTree, V> : BalancingTree>() s.red = true q.red = true } else { - val dir2 = (grandparent?.right ?: - throw IllegalStateException("Grandparent node cannot be null")) == parent + val dir2 = (grandparent?.right ?: throw IllegalStateException("Grandparent node cannot be null")) == parent if (isRed(s.child(last))) { if (dir2) { @@ -145,12 +141,13 @@ class RedBlackTree, V> : BalancingTree>() } else { grandparent.left = doubleRotate(parent, last) } - } else if (isRed(s.child(!last))) + } else if (isRed(s.child(!last))) { if (dir2) { grandparent.right = rotate(parent, last) } else { grandparent.left = rotate(parent, last) } + } /* Ensure correct coloring */ q.red = true grandparent.child(dir2)?.red = true @@ -168,8 +165,7 @@ class RedBlackTree, V> : BalancingTree>() f.value = q.value if (parent?.right == q) { parent.right = q.child(q.left == null) - } - else { + } else { parent?.left = q.child(q.left == null) } } @@ -183,7 +179,7 @@ class RedBlackTree, V> : BalancingTree>() return 1 } - private fun rotate(node: RBTNode, dir: Boolean): RBTNode { + private fun rotate(node: RBTNode, dir: Boolean): RBTNode { val save: RBTNode = if (dir) { rotateRight(node) } else { @@ -194,11 +190,10 @@ class RedBlackTree, V> : BalancingTree>() return save } - private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { + private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { if (dir) { node.left = node.left?.let { rotate(it, false) } - } - else { + } else { node.right = node.right?.let { rotate(it, true) } } return rotate(node, dir) diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt index 27ab4ec..51f1e74 100644 --- a/trees/src/main/kotlin/bst/nodes/AVLNode.kt +++ b/trees/src/main/kotlin/bst/nodes/AVLNode.kt @@ -1,6 +1,6 @@ package bst.nodes -class AVLNode, V>( +class AVLNode, V>( key: K, value: V ) : BinaryNode>(key, value) { diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt index 2780431..31e9e21 100644 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -1,6 +1,6 @@ package bst.nodes -class BSTNode, V>( +class BSTNode, V>( key: K, value: V ) : BinaryNode>(key, value) diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index 0bc9754..a9e8137 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,8 +1,8 @@ package bst.nodes -abstract class BinaryNode, V, Self: BinaryNode>( - var key: K, - var value: V +abstract class BinaryNode, V, Self : BinaryNode>( + var key: K, + var value: V ) { var left: Self? = null var right: Self? = null diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 5a071a5..46193a8 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -1,9 +1,9 @@ package bst.nodes -class RBTNode, V> ( +class RBTNode, V> ( key: K, value: V, var red: Boolean = true -): BinaryNode>(key, value) { +) : BinaryNode>(key, value) { internal fun child(f: Boolean) = if (f) right else left } diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 02993b2..97ad944 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -1,24 +1,26 @@ package bst import bst.nodes.AVLNode -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import kotlin.math.max -class AvlTreeTest { - private fun, V> isAvl(node: AVLNode?): Boolean { +class AvlTest { + private fun, V> isAvl(node: AVLNode?): Boolean { if (node == null) return true return isAvl(node.left) && - isAvl(node.right) && - (subtreeHeight(node.left) - subtreeHeight(node.right) in -1..1) + isAvl(node.right) && + (subtreeHeight(node.left) - subtreeHeight(node.right) in -1..1) } - private fun , V> subtreeHeight(node: AVLNode?): Int { + private fun, V> subtreeHeight(node: AVLNode?): Int { if (node == null) return 0 return max(subtreeHeight(node.left), subtreeHeight(node.right)) + 1 } - private fun, V> countNodes(node: AVLNode?): Int { + private fun, V> countNodes(node: AVLNode?): Int { if (node == null) return 0 return countNodes(node.left) + countNodes(node.right) + 1 } @@ -26,7 +28,6 @@ class AvlTreeTest { private lateinit var tree: AVLTree private val values = IntArray(1000) { it + 1 } - @BeforeEach fun initializeObjects() { tree = AVLTree() @@ -41,7 +42,7 @@ class AvlTreeTest { } @Nested - inner class `Insertion tests` { + inner class InsertionTests { @Test fun `Single insertion`() { tree.insert(1, 1) @@ -104,7 +105,6 @@ class AvlTreeTest { assertEquals(3, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) - } @Test @@ -131,29 +131,29 @@ class AvlTreeTest { @Test fun `Multiple insertions`() { - values.forEach{ tree.insert(it, it) } + values.forEach { tree.insert(it, it) } assertEquals(1000, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } } @Nested - inner class `Find tests` { + inner class FindTests { @Test fun `Find test`() { - values.forEach{ tree.insert(it, it) } - values.forEach{ assertEquals(it, tree.find(it)) } + values.forEach { tree.insert(it, it) } + values.forEach { assertEquals(it, tree.find(it)) } } @Test fun `Find by non-existing key`() { - values.forEach{ tree.insert(it, it) } + values.forEach { tree.insert(it, it) } assertNull(tree.find(-1)) } } @Nested - inner class `Deletion tests` { + inner class DeletionTests { @Test fun `Single deletion`() { tree.insert(1, 1) @@ -172,13 +172,11 @@ class AvlTreeTest { @Test fun `Multiple deletions`() { - values.forEach{ tree.insert(it, it) } - values.take(500).forEach{ tree.remove(it) } + values.forEach { tree.insert(it, it) } + values.take(500).forEach { tree.remove(it) } assertEquals(500, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } - - //TODO: Add tests on deletions that cause rotations + // TODO: Add tests on deletions that cause rotations } } - diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt index 5a0b668..e294f13 100644 --- a/trees/src/test/kotlin/bst/BstTest.kt +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -1,8 +1,10 @@ package bst - import bst.nodes.BSTNode -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -13,8 +15,7 @@ class BstTest { } val left = root.left val right = root.right - if ((left != null && left.key >= root.key) || - (right != null && right.key <= root.key)) { + if ((left != null && left.key >= root.key) || (right != null && right.key <= root.key)) { return false } return isBst(left) && isBst(right) @@ -41,7 +42,7 @@ class BstTest { @Test fun `Check invariants after each action`() { - val values = IntArray(1000){ it + 1 } + val values = IntArray(1000) { it + 1 } values.shuffle() values.forEach { tree.insert(it, it) diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index e1c030a..c80d1d0 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -1,12 +1,13 @@ package bst import bst.nodes.RBTNode -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test - class RbtTest { /* This function makes sure that a tree is indeed red-black by walking over @@ -14,7 +15,7 @@ class RbtTest { the tree is invalid red-black tree, and any other number is the black height of the entire tree. */ - private fun , V>isRbt(root: RBTNode?): Int { + private fun , V>isRbt(root: RBTNode?): Int { if (root == null) { return 1 } @@ -29,12 +30,10 @@ class RbtTest { val rightHeight = isRbt(right) // Invalid binary search tree - if ((left != null && left.key >= root.key) || - (right != null && right.key <= root.key)) { + if ((left != null && left.key >= root.key) || (right != null && right.key <= root.key)) { return 0 } - // Black height mismatch if (leftHeight != 0 && rightHeight != 0 && leftHeight != rightHeight) { return 0 @@ -46,7 +45,6 @@ class RbtTest { } else { 0 } - } private fun , V> isRed(node: RBTNode?): Boolean { @@ -84,7 +82,6 @@ class RbtTest { assertNotEquals(0, isRbt(tree.rootNode)) } assertEquals(500, countNodes(tree.rootNode)) - } @Nested @@ -119,7 +116,6 @@ class RbtTest { assertNotEquals(0, isRbt(tree.rootNode)) assertEquals("B", tree.find(1)) } - } @Nested @@ -148,7 +144,7 @@ class RbtTest { tree.insert(18, "R") tree.remove(1) tree.remove(19) - assertEquals("R", tree. find(18)) + assertEquals("R", tree.find(18)) assertEquals(null, tree.find(1)) tree.remove(18) assertEquals("E", tree.find(5)) @@ -159,8 +155,8 @@ class RbtTest { @Test fun `Many deletions`() { values.shuffle() - values.forEach{ tree.insert(it, it.toString()) } - values.take(500_000).forEach{ tree.remove(it) } + values.forEach { tree.insert(it, it.toString()) } + values.take(500_000).forEach { tree.remove(it) } assertEquals(500_000, countNodes(tree.rootNode)) assertNotEquals(0, isRbt(tree.rootNode)) } From 36b408e0b0e2eea7652e352246ecc550eeb673bf Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 19 Apr 2023 05:05:29 +0300 Subject: [PATCH 039/118] repo: add basic README.md --- README.md | 34 ++++++++++++++++++++-- trees/src/main/kotlin/bst/BalancingTree.kt | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2d58d66..790271a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,39 @@ # Trees library. -> An open source library library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. +> An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) ![Junit](https://img.shields.io/badge/-Junit-525A162?&style=for-the-badge) -## :package: How to use -Firstly you need clone repository: +## :package: Getting started +Firstly, you need to clone repository ```sh https://github.com/spbu-coding-2022/trees-3.git ``` +To build the library run + +```sh + ./gradlew build +``` + +## Using BSTs +Any `Comparable` data can be stored in trees. +```kotlin +import bst.AVLTree +import bst.RedBlackTree +import bst.BSTree +val rbTree = RedBlackTree() // instantiate empty red-black tree +val avlTree = AVLTree() // instantiate empty AVL tree +val simpleTree = BSTree() // instantiate empty simple tree +``` + +Each tree supports 3 basic operations: `insert`, `find`, `remove`. +```kotlin +rbTree.insert(10, "E") +rbTree.insert(20, "B") +rbTree.find(10) // returns "E" +rbTree.find(500) // returns null +rbTree.remove(20) +rbTree.remove(-100) +``` +## Storing BSTs +`bst` provides \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index f9df490..8622300 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -10,7 +10,7 @@ abstract class BalancingTree, V, Self : BinaryNode return right } protected open fun rotateRight(node: Self): Self { - val left = node.left ?: throw NullPointerException("Node's left child cannot be null") + val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") node.left = left.right left.right = node return left From 2dd00acac5a6dc5ae1f401a7e7d52dc9c7acc3b6 Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 17 Apr 2023 22:40:45 +0300 Subject: [PATCH 040/118] db: initial commit for neo4j --- trees/build.gradle.kts | 10 ++++++ trees/src/main/kotlin/bst/AbstractBST.kt | 16 +++++++++ trees/src/main/kotlin/bst/BSTree.kt | 4 +++ trees/src/main/kotlin/bst/db/Neo4j.kt | 28 +++++++++++++++ trees/src/main/kotlin/bst/nodes/BSTNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 34 +++++++++++++++++-- 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/Neo4j.kt diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index d2746f5..4bc97b6 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -2,6 +2,7 @@ plugins { // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. id("org.jetbrains.kotlin.jvm") version "1.8.10" id("jacoco") + id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" } repositories { @@ -15,6 +16,10 @@ dependencies { // Use the JUnit 5 integration. testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + + implementation("org.neo4j:neo4j-ogm-core:4.0.5") + runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") + } tasks.test { @@ -33,3 +38,8 @@ tasks.jacocoTestReport { csv.outputLocation.set(layout.buildDirectory.file("jacoco/report.csv")) } } + +noArg { + annotation("org.neo4j.ogm.annotation.NodeEntity") + annotation("org.neo4j.ogm.annotation.RelationshipEntity") +} diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 5a6a961..b388520 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -1,8 +1,22 @@ package bst import bst.nodes.BinaryNode +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.Labels +import org.neo4j.ogm.annotation.NodeEntity + +@NodeEntity abstract class AbstractBST, V, Self: BinaryNode> : Tree { + @Id + @GeneratedValue + val id: Long? = null + + @Labels + val labels = mutableListOf() + + internal var rootNode: Self? = null // factory method protected abstract fun initNode(key: K, value: V): Self @@ -22,6 +36,7 @@ abstract class AbstractBST, V, Self: BinaryNode> : return node } + override fun remove(key: K) { rootNode = removeNode(rootNode, key) } @@ -82,3 +97,4 @@ abstract class AbstractBST, V, Self: BinaryNode> : } ?: "${root}null\n" } } + diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index e4b87ee..5aa1efa 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,6 +1,10 @@ package bst import bst.nodes.BSTNode +import org.neo4j.ogm.annotation.Labels +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.session.SessionFactory +import org.neo4j.ogm.session.query class BSTree, V> : AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) diff --git a/trees/src/main/kotlin/bst/db/Neo4j.kt b/trees/src/main/kotlin/bst/db/Neo4j.kt new file mode 100644 index 0000000..50ead79 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/Neo4j.kt @@ -0,0 +1,28 @@ +package bst.db + +import bst.* +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.session.SessionFactory +import java.util.NoSuchElementException + +class Neo4j(config: Configuration) { + private val sessionFactory = SessionFactory(config, "bst") + private val session = sessionFactory.openSession() + + fun saveTree(tree: AbstractBST<*, *, *>) { + session.save(tree) + session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) + } + + fun removeTree(name: String) { + session.query("MATCH (n:${name})-[r *0..]->(m) DETACH DELETE m", mapOf()) + ?: throw NoSuchElementException("No tree with that name has been found") + } + + fun getTree(name: String) = session.queryForObject( + AbstractBST::class.java, "MATCH (n:${name})-[r *1..]-(m) " + + "RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") + + fun getNames() = session.query("MATCH (n: AbstractBST) return n", mapOf()) + +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt index 2780431..c250733 100644 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -3,4 +3,4 @@ package bst.nodes class BSTNode, V>( key: K, value: V -) : BinaryNode>(key, value) +) : BinaryNode>(key, value) \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index 0bc9754..d645314 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,9 +1,39 @@ package bst.nodes +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity +import org.neo4j.ogm.annotation.PostLoad +import org.neo4j.ogm.annotation.Property +@NodeEntity abstract class BinaryNode, V, Self: BinaryNode>( - var key: K, - var value: V + var key: K, + var value: V ) { + @Id + @GeneratedValue + var id: Long? = null var left: Self? = null var right: Self? = null + + @Property(name = "key") + private val clavis = key as Any + + @PostLoad + fun keyInit() { + key = clavis as K + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BinaryNode<*, *, *> + + if (id != other.id) return false + if (key != other.key) return false + + return true + } + } From 439eff7d8e714000d2fa489c4db2815829c985aa Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 19 Apr 2023 05:37:26 +0300 Subject: [PATCH 041/118] db: change name to property of a tree --- trees/src/main/kotlin/bst/AbstractBST.kt | 3 +-- trees/src/main/kotlin/bst/db/Neo4j.kt | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index b388520..bd5b0a3 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -13,8 +13,7 @@ abstract class AbstractBST, V, Self: BinaryNode> : @GeneratedValue val id: Long? = null - @Labels - val labels = mutableListOf() + lateinit var treeName: String internal var rootNode: Self? = null diff --git a/trees/src/main/kotlin/bst/db/Neo4j.kt b/trees/src/main/kotlin/bst/db/Neo4j.kt index 50ead79..f742d53 100644 --- a/trees/src/main/kotlin/bst/db/Neo4j.kt +++ b/trees/src/main/kotlin/bst/db/Neo4j.kt @@ -15,13 +15,13 @@ class Neo4j(config: Configuration) { } fun removeTree(name: String) { - session.query("MATCH (n:${name})-[r *0..]->(m) DETACH DELETE m", mapOf()) + session.query("MATCH (n)-[r *0..]->(m) WHERE n.property = ${name} DETACH DELETE m", mapOf()) ?: throw NoSuchElementException("No tree with that name has been found") } fun getTree(name: String) = session.queryForObject( - AbstractBST::class.java, "MATCH (n:${name})-[r *1..]-(m) " + - "RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") + AbstractBST::class.java, "MATCH (n)-[r *1..]-(m) " + + "WHERE n.property = ${name} RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") fun getNames() = session.query("MATCH (n: AbstractBST) return n", mapOf()) From 8504510212d2105b67dde3c577e879641e08ff6f Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 17 Apr 2023 23:11:17 +0300 Subject: [PATCH 042/118] fix: change assertion calls to safe --- trees/src/main/kotlin/bst/BalancingTree.kt | 16 +++--- trees/src/main/kotlin/bst/RedBlackTree.kt | 57 ++++++++++------------ trees/src/test/kotlin/bst/RbtTest.kt | 6 +-- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 4bf1b49..696d1cb 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -4,15 +4,15 @@ import bst.nodes.BinaryNode abstract class BalancingTree, V, Self: BinaryNode>: AbstractBST() { protected open fun rotateLeft(node: Self): Self { - val right = node.right - node.right = right?.left - right?.left = node - return right!! + val right = node.right ?: throw NullPointerException() + node.right = right.left + right.left = node + return right } protected open fun rotateRight(node: Self): Self { - val left = node.left - node.left = left?.right - left?.right = node - return left!! + val left = node.left ?: throw NullPointerException() + node.left = left.right + left.right = node + return left } } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index ce9d8e2..ef78a5c 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -15,7 +15,7 @@ class RedBlackTree, V> : BalancingTree>() if (rootNode == null) { /* Empty tree case */ rootNode = initNode(key, value) - rootNode!!.red = false + rootNode?.red = false return } else { val head = initNode(key, value) // False tree root @@ -38,25 +38,25 @@ class RedBlackTree, V> : BalancingTree>() } else if (isRed(q.left) && isRed(q.right)) { // Color flip q.red = true - q.left!!.red = false - q.right!!.red = false + q.left?.red = false + q.right?.red = false } // Fix red violation - if (isRed(q) && isRed(parent)) { + if (isRed(q) && isRed(parent) && grandparent != null) { val dir2 = t.child(true) == grandparent// === or == hmmm if (dir2) { - if (q == parent!!.child(last)) { - t.right = rotate(grandparent!!, !last) + if (q == parent?.child(last)) { + t.right = rotate(grandparent, !last) } else { - t.right = doubleRotate(grandparent!!, !last) + t.right = doubleRotate(grandparent, !last) } } else { - if (q == parent!!.child(last)) { - t.left = rotate(grandparent!!, !last) + if (q == parent?.child(last)) { + t.left = rotate(grandparent, !last) } else { - t.left = doubleRotate(grandparent!!, !last) + t.left = doubleRotate(grandparent, !last) } } @@ -72,10 +72,7 @@ class RedBlackTree, V> : BalancingTree>() dir = q.key < key // Update helpers - if (grandparent != null) { - t = grandparent - } - + t = grandparent ?: t grandparent = parent parent = q q = q.child(dir) @@ -84,7 +81,7 @@ class RedBlackTree, V> : BalancingTree>() // Update root rootNode = head.right } - rootNode!!.red = false + rootNode?.red = false } override fun remove(key: K) { removeNode(key) @@ -92,26 +89,26 @@ class RedBlackTree, V> : BalancingTree>() private fun removeNode(key: K): Int { if (rootNode != null) { val head = initNode(key, "" as V) // False tree root - var q: RBTNode? = head + var q: RBTNode = head var parent: RBTNode? = null var grandparent: RBTNode? // Helpers var f: RBTNode? = null /* Found item's parent */ var dir = true - q?.right = rootNode + q.right = rootNode /* Search and push a red node down to fix red violations as we go */ - while (q!!.child(dir) != null) { + while (q.child(dir) != null) { val last = dir /* Move the helpers down */ grandparent = parent parent = q - q = parent.child(dir) - dir = q!!.key < key + q = parent.child(dir) ?: throw IllegalStateException("Parent node cannot be null") + dir = q.key < key /* Save parent of the node with matching data and keep @@ -139,7 +136,8 @@ class RedBlackTree, V> : BalancingTree>() s.red = true q.red = true } else { - val dir2 = (grandparent!!.right == parent) + val dir2 = (grandparent?.right ?: + throw IllegalStateException("Grandparent node cannot be null")) == parent if (isRed(s.child(last))) { if (dir2) { @@ -155,9 +153,9 @@ class RedBlackTree, V> : BalancingTree>() } /* Ensure correct coloring */ q.red = true - grandparent.child(dir2)!!.red = true - grandparent.child(dir2)!!.left!!.red = false - grandparent.child(dir2)!!.right!!.red = false + grandparent.child(dir2)?.red = true + grandparent.child(dir2)?.left?.red = false + grandparent.child(dir2)?.right?.red = false } } } @@ -168,11 +166,11 @@ class RedBlackTree, V> : BalancingTree>() if (f != null) { f.key = q.key f.value = q.value - if (parent!!.right == q) { + if (parent?.right == q) { parent.right = q.child(q.left == null) } else { - parent.left = q.child(q.left == null) + parent?.left = q.child(q.left == null) } } @@ -180,8 +178,7 @@ class RedBlackTree, V> : BalancingTree>() rootNode = head.child(true) /* Make the root black for simplified logic */ - if (rootNode != null) - rootNode!!.red = false + rootNode?.red = false } return 1 } @@ -199,10 +196,10 @@ class RedBlackTree, V> : BalancingTree>() private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { if (dir) { - node.left = rotate(node.left!!, false) + node.left = node.left?.let { rotate(it, false) } } else { - node.right = rotate(node.right!!, true) + node.right = node.right?.let { rotate(it, true) } } return rotate(node, dir) } diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index faf3fbf..e1c030a 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -92,7 +92,7 @@ class RbtTest { @Test fun `Single insertion`() { tree.insert(1, "A") - assertEquals(1, tree.rootNode!!.key) + assertEquals(1, tree.rootNode?.key) assertEquals(1, countNodes(tree.rootNode)) assertNotEquals(0, isRbt(tree.rootNode)) } @@ -103,8 +103,8 @@ class RbtTest { tree.insert(5, "E") tree.insert(1, "A") tree.insert(18, "R") - assertEquals("E", tree.find(tree.rootNode!!.key)) - assertEquals("A", tree.find(tree.rootNode!!.left!!.key)) + assertEquals("E", tree.rootNode?.let { tree.find(it.key) }) + assertEquals("A", tree.rootNode?.left?.let { tree.find(it.key) }) assertEquals(null, tree.find(10)) assertEquals(4, countNodes(tree.rootNode)) From 49235b4f38c559cb6d083eea1bbc3dd4f5e1df94 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 18 Apr 2023 21:41:59 +0300 Subject: [PATCH 043/118] fix: new faster linter, remove gradle test --- .github/workflows/{lint_code.yml => linter.yml} | 3 ++- .github/workflows/test.yml | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) rename .github/workflows/{lint_code.yml => linter.yml} (81%) diff --git a/.github/workflows/lint_code.yml b/.github/workflows/linter.yml similarity index 81% rename from .github/workflows/lint_code.yml rename to .github/workflows/linter.yml index eb4a205..92c0355 100644 --- a/.github/workflows/lint_code.yml +++ b/.github/workflows/linter.yml @@ -14,8 +14,9 @@ jobs: fetch-depth: 0 - name: Lint Code Base - uses: github/super-linter@v4 + uses: github/super-linter/slim@v5 env: + VALIDATE_KOTLIN_ANDROID: false VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: "main" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df860ac..718985c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,9 +21,6 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle - - name: Run test - run: | - ./gradlew test - name: JaCoCo Coverage Report env: report_path: trees/build/jacoco/report.csv From 85c0f1a9ed117f090ae3cbacb9b6210ec8f9bbc3 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 18 Apr 2023 22:33:30 +0300 Subject: [PATCH 044/118] =?UTF-8?q?refa=D1=81tor:=20general=20refactoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/linter.yml | 2 + trees/src/main/kotlin/bst/AVLTree.kt | 16 ++++---- trees/src/main/kotlin/bst/AbstractBST.kt | 12 ++++-- trees/src/main/kotlin/bst/BSTree.kt | 2 +- trees/src/main/kotlin/bst/BalancingTree.kt | 6 +-- trees/src/main/kotlin/bst/RedBlackTree.kt | 25 +++++------- trees/src/main/kotlin/bst/nodes/AVLNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/BSTNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 6 +-- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 4 +- trees/src/test/kotlin/bst/AvlTest.kt | 40 +++++++++---------- trees/src/test/kotlin/bst/BstTest.kt | 11 ++--- trees/src/test/kotlin/bst/RbtTest.kt | 20 ++++------ 13 files changed, 73 insertions(+), 75 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 92c0355..bd49cee 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -16,6 +16,8 @@ jobs: - name: Lint Code Base uses: github/super-linter/slim@v5 env: + VALIDATE_JSCPD: false + VALIDATE_MARKDOWN: false VALIDATE_KOTLIN_ANDROID: false VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: "main" diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index 2907c2f..e1c9bd0 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -1,12 +1,12 @@ -//GPL-3.0-or-later +// GPL-3.0-or-later // -//This file is part of Trees-3. +// This file is part of Trees-3. // -//Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. +// Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. // -//Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. +// Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. // -//You should have obtained a copy of the GNU General Public License with this program. If it is not, see . +// You should have obtained a copy of the GNU General Public License with this program. If it is not, see . // Copyright (C) <2023> package bst @@ -14,7 +14,7 @@ package bst import bst.nodes.AVLNode import kotlin.math.max -class AVLTree, V> : BalancingTree>() { +class AVLTree, V> : BalancingTree>() { override fun initNode(key: K, value: V): AVLNode = AVLNode(key, value) override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { @@ -45,13 +45,13 @@ class AVLTree, V> : BalancingTree>() { return when (getBalanceFactor(node)) { -2 -> { if (getBalanceFactor(node.left) == 1) { - node.left = rotateLeft(node.left!!) + node.left = node.left?.let { rotateLeft(it) } } return rotateRight(node) } 2 -> { if (getBalanceFactor(node.right) == -1) { - node.right = rotateRight(node.right!!) + node.right = node.right?.let { rotateRight(it) } } return rotateLeft(node) } diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 5a6a961..adbb845 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -2,8 +2,9 @@ package bst import bst.nodes.BinaryNode -abstract class AbstractBST, V, Self: BinaryNode> : Tree { +abstract class AbstractBST, V, Self : BinaryNode> : Tree { internal var rootNode: Self? = null + // factory method protected abstract fun initNode(key: K, value: V): Self @@ -53,10 +54,11 @@ abstract class AbstractBST, V, Self: BinaryNode> : } else if (key == node.key) { node.value } else { - if (key < node.key) + if (key < node.key) { findNode(node.left, key) - else + } else { findNode(node.right, key) + } } } @@ -66,6 +68,8 @@ abstract class AbstractBST, V, Self: BinaryNode> : else -> findMax(node.right) } + /* + // for debug purposes only fun printTree() = println(diagram(rootNode)) private fun diagram(node: Self?, @@ -81,4 +85,6 @@ abstract class AbstractBST, V, Self: BinaryNode> : } } ?: "${root}null\n" } + + */ } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index e4b87ee..d294f74 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -2,6 +2,6 @@ package bst import bst.nodes.BSTNode -class BSTree, V> : AbstractBST>() { +class BSTree, V> : AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) } diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 696d1cb..f9df490 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -2,15 +2,15 @@ package bst import bst.nodes.BinaryNode -abstract class BalancingTree, V, Self: BinaryNode>: AbstractBST() { +abstract class BalancingTree, V, Self : BinaryNode> : AbstractBST() { protected open fun rotateLeft(node: Self): Self { - val right = node.right ?: throw NullPointerException() + val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") node.right = right.left right.left = node return right } protected open fun rotateRight(node: Self): Self { - val left = node.left ?: throw NullPointerException() + val left = node.left ?: throw NullPointerException("Node's left child cannot be null") node.left = left.right left.right = node return left diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index ef78a5c..c7bdcbc 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -43,21 +43,18 @@ class RedBlackTree, V> : BalancingTree>() } // Fix red violation if (isRed(q) && isRed(parent) && grandparent != null) { - val dir2 = t.child(true) == grandparent// === or == hmmm + val dir2 = t.child(true) == grandparent if (dir2) { if (q == parent?.child(last)) { t.right = rotate(grandparent, !last) } else { t.right = doubleRotate(grandparent, !last) - } - } - else { + } else { if (q == parent?.child(last)) { t.left = rotate(grandparent, !last) } else { t.left = doubleRotate(grandparent, !last) - } } } @@ -91,7 +88,7 @@ class RedBlackTree, V> : BalancingTree>() val head = initNode(key, "" as V) // False tree root var q: RBTNode = head var parent: RBTNode? = null - var grandparent: RBTNode? // Helpers + var grandparent: RBTNode? var f: RBTNode? = null /* Found item's parent */ var dir = true @@ -136,8 +133,7 @@ class RedBlackTree, V> : BalancingTree>() s.red = true q.red = true } else { - val dir2 = (grandparent?.right ?: - throw IllegalStateException("Grandparent node cannot be null")) == parent + val dir2 = (grandparent?.right ?: throw IllegalStateException("Grandparent node cannot be null")) == parent if (isRed(s.child(last))) { if (dir2) { @@ -145,12 +141,13 @@ class RedBlackTree, V> : BalancingTree>() } else { grandparent.left = doubleRotate(parent, last) } - } else if (isRed(s.child(!last))) + } else if (isRed(s.child(!last))) { if (dir2) { grandparent.right = rotate(parent, last) } else { grandparent.left = rotate(parent, last) } + } /* Ensure correct coloring */ q.red = true grandparent.child(dir2)?.red = true @@ -168,8 +165,7 @@ class RedBlackTree, V> : BalancingTree>() f.value = q.value if (parent?.right == q) { parent.right = q.child(q.left == null) - } - else { + } else { parent?.left = q.child(q.left == null) } } @@ -183,7 +179,7 @@ class RedBlackTree, V> : BalancingTree>() return 1 } - private fun rotate(node: RBTNode, dir: Boolean): RBTNode { + private fun rotate(node: RBTNode, dir: Boolean): RBTNode { val save: RBTNode = if (dir) { rotateRight(node) } else { @@ -194,11 +190,10 @@ class RedBlackTree, V> : BalancingTree>() return save } - private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { + private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { if (dir) { node.left = node.left?.let { rotate(it, false) } - } - else { + } else { node.right = node.right?.let { rotate(it, true) } } return rotate(node, dir) diff --git a/trees/src/main/kotlin/bst/nodes/AVLNode.kt b/trees/src/main/kotlin/bst/nodes/AVLNode.kt index 27ab4ec..51f1e74 100644 --- a/trees/src/main/kotlin/bst/nodes/AVLNode.kt +++ b/trees/src/main/kotlin/bst/nodes/AVLNode.kt @@ -1,6 +1,6 @@ package bst.nodes -class AVLNode, V>( +class AVLNode, V>( key: K, value: V ) : BinaryNode>(key, value) { diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt index 2780431..31e9e21 100644 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -1,6 +1,6 @@ package bst.nodes -class BSTNode, V>( +class BSTNode, V>( key: K, value: V ) : BinaryNode>(key, value) diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index 0bc9754..a9e8137 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,8 +1,8 @@ package bst.nodes -abstract class BinaryNode, V, Self: BinaryNode>( - var key: K, - var value: V +abstract class BinaryNode, V, Self : BinaryNode>( + var key: K, + var value: V ) { var left: Self? = null var right: Self? = null diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 5a071a5..46193a8 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -1,9 +1,9 @@ package bst.nodes -class RBTNode, V> ( +class RBTNode, V> ( key: K, value: V, var red: Boolean = true -): BinaryNode>(key, value) { +) : BinaryNode>(key, value) { internal fun child(f: Boolean) = if (f) right else left } diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 02993b2..97ad944 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -1,24 +1,26 @@ package bst import bst.nodes.AVLNode -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import kotlin.math.max -class AvlTreeTest { - private fun, V> isAvl(node: AVLNode?): Boolean { +class AvlTest { + private fun, V> isAvl(node: AVLNode?): Boolean { if (node == null) return true return isAvl(node.left) && - isAvl(node.right) && - (subtreeHeight(node.left) - subtreeHeight(node.right) in -1..1) + isAvl(node.right) && + (subtreeHeight(node.left) - subtreeHeight(node.right) in -1..1) } - private fun , V> subtreeHeight(node: AVLNode?): Int { + private fun, V> subtreeHeight(node: AVLNode?): Int { if (node == null) return 0 return max(subtreeHeight(node.left), subtreeHeight(node.right)) + 1 } - private fun, V> countNodes(node: AVLNode?): Int { + private fun, V> countNodes(node: AVLNode?): Int { if (node == null) return 0 return countNodes(node.left) + countNodes(node.right) + 1 } @@ -26,7 +28,6 @@ class AvlTreeTest { private lateinit var tree: AVLTree private val values = IntArray(1000) { it + 1 } - @BeforeEach fun initializeObjects() { tree = AVLTree() @@ -41,7 +42,7 @@ class AvlTreeTest { } @Nested - inner class `Insertion tests` { + inner class InsertionTests { @Test fun `Single insertion`() { tree.insert(1, 1) @@ -104,7 +105,6 @@ class AvlTreeTest { assertEquals(3, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) - } @Test @@ -131,29 +131,29 @@ class AvlTreeTest { @Test fun `Multiple insertions`() { - values.forEach{ tree.insert(it, it) } + values.forEach { tree.insert(it, it) } assertEquals(1000, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } } @Nested - inner class `Find tests` { + inner class FindTests { @Test fun `Find test`() { - values.forEach{ tree.insert(it, it) } - values.forEach{ assertEquals(it, tree.find(it)) } + values.forEach { tree.insert(it, it) } + values.forEach { assertEquals(it, tree.find(it)) } } @Test fun `Find by non-existing key`() { - values.forEach{ tree.insert(it, it) } + values.forEach { tree.insert(it, it) } assertNull(tree.find(-1)) } } @Nested - inner class `Deletion tests` { + inner class DeletionTests { @Test fun `Single deletion`() { tree.insert(1, 1) @@ -172,13 +172,11 @@ class AvlTreeTest { @Test fun `Multiple deletions`() { - values.forEach{ tree.insert(it, it) } - values.take(500).forEach{ tree.remove(it) } + values.forEach { tree.insert(it, it) } + values.take(500).forEach { tree.remove(it) } assertEquals(500, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } - - //TODO: Add tests on deletions that cause rotations + // TODO: Add tests on deletions that cause rotations } } - diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt index 5a0b668..e294f13 100644 --- a/trees/src/test/kotlin/bst/BstTest.kt +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -1,8 +1,10 @@ package bst - import bst.nodes.BSTNode -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -13,8 +15,7 @@ class BstTest { } val left = root.left val right = root.right - if ((left != null && left.key >= root.key) || - (right != null && right.key <= root.key)) { + if ((left != null && left.key >= root.key) || (right != null && right.key <= root.key)) { return false } return isBst(left) && isBst(right) @@ -41,7 +42,7 @@ class BstTest { @Test fun `Check invariants after each action`() { - val values = IntArray(1000){ it + 1 } + val values = IntArray(1000) { it + 1 } values.shuffle() values.forEach { tree.insert(it, it) diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index e1c030a..c80d1d0 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -1,12 +1,13 @@ package bst import bst.nodes.RBTNode -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test - class RbtTest { /* This function makes sure that a tree is indeed red-black by walking over @@ -14,7 +15,7 @@ class RbtTest { the tree is invalid red-black tree, and any other number is the black height of the entire tree. */ - private fun , V>isRbt(root: RBTNode?): Int { + private fun , V>isRbt(root: RBTNode?): Int { if (root == null) { return 1 } @@ -29,12 +30,10 @@ class RbtTest { val rightHeight = isRbt(right) // Invalid binary search tree - if ((left != null && left.key >= root.key) || - (right != null && right.key <= root.key)) { + if ((left != null && left.key >= root.key) || (right != null && right.key <= root.key)) { return 0 } - // Black height mismatch if (leftHeight != 0 && rightHeight != 0 && leftHeight != rightHeight) { return 0 @@ -46,7 +45,6 @@ class RbtTest { } else { 0 } - } private fun , V> isRed(node: RBTNode?): Boolean { @@ -84,7 +82,6 @@ class RbtTest { assertNotEquals(0, isRbt(tree.rootNode)) } assertEquals(500, countNodes(tree.rootNode)) - } @Nested @@ -119,7 +116,6 @@ class RbtTest { assertNotEquals(0, isRbt(tree.rootNode)) assertEquals("B", tree.find(1)) } - } @Nested @@ -148,7 +144,7 @@ class RbtTest { tree.insert(18, "R") tree.remove(1) tree.remove(19) - assertEquals("R", tree. find(18)) + assertEquals("R", tree.find(18)) assertEquals(null, tree.find(1)) tree.remove(18) assertEquals("E", tree.find(5)) @@ -159,8 +155,8 @@ class RbtTest { @Test fun `Many deletions`() { values.shuffle() - values.forEach{ tree.insert(it, it.toString()) } - values.take(500_000).forEach{ tree.remove(it) } + values.forEach { tree.insert(it, it.toString()) } + values.take(500_000).forEach { tree.remove(it) } assertEquals(500_000, countNodes(tree.rootNode)) assertNotEquals(0, isRbt(tree.rootNode)) } From bcc69b3af5ccb5c49fa43b54fcfd49593f2e0f99 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 19 Apr 2023 05:05:29 +0300 Subject: [PATCH 045/118] repo: add basic README.md --- README.md | 34 ++++++++++++++++++++-- trees/src/main/kotlin/bst/BalancingTree.kt | 2 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2d58d66..790271a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,39 @@ # Trees library. -> An open source library library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. +> An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) ![Junit](https://img.shields.io/badge/-Junit-525A162?&style=for-the-badge) -## :package: How to use -Firstly you need clone repository: +## :package: Getting started +Firstly, you need to clone repository ```sh https://github.com/spbu-coding-2022/trees-3.git ``` +To build the library run + +```sh + ./gradlew build +``` + +## Using BSTs +Any `Comparable` data can be stored in trees. +```kotlin +import bst.AVLTree +import bst.RedBlackTree +import bst.BSTree +val rbTree = RedBlackTree() // instantiate empty red-black tree +val avlTree = AVLTree() // instantiate empty AVL tree +val simpleTree = BSTree() // instantiate empty simple tree +``` + +Each tree supports 3 basic operations: `insert`, `find`, `remove`. +```kotlin +rbTree.insert(10, "E") +rbTree.insert(20, "B") +rbTree.find(10) // returns "E" +rbTree.find(500) // returns null +rbTree.remove(20) +rbTree.remove(-100) +``` +## Storing BSTs +`bst` provides \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index f9df490..8622300 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -10,7 +10,7 @@ abstract class BalancingTree, V, Self : BinaryNode return right } protected open fun rotateRight(node: Self): Self { - val left = node.left ?: throw NullPointerException("Node's left child cannot be null") + val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") node.left = left.right left.right = node return left From 9edffc95a5cf73922ffb196d09a6760f060f7b71 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 19 Apr 2023 08:20:05 +0300 Subject: [PATCH 046/118] Add x, y coordinates to nodes Add jetbrains exposed models Node and Tree Add ability to save BST to json file, and read from json file Connected sqlite db in TreeSQLdbApp.kt --- README.md | 52 ++++++++---- gradle.properties | 4 + trees/build.gradle.kts | 14 ++++ trees/src/db/Node.kt | 17 ++++ trees/src/db/Nodes.kt | 14 ++++ trees/src/db/Tree.kt | 14 ++++ trees/src/db/TreeSQLdbApp.kt | 83 +++++++++++++++++++ trees/src/db/Trees.kt | 10 +++ trees/src/main/kotlin/bst/BSTree.kt | 53 +++++++++++- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 7 +- 10 files changed, 251 insertions(+), 17 deletions(-) create mode 100644 gradle.properties create mode 100644 trees/src/db/Node.kt create mode 100644 trees/src/db/Nodes.kt create mode 100644 trees/src/db/Tree.kt create mode 100644 trees/src/db/TreeSQLdbApp.kt create mode 100644 trees/src/db/Trees.kt diff --git a/README.md b/README.md index 790271a..aaf242f 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,49 @@ To build the library run ./gradlew build ``` -## Using BSTs +## Using BinarySearchTree Any `Comparable` data can be stored in trees. +You can start from create bst by: + ```kotlin -import bst.AVLTree -import bst.RedBlackTree -import bst.BSTree -val rbTree = RedBlackTree() // instantiate empty red-black tree -val avlTree = AVLTree() // instantiate empty AVL tree -val simpleTree = BSTree() // instantiate empty simple tree + import bst.BSTree + val test_data = BSTree("121", "dgs", "tree_1") + //also we support add and remove elements from bst. You can do this by: + test_data.insert("110", "dafad") + test_data.insert("118", "adfaf") + test_data.insert("124", "fggsg") +``` + +find or remove element from tree: +```kotlin + test_data.remove("124") + test_data.find("118") ``` +Same operations support RBT and AVL trees. -Each tree supports 3 basic operations: `insert`, `find`, `remove`. + +BinarySearchTree supports save tree object to json file, and up tree from json. +For example: ```kotlin -rbTree.insert(10, "E") -rbTree.insert(20, "B") -rbTree.find(10) // returns "E" -rbTree.find(500) // returns null -rbTree.remove(20) -rbTree.remove(-100) + test_data.saveTreeToJson() + readFromJson("tree_1.json") ``` + +In development saving nodes to databases: neo4j and sql. + +To save BST in SQL database we propose you to use models in directory main/db. +for example: +```kotlin + val treeObj = Tree.new { + name = "Tree_1" + } + val rootNode = Node.new { + key = "test" + value = "234" + tree = treeObj + } + treeObj.rootNode = rootNode +``` + ## Storing BSTs `bst` provides \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..b35f871 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +sqliteJdbcVersion=3.41.2.1 +kotlinVersion=1.8.10 +neo4jDriverVersion=5.6.0 +exposedVersion=0.40.1 \ No newline at end of file diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index d2746f5..dcd8ac0 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -9,12 +9,26 @@ repositories { mavenCentral() } +val exposedVersion: String by project +val sqliteJdbcVersion: String by project 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") + + implementation("com.google.code.gson:gson:2.10.1") + + // Use JetBrains Exposed + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + + implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion) + + implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") + implementation("org.slf4j", "slf4j-simple", "1.7.29") } tasks.test { diff --git a/trees/src/db/Node.kt b/trees/src/db/Node.kt new file mode 100644 index 0000000..91789cf --- /dev/null +++ b/trees/src/db/Node.kt @@ -0,0 +1,17 @@ +package db +import org.jetbrains.exposed.dao.* +import org.jetbrains.exposed.dao.id.EntityID + +class Node(id: EntityID): IntEntity(id) { + companion object: IntEntityClass(Nodes) + var key by Nodes.key + var value by Nodes.value + val x by Nodes.x + val y by Nodes.y + var left by Node optionalReferencedOn Nodes.left + var right by Node optionalReferencedOn Nodes.right + var tree by Tree referencedOn Nodes.tree + override fun toString(): String = "Node(key = $key, value=$value, x=$x, y=$y, left=$left, right=$right, tree=$tree)" + +} + diff --git a/trees/src/db/Nodes.kt b/trees/src/db/Nodes.kt new file mode 100644 index 0000000..5cee74b --- /dev/null +++ b/trees/src/db/Nodes.kt @@ -0,0 +1,14 @@ +package db +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +object Nodes : IntIdTable("nodes") { + val key = varchar("key", length = 256) + val value = varchar("value", length = 256) + val x = integer("x_coordinate").nullable() + val y = integer("y_coordinate").nullable() + val left = reference("left", Nodes).nullable() + val right = reference("right", Nodes).nullable() + val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) +} + diff --git a/trees/src/db/Tree.kt b/trees/src/db/Tree.kt new file mode 100644 index 0000000..b52af8f --- /dev/null +++ b/trees/src/db/Tree.kt @@ -0,0 +1,14 @@ +package db + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class Tree(id: EntityID): IntEntity(id){ + companion object: IntEntityClass(Trees) + var name by Trees.name + var rootNode by Node optionalReferencedOn Trees.rootNode + override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" +} + +//fun createTree diff --git a/trees/src/db/TreeSQLdbApp.kt b/trees/src/db/TreeSQLdbApp.kt new file mode 100644 index 0000000..0d84738 --- /dev/null +++ b/trees/src/db/TreeSQLdbApp.kt @@ -0,0 +1,83 @@ +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger +import db.Trees +import db.Node +import db.Nodes +import db.Tree +import bst.BSTree +import bst.nodes.BSTNode + +private const val dbPath = "exposed_database.db" +//not finaly сompleted +fun treeSave(tree: BSTree<*, *>){ +// transaction { + val rootNode_ = Node.new { + key=tree.rootNode?.key.toString() + value=tree.rootNode?.value.toString() + } +// val TreeObject = Tree.new { +// name = tree.treeName +// } +// TreeObject.rootNode = rootNode_ +// saveNodes(tree.rootNode, tree.rootNode, TreeObject) +// } +} +//not finaly сompleted +fun saveNodes(node: BSTNode<*, *>?, parentNode:BSTNode<*, *>? = null, tree_: Tree){ + node?.let { + saveNodes(node.left, node, tree_) + saveNodes(node.right, node, tree_) +// transaction { +// if (parentNode == null){ + +// } + + val leftChildNode = Node.new { + key = parentNode?.left?.key.toString() + value = parentNode?.left?.value.toString() + tree = tree_ + } + val rightChildNode = Node.new { + key = parentNode?.right?.key.toString() + value = parentNode?.right?.value.toString() + tree = tree_ + + } + val parentNode_ = Node.new { + key = parentNode?.key.toString() + value = parentNode?.value.toString() + left = leftChildNode + right = rightChildNode + } +// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") + } +} + + +fun main() { + Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") + transaction { + addLogger(StdOutSqlLogger) + SchemaUtils.create(Trees) + SchemaUtils.create(Nodes) + val test_data = BSTree("121", "dgs", "tree_1") + test_data.insert("110", "dafad") + test_data.insert("118", "adfaf") + test_data.insert("124", "fggsg") + treeSave(test_data) + + val treeObj = Tree.new { + name = "Tree_1" + } + val rootNode = Node.new { + key = "test" + value = "234" + tree = treeObj + } + treeObj.rootNode = rootNode +// treeObj.delete() + } +} \ No newline at end of file diff --git a/trees/src/db/Trees.kt b/trees/src/db/Trees.kt new file mode 100644 index 0000000..cd17c52 --- /dev/null +++ b/trees/src/db/Trees.kt @@ -0,0 +1,10 @@ +package db + +import db.Nodes +import org.jetbrains.exposed.dao.id.IntIdTable + +object Trees : IntIdTable("trees") { + val name = varchar("name", length = 128) + val rootNode = reference("rootNode", Nodes).nullable() +} + diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index d294f74..6917916 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,7 +1,58 @@ package bst import bst.nodes.BSTNode +import com.google.gson.Gson +import java.io.FileReader +import java.io.FileWriter -class BSTree, V> : AbstractBST>() { +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, val treeName:String = ""): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) + + init { + if (key != null && value != null) { + rootNode = initNode(key, value) + } + } + + //save_node is method for check recursive addition elements to db + fun save_node(node: BSTNode?, parentNode:BSTNode? = null){ + node?.let { + save_node(node.left, node) + save_node(node.right, node) + println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") + } + } + + fun saveTreeToJson(){ + val gson = Gson() + val writer = FileWriter("${this.treeName}.json") + val treeToSave = this +// val person = Person("John", 30) + gson.toJson(treeToSave, writer) + writer.close() + } + + fun readFromJson(treeName: String): BSTree<*, *>{ + val gson = Gson() + val reader = FileReader(treeName) + val tree = gson.fromJson(reader, BSTree::class.java) + reader.close() + return tree + } +} + + +fun main(){ + val test_data = BSTree("121", "dgs", "tree_1") + test_data.insert("110", "dafad") + test_data.insert("118", "adfaf") + test_data.insert("124", "fggsg") + test_data.remove("124") + test_data.saveTreeToJson() + test_data.readFromJson("tree_1.json") +// test_data.save_node(test_data.rootNode) +// val json = Gson().toJson(test_data) +// println(json) +// val ret_obj = Gson().fromJson, Any>>(json, BSTree::class.java) + } diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index a9e8137..4e9d8ed 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,9 +1,12 @@ package bst.nodes -abstract class BinaryNode, V, Self : BinaryNode>( +abstract class BinaryNode, V, Self: BinaryNode>( var key: K, var value: V ) { + var x: Double = 0.0 + var y: Double = 0.0 var left: Self? = null var right: Self? = null -} + +} \ No newline at end of file From 62a655e1e049a44ba6303e8372c392bd79ea485b Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 17 Apr 2023 22:40:45 +0300 Subject: [PATCH 047/118] db: initial commit for neo4j resolving conflict --- trees/build.gradle.kts | 10 +++++ trees/src/main/kotlin/bst/AbstractBST.kt | 20 ++++++++++ trees/src/main/kotlin/bst/BSTree.kt | 7 ++++ trees/src/main/kotlin/bst/db/Neo4j.kt | 28 ++++++++++++++ trees/src/main/kotlin/bst/nodes/BSTNode.kt | 2 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 38 ++++++++++++++++++- 6 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/Neo4j.kt diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index dcd8ac0..43f83f4 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -2,6 +2,7 @@ plugins { // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. id("org.jetbrains.kotlin.jvm") version "1.8.10" id("jacoco") + id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" } repositories { @@ -29,6 +30,10 @@ dependencies { implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") implementation("org.slf4j", "slf4j-simple", "1.7.29") + + implementation("org.neo4j:neo4j-ogm-core:4.0.5") + runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") + } tasks.test { @@ -47,3 +52,8 @@ tasks.jacocoTestReport { csv.outputLocation.set(layout.buildDirectory.file("jacoco/report.csv")) } } + +noArg { + annotation("org.neo4j.ogm.annotation.NodeEntity") + annotation("org.neo4j.ogm.annotation.RelationshipEntity") +} diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index adbb845..9355502 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -1,8 +1,26 @@ package bst import bst.nodes.BinaryNode +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.Labels +import org.neo4j.ogm.annotation.NodeEntity +<<<<<<< HEAD abstract class AbstractBST, V, Self : BinaryNode> : Tree { +======= + +@NodeEntity +abstract class AbstractBST, V, Self: BinaryNode> : Tree { + @Id + @GeneratedValue + val id: Long? = null + + @Labels + val labels = mutableListOf() + + +>>>>>>> 2dd00ac (db: initial commit for neo4j) internal var rootNode: Self? = null // factory method @@ -23,6 +41,7 @@ abstract class AbstractBST, V, Self : BinaryNode> return node } + override fun remove(key: K) { rootNode = removeNode(rootNode, key) } @@ -88,3 +107,4 @@ abstract class AbstractBST, V, Self : BinaryNode> */ } + diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 6917916..9ec15e2 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,9 +1,16 @@ package bst import bst.nodes.BSTNode +<<<<<<< HEAD import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter +======= +import org.neo4j.ogm.annotation.Labels +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.session.SessionFactory +import org.neo4j.ogm.session.query +>>>>>>> 2dd00ac (db: initial commit for neo4j) class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, val treeName:String = ""): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) diff --git a/trees/src/main/kotlin/bst/db/Neo4j.kt b/trees/src/main/kotlin/bst/db/Neo4j.kt new file mode 100644 index 0000000..50ead79 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/Neo4j.kt @@ -0,0 +1,28 @@ +package bst.db + +import bst.* +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.session.SessionFactory +import java.util.NoSuchElementException + +class Neo4j(config: Configuration) { + private val sessionFactory = SessionFactory(config, "bst") + private val session = sessionFactory.openSession() + + fun saveTree(tree: AbstractBST<*, *, *>) { + session.save(tree) + session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) + } + + fun removeTree(name: String) { + session.query("MATCH (n:${name})-[r *0..]->(m) DETACH DELETE m", mapOf()) + ?: throw NoSuchElementException("No tree with that name has been found") + } + + fun getTree(name: String) = session.queryForObject( + AbstractBST::class.java, "MATCH (n:${name})-[r *1..]-(m) " + + "RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") + + fun getNames() = session.query("MATCH (n: AbstractBST) return n", mapOf()) + +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/BSTNode.kt b/trees/src/main/kotlin/bst/nodes/BSTNode.kt index 31e9e21..dc45fbf 100644 --- a/trees/src/main/kotlin/bst/nodes/BSTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BSTNode.kt @@ -3,4 +3,4 @@ package bst.nodes class BSTNode, V>( key: K, value: V -) : BinaryNode>(key, value) +) : BinaryNode>(key, value) \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index 4e9d8ed..759b544 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,12 +1,48 @@ package bst.nodes +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity +import org.neo4j.ogm.annotation.PostLoad +import org.neo4j.ogm.annotation.Property +@NodeEntity abstract class BinaryNode, V, Self: BinaryNode>( var key: K, var value: V ) { +<<<<<<< HEAD var x: Double = 0.0 var y: Double = 0.0 var left: Self? = null var right: Self? = null -} \ No newline at end of file +} +======= + @Id + @GeneratedValue + var id: Long? = null + var left: Self? = null + var right: Self? = null + + @Property(name = "key") + private val clavis = key as Any + + @PostLoad + fun keyInit() { + key = clavis as K + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BinaryNode<*, *, *> + + if (id != other.id) return false + if (key != other.key) return false + + return true + } + +} +>>>>>>> 2dd00ac (db: initial commit for neo4j) From 4a973be5e52afb1f0445abfec590623b9836fb47 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 19 Apr 2023 05:37:26 +0300 Subject: [PATCH 048/118] db: change name to property of a tree --- trees/src/main/kotlin/bst/AbstractBST.kt | 3 +-- trees/src/main/kotlin/bst/db/Neo4j.kt | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 9355502..284da84 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -16,8 +16,7 @@ abstract class AbstractBST, V, Self: BinaryNode> : @GeneratedValue val id: Long? = null - @Labels - val labels = mutableListOf() + lateinit var treeName: String >>>>>>> 2dd00ac (db: initial commit for neo4j) diff --git a/trees/src/main/kotlin/bst/db/Neo4j.kt b/trees/src/main/kotlin/bst/db/Neo4j.kt index 50ead79..f742d53 100644 --- a/trees/src/main/kotlin/bst/db/Neo4j.kt +++ b/trees/src/main/kotlin/bst/db/Neo4j.kt @@ -15,13 +15,13 @@ class Neo4j(config: Configuration) { } fun removeTree(name: String) { - session.query("MATCH (n:${name})-[r *0..]->(m) DETACH DELETE m", mapOf()) + session.query("MATCH (n)-[r *0..]->(m) WHERE n.property = ${name} DETACH DELETE m", mapOf()) ?: throw NoSuchElementException("No tree with that name has been found") } fun getTree(name: String) = session.queryForObject( - AbstractBST::class.java, "MATCH (n:${name})-[r *1..]-(m) " + - "RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") + AbstractBST::class.java, "MATCH (n)-[r *1..]-(m) " + + "WHERE n.property = ${name} RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") fun getNames() = session.query("MATCH (n: AbstractBST) return n", mapOf()) From 6f2f97d6b8a471d1e8566a86bffa3bda1997f0ec Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 19 Apr 2023 08:20:05 +0300 Subject: [PATCH 049/118] Add x, y coordinates to nodes Add jetbrains exposed models Node and Tree Add ability to save BST to json file, and read from json file Connected sqlite db in TreeSQLdbApp.kt resolved conflicts with AbstractBST and gradle --- trees/build.gradle.kts | 8 +++++--- trees/src/main/kotlin/bst/BSTree.kt | 17 +++++++++++++++++ trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 43f83f4..08df151 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -19,6 +19,11 @@ dependencies { // Use the JUnit 5 integration. testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + + implementation("org.neo4j:neo4j-ogm-core:4.0.5") + runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") + + implementation("com.google.code.gson:gson:2.10.1") // Use JetBrains Exposed @@ -31,9 +36,6 @@ dependencies { implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") implementation("org.slf4j", "slf4j-simple", "1.7.29") - implementation("org.neo4j:neo4j-ogm-core:4.0.5") - runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") - } tasks.test { diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 9ec15e2..f2c3b75 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -2,17 +2,30 @@ package bst import bst.nodes.BSTNode <<<<<<< HEAD +<<<<<<< HEAD import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter ======= +======= + +>>>>>>> 001bf13 (Add x, y coordinates to nodes) import org.neo4j.ogm.annotation.Labels import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory import org.neo4j.ogm.session.query >>>>>>> 2dd00ac (db: initial commit for neo4j) +<<<<<<< HEAD class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, val treeName:String = ""): AbstractBST>() { +======= +import com.google.gson.Gson +import java.io.FileReader +import java.io.FileWriter + + +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { +>>>>>>> 001bf13 (Add x, y coordinates to nodes) override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) init { @@ -50,7 +63,11 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val fun main(){ +<<<<<<< HEAD val test_data = BSTree("121", "dgs", "tree_1") +======= + val test_data = BSTree("121", "dgs") +>>>>>>> 001bf13 (Add x, y coordinates to nodes) test_data.insert("110", "dafad") test_data.insert("118", "adfaf") test_data.insert("124", "fggsg") diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index 759b544..017b85f 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -5,6 +5,7 @@ import org.neo4j.ogm.annotation.NodeEntity import org.neo4j.ogm.annotation.PostLoad import org.neo4j.ogm.annotation.Property +<<<<<<< HEAD @NodeEntity abstract class BinaryNode, V, Self: BinaryNode>( var key: K, @@ -45,4 +46,19 @@ abstract class BinaryNode, V, Self: BinaryNode>( } } +<<<<<<< HEAD >>>>>>> 2dd00ac (db: initial commit for neo4j) +======= +======= +abstract class BinaryNode, V, Self: BinaryNode>( + var key: K, + var value: V +) { + var x: Double = 0.0 + var y: Double = 0.0 + var left: Self? = null + var right: Self? = null + +} +>>>>>>> 9edffc9 (Add x, y coordinates to nodes) +>>>>>>> 001bf13 (Add x, y coordinates to nodes) From 06e414b73b1fe7968a2eb27abf62b42c27a88e7e Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 19 Apr 2023 09:39:04 +0300 Subject: [PATCH 050/118] resolving conflicts --- trees/src/main/kotlin/bst/AbstractBST.kt | 9 +- trees/src/main/kotlin/bst/BSTree.kt | 41 +++------ trees/src/main/kotlin/bst/db/Node.kt | 17 ++++ trees/src/main/kotlin/bst/db/Nodes.kt | 14 ++++ trees/src/main/kotlin/bst/db/Tree.kt | 14 ++++ trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt | 83 +++++++++++++++++++ trees/src/main/kotlin/bst/db/Trees.kt | 10 +++ trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 29 +------ 8 files changed, 153 insertions(+), 64 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/Node.kt create mode 100644 trees/src/main/kotlin/bst/db/Nodes.kt create mode 100644 trees/src/main/kotlin/bst/db/Tree.kt create mode 100644 trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt create mode 100644 trees/src/main/kotlin/bst/db/Trees.kt diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 284da84..b16c9fc 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -6,20 +6,13 @@ import org.neo4j.ogm.annotation.Id import org.neo4j.ogm.annotation.Labels import org.neo4j.ogm.annotation.NodeEntity -<<<<<<< HEAD -abstract class AbstractBST, V, Self : BinaryNode> : Tree { -======= - @NodeEntity abstract class AbstractBST, V, Self: BinaryNode> : Tree { @Id @GeneratedValue val id: Long? = null - lateinit var treeName: String - - ->>>>>>> 2dd00ac (db: initial commit for neo4j) + var treeName: String = "" internal var rootNode: Self? = null // factory method diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index f2c3b75..40276ea 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,36 +1,23 @@ package bst import bst.nodes.BSTNode -<<<<<<< HEAD -<<<<<<< HEAD + import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter -======= -======= ->>>>>>> 001bf13 (Add x, y coordinates to nodes) import org.neo4j.ogm.annotation.Labels import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory import org.neo4j.ogm.session.query ->>>>>>> 2dd00ac (db: initial commit for neo4j) - -<<<<<<< HEAD -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, val treeName:String = ""): AbstractBST>() { -======= -import com.google.gson.Gson -import java.io.FileReader -import java.io.FileWriter -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { ->>>>>>> 001bf13 (Add x, y coordinates to nodes) +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, treeName_: String): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) - init { if (key != null && value != null) { rootNode = initNode(key, value) + treeName = treeName_ } } @@ -47,33 +34,27 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val val gson = Gson() val writer = FileWriter("${this.treeName}.json") val treeToSave = this -// val person = Person("John", 30) gson.toJson(treeToSave, writer) writer.close() } - fun readFromJson(treeName: String): BSTree<*, *>{ - val gson = Gson() - val reader = FileReader(treeName) - val tree = gson.fromJson(reader, BSTree::class.java) - reader.close() - return tree - } +} +fun readFromJson(treeName: String): BSTree<*, *>{ + val gson = Gson() + val reader = FileReader(treeName) + val tree = gson.fromJson(reader, BSTree::class.java) + reader.close() + return tree } fun main(){ -<<<<<<< HEAD val test_data = BSTree("121", "dgs", "tree_1") -======= - val test_data = BSTree("121", "dgs") ->>>>>>> 001bf13 (Add x, y coordinates to nodes) test_data.insert("110", "dafad") test_data.insert("118", "adfaf") test_data.insert("124", "fggsg") - test_data.remove("124") test_data.saveTreeToJson() - test_data.readFromJson("tree_1.json") + readFromJson("tree_1.json") // test_data.save_node(test_data.rootNode) // val json = Gson().toJson(test_data) // println(json) diff --git a/trees/src/main/kotlin/bst/db/Node.kt b/trees/src/main/kotlin/bst/db/Node.kt new file mode 100644 index 0000000..91789cf --- /dev/null +++ b/trees/src/main/kotlin/bst/db/Node.kt @@ -0,0 +1,17 @@ +package db +import org.jetbrains.exposed.dao.* +import org.jetbrains.exposed.dao.id.EntityID + +class Node(id: EntityID): IntEntity(id) { + companion object: IntEntityClass(Nodes) + var key by Nodes.key + var value by Nodes.value + val x by Nodes.x + val y by Nodes.y + var left by Node optionalReferencedOn Nodes.left + var right by Node optionalReferencedOn Nodes.right + var tree by Tree referencedOn Nodes.tree + override fun toString(): String = "Node(key = $key, value=$value, x=$x, y=$y, left=$left, right=$right, tree=$tree)" + +} + diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/Nodes.kt new file mode 100644 index 0000000..5cee74b --- /dev/null +++ b/trees/src/main/kotlin/bst/db/Nodes.kt @@ -0,0 +1,14 @@ +package db +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +object Nodes : IntIdTable("nodes") { + val key = varchar("key", length = 256) + val value = varchar("value", length = 256) + val x = integer("x_coordinate").nullable() + val y = integer("y_coordinate").nullable() + val left = reference("left", Nodes).nullable() + val right = reference("right", Nodes).nullable() + val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) +} + diff --git a/trees/src/main/kotlin/bst/db/Tree.kt b/trees/src/main/kotlin/bst/db/Tree.kt new file mode 100644 index 0000000..b52af8f --- /dev/null +++ b/trees/src/main/kotlin/bst/db/Tree.kt @@ -0,0 +1,14 @@ +package db + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class Tree(id: EntityID): IntEntity(id){ + companion object: IntEntityClass(Trees) + var name by Trees.name + var rootNode by Node optionalReferencedOn Trees.rootNode + override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" +} + +//fun createTree diff --git a/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt b/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt new file mode 100644 index 0000000..0d84738 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt @@ -0,0 +1,83 @@ +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger +import db.Trees +import db.Node +import db.Nodes +import db.Tree +import bst.BSTree +import bst.nodes.BSTNode + +private const val dbPath = "exposed_database.db" +//not finaly сompleted +fun treeSave(tree: BSTree<*, *>){ +// transaction { + val rootNode_ = Node.new { + key=tree.rootNode?.key.toString() + value=tree.rootNode?.value.toString() + } +// val TreeObject = Tree.new { +// name = tree.treeName +// } +// TreeObject.rootNode = rootNode_ +// saveNodes(tree.rootNode, tree.rootNode, TreeObject) +// } +} +//not finaly сompleted +fun saveNodes(node: BSTNode<*, *>?, parentNode:BSTNode<*, *>? = null, tree_: Tree){ + node?.let { + saveNodes(node.left, node, tree_) + saveNodes(node.right, node, tree_) +// transaction { +// if (parentNode == null){ + +// } + + val leftChildNode = Node.new { + key = parentNode?.left?.key.toString() + value = parentNode?.left?.value.toString() + tree = tree_ + } + val rightChildNode = Node.new { + key = parentNode?.right?.key.toString() + value = parentNode?.right?.value.toString() + tree = tree_ + + } + val parentNode_ = Node.new { + key = parentNode?.key.toString() + value = parentNode?.value.toString() + left = leftChildNode + right = rightChildNode + } +// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") + } +} + + +fun main() { + Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") + transaction { + addLogger(StdOutSqlLogger) + SchemaUtils.create(Trees) + SchemaUtils.create(Nodes) + val test_data = BSTree("121", "dgs", "tree_1") + test_data.insert("110", "dafad") + test_data.insert("118", "adfaf") + test_data.insert("124", "fggsg") + treeSave(test_data) + + val treeObj = Tree.new { + name = "Tree_1" + } + val rootNode = Node.new { + key = "test" + value = "234" + tree = treeObj + } + treeObj.rootNode = rootNode +// treeObj.delete() + } +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/Trees.kt b/trees/src/main/kotlin/bst/db/Trees.kt new file mode 100644 index 0000000..cd17c52 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/Trees.kt @@ -0,0 +1,10 @@ +package db + +import db.Nodes +import org.jetbrains.exposed.dao.id.IntIdTable + +object Trees : IntIdTable("trees") { + val name = varchar("name", length = 128) + val rootNode = reference("rootNode", Nodes).nullable() +} + diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index 017b85f..b49755a 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -5,25 +5,18 @@ import org.neo4j.ogm.annotation.NodeEntity import org.neo4j.ogm.annotation.PostLoad import org.neo4j.ogm.annotation.Property -<<<<<<< HEAD @NodeEntity -abstract class BinaryNode, V, Self: BinaryNode>( +abstract class BinaryNode, V, Self : BinaryNode>( var key: K, var value: V ) { -<<<<<<< HEAD - var x: Double = 0.0 - var y: Double = 0.0 - var left: Self? = null - var right: Self? = null - -} -======= @Id @GeneratedValue var id: Long? = null var left: Self? = null var right: Self? = null + var x: Double = 0.0 + var y: Double = 0.0 @Property(name = "key") private val clavis = key as Any @@ -46,19 +39,3 @@ abstract class BinaryNode, V, Self: BinaryNode>( } } -<<<<<<< HEAD ->>>>>>> 2dd00ac (db: initial commit for neo4j) -======= -======= -abstract class BinaryNode, V, Self: BinaryNode>( - var key: K, - var value: V -) { - var x: Double = 0.0 - var y: Double = 0.0 - var left: Self? = null - var right: Self? = null - -} ->>>>>>> 9edffc9 (Add x, y coordinates to nodes) ->>>>>>> 001bf13 (Add x, y coordinates to nodes) From 54d2d1e963f3263b52e8fd21159819244ce086a3 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 19 Apr 2023 09:47:03 +0300 Subject: [PATCH 051/118] fix after resolve in BST --- trees/src/main/kotlin/bst/BSTree.kt | 2 +- trees/src/test/kotlin/bst/BstTest.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 40276ea..cb60376 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -12,7 +12,7 @@ import org.neo4j.ogm.session.SessionFactory import org.neo4j.ogm.session.query -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, treeName_: String): AbstractBST>() { +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, treeName_: String=""): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) init { if (key != null && value != null) { diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt index e294f13..3d34086 100644 --- a/trees/src/test/kotlin/bst/BstTest.kt +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import bst.BSTree class BstTest { private fun , V> isBst(root: BSTNode?): Boolean { From 3659ec461c11a328f25784544c17f76196ed41c6 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 21 Apr 2023 23:15:28 +0300 Subject: [PATCH 052/118] ci: build on multiple os, remove redundant cache --- .github/workflows/test.yml | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 718985c..634a8fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,36 +5,22 @@ on: jobs: build: name: Build and Run Tests - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] steps: - uses: actions/checkout@v3 - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Build with Gradle run: ./gradlew build - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle - - name: JaCoCo Coverage Report - env: - report_path: trees/build/jacoco/report.csv - run: | - awk -F"," '{ instructions += $4 + $5; covered += $5; branches += $6 + $7; branches_covered +=$7 } END { print "Instructions covered:", covered"/"instructions, "--", 100*covered/instructions"%"; print "Branches covered:", branches_covered"/"branches, "--", 100*branches_covered/branches"%" }' $report_path + - uses: actions/upload-artifact@v3 + if: ${{ github.ref == 'refs/heads/main' }} with: name: test-and-coverage-reports path: trees/build/reports - retention-days: 365 - - name: Cleanup Gradle Cache - - # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. - # Restoring these files from a GitHub Actions cache might cause problems for future builds. - run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties + retention-days: 365 \ No newline at end of file From a7439f4fa2118a5958ddd0bbceaa12ac07c8fd5e Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 21 Apr 2023 23:45:55 +0300 Subject: [PATCH 053/118] refactor: better build.gradle.kts code style --- gradle.properties | 4 ---- trees/build.gradle.kts | 14 ++++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index b35f871..0000000 --- a/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -sqliteJdbcVersion=3.41.2.1 -kotlinVersion=1.8.10 -neo4jDriverVersion=5.6.0 -exposedVersion=0.40.1 \ No newline at end of file diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 08df151..f2f5c28 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -10,8 +10,6 @@ repositories { mavenCentral() } -val exposedVersion: String by project -val sqliteJdbcVersion: String by project dependencies { // Use the Kotlin JUnit 5 integration. testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") @@ -27,14 +25,14 @@ dependencies { implementation("com.google.code.gson:gson:2.10.1") // Use JetBrains Exposed - implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + 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", sqliteJdbcVersion) + implementation("org.xerial:sqlite-jdbc:3.41.2.1") - implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") - implementation("org.slf4j", "slf4j-simple", "1.7.29") + implementation("io.github.microutils:kotlin-logging-jvm:2.0.6") + implementation("org.slf4j:slf4j-simple:1.7.29") } From 1ad35158c2e30ef13f771701bb2d137e8be268a9 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 21 Apr 2023 23:52:21 +0300 Subject: [PATCH 054/118] ci: Add dependabot --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..cb2d165 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file From 7df22d1834cfac2fc9fdb3a7cedd158038075784 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 22 Apr 2023 19:43:48 +0300 Subject: [PATCH 055/118] fix: remove debugging tools --- trees/src/main/kotlin/bst/AbstractBST.kt | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 7d214f4..a95ff54 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -79,25 +79,5 @@ abstract class AbstractBST, V, Self: BinaryNode> : node.right == null -> node else -> findMax(node.right) } - - /* - - // for debug purposes only - fun printTree() = println(diagram(rootNode)) - private fun diagram(node: Self?, - top: String = "", - root: String = "", - bottom: String = ""): String { - return node?.let { - if (node.left == null && node.right == null) { - "$root${node.value}\n" - } else { - diagram(node.right, "$top ", "$top┌──", "$top│ ") + - root + "${node.value}\n" + diagram(node.left, "$bottom│ ", "$bottom└──", "$bottom ") - } - } ?: "${root}null\n" - } - - */ } From 072ac94ab6de20d733f907a37d71c27f392c7bd7 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 22 Apr 2023 19:55:19 +0300 Subject: [PATCH 056/118] fix: change color of RBT node to enum type --- trees/src/main/kotlin/bst/RedBlackTree.kt | 40 +++++++++++----------- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 5 ++- trees/src/test/kotlin/bst/RbtTest.kt | 2 +- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index c7bdcbc..fa7a165 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -4,7 +4,7 @@ import bst.nodes.RBTNode class RedBlackTree, V> : BalancingTree>() { private fun isRed(node: RBTNode?): Boolean { - return node != null && node.red + return node != null && node.color == RBTNode.Color.RED } override fun insert(key: K, value: V) { @@ -15,17 +15,17 @@ class RedBlackTree, V> : BalancingTree>() if (rootNode == null) { /* Empty tree case */ rootNode = initNode(key, value) - rootNode?.red = false + rootNode?.color = RBTNode.Color.BLACK return } else { val head = initNode(key, value) // False tree root - var grandparent: RBTNode? = null // Grandparent - var t: RBTNode = head // Parent - var parent: RBTNode? = null // Iterator + var grandparent: RBTNode? = null + var t: RBTNode = head // Iterator + var parent: RBTNode? = null t.right = rootNode - var q: RBTNode? = t.right // Parent + var q: RBTNode? = t.right var dir = false // false - left, true - right var last = false @@ -37,9 +37,9 @@ class RedBlackTree, V> : BalancingTree>() if (dir) parent?.right = q else parent?.left = q } else if (isRed(q.left) && isRed(q.right)) { // Color flip - q.red = true - q.left?.red = false - q.right?.red = false + q.color = RBTNode.Color.RED + q.left?.color = RBTNode.Color.BLACK + q.right?.color = RBTNode.Color.BLACK } // Fix red violation if (isRed(q) && isRed(parent) && grandparent != null) { @@ -78,7 +78,7 @@ class RedBlackTree, V> : BalancingTree>() // Update root rootNode = head.right } - rootNode?.red = false + rootNode?.color = RBTNode.Color.BLACK } override fun remove(key: K) { removeNode(key) @@ -129,9 +129,9 @@ class RedBlackTree, V> : BalancingTree>() if (s != null) { if (!isRed(s.child(!last)) && !isRed(s.child(last))) { /* Color flip */ - parent.red = false - s.red = true - q.red = true + parent.color = RBTNode.Color.BLACK + s.color = RBTNode.Color.RED + q.color = RBTNode.Color.RED } else { val dir2 = (grandparent?.right ?: throw IllegalStateException("Grandparent node cannot be null")) == parent @@ -149,10 +149,10 @@ class RedBlackTree, V> : BalancingTree>() } } /* Ensure correct coloring */ - q.red = true - grandparent.child(dir2)?.red = true - grandparent.child(dir2)?.left?.red = false - grandparent.child(dir2)?.right?.red = false + q.color = RBTNode.Color.RED + grandparent.child(dir2)?.color = RBTNode.Color.RED + grandparent.child(dir2)?.left?.color = RBTNode.Color.BLACK + grandparent.child(dir2)?.right?.color = RBTNode.Color.BLACK } } } @@ -174,7 +174,7 @@ class RedBlackTree, V> : BalancingTree>() rootNode = head.child(true) /* Make the root black for simplified logic */ - rootNode?.red = false + rootNode?.color = RBTNode.Color.BLACK } return 1 } @@ -185,8 +185,8 @@ class RedBlackTree, V> : BalancingTree>() } else { rotateLeft(node) } - node.red = true - save.red = false + node.color = RBTNode.Color.RED + save.color = RBTNode.Color.BLACK return save } diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 46193a8..21e79f6 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -3,7 +3,10 @@ package bst.nodes class RBTNode, V> ( key: K, value: V, - var red: Boolean = true + var color: Color = Color.RED ) : BinaryNode>(key, value) { internal fun child(f: Boolean) = if (f) right else left + enum class Color { + RED, BLACK + } } diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index c80d1d0..2445973 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -48,7 +48,7 @@ class RbtTest { } private fun , V> isRed(node: RBTNode?): Boolean { - return node != null && node.red + return node != null && node.color == RBTNode.Color.RED } private fun , V> countNodes(node: RBTNode?): Int { From 0bc692692f39f308b7a53a085f73d8e9a89a1e1d Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Sat, 22 Apr 2023 22:27:23 +0300 Subject: [PATCH 057/118] fix: Make methods for rotation abstract, fix flaky AVL test with shuffling values and other minor fixes... --- trees/src/main/kotlin/bst/AVLTree.kt | 30 +++++++++------------- trees/src/main/kotlin/bst/BSTree.kt | 4 --- trees/src/main/kotlin/bst/BalancingTree.kt | 14 ++-------- trees/src/main/kotlin/bst/RedBlackTree.kt | 14 ++++++++++ trees/src/test/kotlin/bst/AvlTest.kt | 21 +++++---------- 5 files changed, 34 insertions(+), 49 deletions(-) diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index e1c9bd0..e135fd9 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -1,14 +1,3 @@ -// GPL-3.0-or-later -// -// This file is part of Trees-3. -// -// Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. -// -// Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. -// -// You should have obtained a copy of the GNU General Public License with this program. If it is not, see . -// Copyright (C) <2023> - package bst import bst.nodes.AVLNode @@ -71,7 +60,8 @@ class AVLTree, V> : BalancingTree>() { } else if (node.right == null) { return node.left } else { - val tmp: AVLNode = findMax(node.left)!! + val tmp: AVLNode = findMax(node.left) ?: + throw IllegalStateException("Left subtree must contain elements") node.key = tmp.key node.value = tmp.value node.left = removeNode(node.left, tmp.key) @@ -83,15 +73,19 @@ class AVLTree, V> : BalancingTree>() { } override fun rotateRight(node: AVLNode): AVLNode { - val tmp = super.rotateRight(node) + val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") + node.left = left.right + left.right = node updateHeight(node) - updateHeight(tmp) - return tmp + updateHeight(left) + return left } override fun rotateLeft(node: AVLNode): AVLNode { - val tmp = super.rotateLeft(node) + val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") + node.right = right.left + right.left = node updateHeight(node) - updateHeight(tmp) - return tmp + updateHeight(right) + return right } } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 4b3a893..21d751a 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,8 +1,4 @@ package bst -import org.neo4j.ogm.annotation.Labels -import org.neo4j.ogm.config.Configuration -import org.neo4j.ogm.session.SessionFactory -import org.neo4j.ogm.session.query import bst.nodes.BSTNode import com.google.gson.Gson diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 8622300..5956ff1 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -3,16 +3,6 @@ package bst import bst.nodes.BinaryNode abstract class BalancingTree, V, Self : BinaryNode> : AbstractBST() { - protected open fun rotateLeft(node: Self): Self { - val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") - node.right = right.left - right.left = node - return right - } - protected open fun rotateRight(node: Self): Self { - val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") - node.left = left.right - left.right = node - return left - } + protected abstract fun rotateLeft(node: Self): Self + protected abstract fun rotateRight(node: Self): Self } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index fa7a165..680b9dd 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -200,4 +200,18 @@ class RedBlackTree, V> : BalancingTree>() } override fun initNode(key: K, value: V): RBTNode = RBTNode(key, value) + + override fun rotateLeft(node: RBTNode): RBTNode { + val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") + node.right = right.left + right.left = node + return right + } + + override fun rotateRight(node: RBTNode): RBTNode { + val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") + node.left = left.right + left.right = node + return left + } } diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 97ad944..03836ba 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -31,7 +31,6 @@ class AvlTest { @BeforeEach fun initializeObjects() { tree = AVLTree() - values.shuffle() } @Test @@ -44,15 +43,7 @@ class AvlTest { @Nested inner class InsertionTests { @Test - fun `Single insertion`() { - tree.insert(1, 1) - assertEquals(1, tree.rootNode?.key) - assertEquals(1, countNodes(tree.rootNode)) - assertTrue(isAvl(tree.rootNode)) - } - - @Test - fun `Bigger insertion`() { + fun `Simple insertion`() { tree.insert(2, 2) tree.insert(3, 3) tree.insert(1, 1) @@ -66,7 +57,7 @@ class AvlTest { } @Test - fun `Left-left case`() { + fun `Left-left rotation case`() { tree.insert(3, 3) tree.insert(2, 2) tree.insert(1, 1) @@ -80,7 +71,7 @@ class AvlTest { } @Test - fun `Left-right case`() { + fun `Left-right rotation case`() { tree.insert(5, 5) tree.insert(3, 3) tree.insert(4, 4) @@ -94,7 +85,7 @@ class AvlTest { } @Test - fun `Right-right case`() { + fun `Right-right rotation case`() { tree.insert(3, 3) tree.insert(4, 4) tree.insert(5, 5) @@ -108,7 +99,7 @@ class AvlTest { } @Test - fun `Right-left case`() { + fun `Right-left rotation case`() { tree.insert(3, 3) tree.insert(5, 5) tree.insert(4, 4) @@ -168,6 +159,7 @@ class AvlTest { tree.insert(4, 4) tree.insert(1, 1) tree.remove(-1) + assertEquals(3, countNodes(tree.rootNode)) } @Test @@ -177,6 +169,5 @@ class AvlTest { assertEquals(500, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } - // TODO: Add tests on deletions that cause rotations } } From c18cac2849b7d5dc271d924634fbfe6345eee5e1 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Sun, 23 Apr 2023 20:41:31 +0300 Subject: [PATCH 058/118] implemented saving data to sqlite db --- trees/build.gradle.kts | 10 ++- trees/src/main/kotlin/bst/BSTree.kt | 34 ++++++-- trees/src/main/kotlin/bst/db/Node.kt | 7 +- trees/src/main/kotlin/bst/db/Nodes.kt | 6 +- trees/src/main/kotlin/bst/db/SQLController.kt | 77 +++++++++++++++++ .../main/kotlin/bst/db/SerializableNode.kt | 12 +++ .../main/kotlin/bst/db/SerializableTree.kt | 9 ++ trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt | 83 ------------------- trees/src/main/kotlin/bst/db/Trees.kt | 4 +- 9 files changed, 142 insertions(+), 100 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/SQLController.kt create mode 100644 trees/src/main/kotlin/bst/db/SerializableNode.kt create mode 100644 trees/src/main/kotlin/bst/db/SerializableTree.kt delete mode 100644 trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index f2f5c28..bba28bb 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -3,6 +3,8 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.8.10" id("jacoco") id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" + id ("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" + } repositories { @@ -18,6 +20,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + implementation("org.slf4j", "slf4j-simple", "1.7.29") + + implementation("org.neo4j:neo4j-ogm-core:4.0.5") runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") @@ -31,8 +36,9 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.41.2.1") - implementation("io.github.microutils:kotlin-logging-jvm:2.0.6") - implementation("org.slf4j:slf4j-simple:1.7.29") + implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 21d751a..0b5b376 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -5,20 +5,34 @@ import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, treeName_: String=""): AbstractBST>() { +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) init { if (key != null && value != null) { rootNode = initNode(key, value) - treeName = treeName_ } } + fun setName(treeName: String){ + this.treeName = treeName + } + //save_node is method for check recursive addition elements to db - fun save_node(node: BSTNode?, parentNode:BSTNode? = null){ + fun saveTree(node: BSTNode? = this.rootNode, parentNode:BSTNode? = this.rootNode){ node?.let { - save_node(node.left, node) - save_node(node.right, node) + saveTree(node.left, node) +// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") + +// println("${node.key}: ${node.value}") + saveTree(node.right, node) +// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") + +// println("${node.key}: ${node.value}") +// println("parent node: ${null}: ${null} saving node: ${node.key}: ${node.value}") +// if (parentNode?.key == node.key){ +// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") +// } +// else println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") } } @@ -42,12 +56,16 @@ fun readFromJson(treeName: String): BSTree<*, *>{ fun main(){ - val test_data = BSTree("121", "dgs", "tree_1") + val test_data = BSTree("121", "dgs") test_data.insert("110", "dafad") test_data.insert("118", "adfaf") test_data.insert("124", "fggsg") - test_data.saveTreeToJson() - readFromJson("tree_1.json") + test_data.insert("127", "fggsg") + test_data.insert("123", "fggsg") + test_data.setName("etst") +// test_data.saveTreeToJson() + test_data.saveTree() +// readFromJson("tree_1.json") // test_data.save_node(test_data.rootNode) // val json = Gson().toJson(test_data) // println(json) diff --git a/trees/src/main/kotlin/bst/db/Node.kt b/trees/src/main/kotlin/bst/db/Node.kt index 91789cf..15727d6 100644 --- a/trees/src/main/kotlin/bst/db/Node.kt +++ b/trees/src/main/kotlin/bst/db/Node.kt @@ -6,8 +6,8 @@ class Node(id: EntityID): IntEntity(id) { companion object: IntEntityClass(Nodes) var key by Nodes.key var value by Nodes.value - val x by Nodes.x - val y by Nodes.y + var x by Nodes.x + var y by Nodes.y var left by Node optionalReferencedOn Nodes.left var right by Node optionalReferencedOn Nodes.right var tree by Tree referencedOn Nodes.tree @@ -15,3 +15,6 @@ class Node(id: EntityID): IntEntity(id) { } +//class NodeSerialize(){ +// +//} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/Nodes.kt index 5cee74b..fddd210 100644 --- a/trees/src/main/kotlin/bst/db/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/Nodes.kt @@ -3,10 +3,10 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption object Nodes : IntIdTable("nodes") { - val key = varchar("key", length = 256) + val key = varchar("key", length = 256).uniqueIndex() val value = varchar("value", length = 256) - val x = integer("x_coordinate").nullable() - val y = integer("y_coordinate").nullable() + val x = double("x_coordinate").nullable() + val y = double("y_coordinate").nullable() val left = reference("left", Nodes).nullable() val right = reference("right", Nodes).nullable() val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt new file mode 100644 index 0000000..32eba24 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -0,0 +1,77 @@ +package bst.db +import bst.BSTree +import bst.nodes.BSTNode +import db.Node +import org.jetbrains.exposed.sql.* +import db.Trees +import db.Nodes +import db.Tree +import org.jetbrains.exposed.sql.transactions.transaction + +private const val dbPath = "exposed_database.db" + +class SQLController { + private fun connectDB() { + Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") + } + + private fun serializeNode(node: BSTNode<*, *>?): SerializableNode?{ + return if (node == null){ + null + } else{ + val serializableNode = SerializableNode(key = node.key.toString(), value = node.value.toString(), leftNode = null, rightNode = null) + serializableNode.rightNode = serializeNode(node.right) + serializableNode.leftNode = serializeNode(node.left) + serializableNode + } + } + + private fun serializeTree(tree: BSTree<*, *>): SerializableTree{ + val serializedTree = SerializableTree(treeName = tree.treeName, rootNode = tree.rootNode?.let { serializeNode(it) }) + serializedTree.treeName = tree.treeName + serializedTree.rootNode = serializeNode(tree.rootNode) + return serializedTree + } + + private fun createTables(){ + SchemaUtils.create(Trees) + SchemaUtils.create(Nodes) + } + + + private fun SerializableNode.toNodeEntity(treeEntity: Tree): Node { + return Node.new { + key = this@toNodeEntity.key + value = this@toNodeEntity.value + x = this@toNodeEntity.x + y = this@toNodeEntity.y + left = this@toNodeEntity.leftNode?.toNodeEntity(treeEntity) + right = this@toNodeEntity.rightNode?.toNodeEntity(treeEntity) + tree = treeEntity + } + } + + fun saveTreeToDB(tree: BSTree<*, *>){ + connectDB() + val serializedTree = serializeTree(tree) + transaction { + addLogger(StdOutSqlLogger) + createTables() + val daoTree = Tree.new { + name = serializedTree.treeName + } + daoTree.rootNode = serializedTree.rootNode?.toNodeEntity(daoTree) + } + } +} + +//fun main(){ +// val test_data = BSTree(121, "dgs") +// test_data.insert(110, "dafad") +// test_data.insert(118, "adfaf") +// test_data.insert(124, "fggsg") +// test_data.setName("afefadsf") +// val controller = SQLController() +//// val serializedTree = controller.serializeTree(test_data) +// controller.saveTreeToDB(test_data) +//} diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/SerializableNode.kt new file mode 100644 index 0000000..f9a5b8d --- /dev/null +++ b/trees/src/main/kotlin/bst/db/SerializableNode.kt @@ -0,0 +1,12 @@ +package bst.db +import kotlinx.serialization.* + +@Serializable +class SerializableNode( + val key: String, + val value : String, + val x : Double = 0.0, + val y: Double = 0.0, + var leftNode: SerializableNode? = null, + var rightNode: SerializableNode? = null, +) diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/SerializableTree.kt new file mode 100644 index 0000000..b5e00b8 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/SerializableTree.kt @@ -0,0 +1,9 @@ +package bst.db + +import kotlinx.serialization.Serializable + +@Serializable +class SerializableTree ( + var treeName: String, + var rootNode: SerializableNode?, + ) diff --git a/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt b/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt deleted file mode 100644 index 0d84738..0000000 --- a/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt +++ /dev/null @@ -1,83 +0,0 @@ -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger -import db.Trees -import db.Node -import db.Nodes -import db.Tree -import bst.BSTree -import bst.nodes.BSTNode - -private const val dbPath = "exposed_database.db" -//not finaly сompleted -fun treeSave(tree: BSTree<*, *>){ -// transaction { - val rootNode_ = Node.new { - key=tree.rootNode?.key.toString() - value=tree.rootNode?.value.toString() - } -// val TreeObject = Tree.new { -// name = tree.treeName -// } -// TreeObject.rootNode = rootNode_ -// saveNodes(tree.rootNode, tree.rootNode, TreeObject) -// } -} -//not finaly сompleted -fun saveNodes(node: BSTNode<*, *>?, parentNode:BSTNode<*, *>? = null, tree_: Tree){ - node?.let { - saveNodes(node.left, node, tree_) - saveNodes(node.right, node, tree_) -// transaction { -// if (parentNode == null){ - -// } - - val leftChildNode = Node.new { - key = parentNode?.left?.key.toString() - value = parentNode?.left?.value.toString() - tree = tree_ - } - val rightChildNode = Node.new { - key = parentNode?.right?.key.toString() - value = parentNode?.right?.value.toString() - tree = tree_ - - } - val parentNode_ = Node.new { - key = parentNode?.key.toString() - value = parentNode?.value.toString() - left = leftChildNode - right = rightChildNode - } -// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") - } -} - - -fun main() { - Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") - transaction { - addLogger(StdOutSqlLogger) - SchemaUtils.create(Trees) - SchemaUtils.create(Nodes) - val test_data = BSTree("121", "dgs", "tree_1") - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") - treeSave(test_data) - - val treeObj = Tree.new { - name = "Tree_1" - } - val rootNode = Node.new { - key = "test" - value = "234" - tree = treeObj - } - treeObj.rootNode = rootNode -// treeObj.delete() - } -} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/Trees.kt b/trees/src/main/kotlin/bst/db/Trees.kt index cd17c52..df464e5 100644 --- a/trees/src/main/kotlin/bst/db/Trees.kt +++ b/trees/src/main/kotlin/bst/db/Trees.kt @@ -4,7 +4,7 @@ import db.Nodes import org.jetbrains.exposed.dao.id.IntIdTable object Trees : IntIdTable("trees") { - val name = varchar("name", length = 128) - val rootNode = reference("rootNode", Nodes).nullable() + val name = varchar("name", length = 128).uniqueIndex() + val rootNode = reference("rootNode", Nodes.id).nullable() } From da90745bdc87ca57c90680af82ba31b4fb19ae49 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 24 Apr 2023 03:01:51 +0300 Subject: [PATCH 059/118] feat: implemented reading tree object from sqlite database --- trees/src/main/kotlin/bst/BSTree.kt | 36 +----- .../src/main/kotlin/bst/db/JsonController.kt | 24 ++++ trees/src/main/kotlin/bst/db/Nodes.kt | 5 +- trees/src/main/kotlin/bst/db/SQLController.kt | 121 ++++++++++++++---- .../main/kotlin/bst/db/SerializableTree.kt | 2 +- 5 files changed, 126 insertions(+), 62 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/JsonController.kt diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 0b5b376..c62b7a9 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -17,44 +17,12 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val this.treeName = treeName } - //save_node is method for check recursive addition elements to db - fun saveTree(node: BSTNode? = this.rootNode, parentNode:BSTNode? = this.rootNode){ - node?.let { - saveTree(node.left, node) -// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") -// println("${node.key}: ${node.value}") - saveTree(node.right, node) -// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") -// println("${node.key}: ${node.value}") -// println("parent node: ${null}: ${null} saving node: ${node.key}: ${node.value}") -// if (parentNode?.key == node.key){ -// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") -// } -// else - println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") - } - } - - fun saveTreeToJson(){ - val gson = Gson() - val writer = FileWriter("${this.treeName}.json") - val treeToSave = this - gson.toJson(treeToSave, writer) - writer.close() - } - -} -fun readFromJson(treeName: String): BSTree<*, *>{ - val gson = Gson() - val reader = FileReader(treeName) - val tree = gson.fromJson(reader, BSTree::class.java) - reader.close() - return tree } + fun main(){ val test_data = BSTree("121", "dgs") test_data.insert("110", "dafad") @@ -64,7 +32,7 @@ fun main(){ test_data.insert("123", "fggsg") test_data.setName("etst") // test_data.saveTreeToJson() - test_data.saveTree() +// test_data.saveTree() // readFromJson("tree_1.json") // test_data.save_node(test_data.rootNode) // val json = Gson().toJson(test_data) diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/JsonController.kt new file mode 100644 index 0000000..04b3134 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/JsonController.kt @@ -0,0 +1,24 @@ +package bst.db + +import bst.AVLTree +import bst.BSTree +import com.google.gson.Gson +import java.io.FileReader +import java.io.FileWriter + +class JsonController{ + fun saveTreeToJson(tree: AVLTree<*, *>){ + val gson = Gson() + val writer = FileWriter("${tree.treeName}.json") + val treeToSave = tree + gson.toJson(treeToSave, writer) + writer.close() + } + fun readFromJson(treeName: String): BSTree<*, *> { + val gson = Gson() + val reader = FileReader("$treeName.json") + val tree = gson.fromJson(reader, BSTree::class.java) + reader.close() + return tree + } +} diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/Nodes.kt index fddd210..fca7eb1 100644 --- a/trees/src/main/kotlin/bst/db/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/Nodes.kt @@ -1,12 +1,13 @@ package db +import db.Trees.default import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption object Nodes : IntIdTable("nodes") { val key = varchar("key", length = 256).uniqueIndex() val value = varchar("value", length = 256) - val x = double("x_coordinate").nullable() - val y = double("y_coordinate").nullable() + val x = double("x_coordinate").default(0.0) + val y = double("y_coordinate").default(0.0) val left = reference("left", Nodes).nullable() val right = reference("right", Nodes).nullable() val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index 32eba24..187af38 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -26,11 +26,9 @@ class SQLController { } } - private fun serializeTree(tree: BSTree<*, *>): SerializableTree{ - val serializedTree = SerializableTree(treeName = tree.treeName, rootNode = tree.rootNode?.let { serializeNode(it) }) - serializedTree.treeName = tree.treeName - serializedTree.rootNode = serializeNode(tree.rootNode) - return serializedTree + private fun serializeTree(tree: BSTree<*, *>): SerializableTree? { + return tree.rootNode?.let { serializeNode(it) } + ?.let { SerializableTree(treeName = tree.treeName, rootNode = it) } } private fun createTables(){ @@ -39,15 +37,15 @@ class SQLController { } - private fun SerializableNode.toNodeEntity(treeEntity: Tree): Node { + private fun SerializableNode.toNodeDao(treeDao: Tree): Node { return Node.new { - key = this@toNodeEntity.key - value = this@toNodeEntity.value - x = this@toNodeEntity.x - y = this@toNodeEntity.y - left = this@toNodeEntity.leftNode?.toNodeEntity(treeEntity) - right = this@toNodeEntity.rightNode?.toNodeEntity(treeEntity) - tree = treeEntity + key = this@toNodeDao.key + value = this@toNodeDao.value + x = this@toNodeDao.x + y = this@toNodeDao.y + left = this@toNodeDao.leftNode?.toNodeDao(treeDao) + right = this@toNodeDao.rightNode?.toNodeDao(treeDao) + tree = treeDao } } @@ -58,20 +56,93 @@ class SQLController { addLogger(StdOutSqlLogger) createTables() val daoTree = Tree.new { + if(serializedTree!=null) name = serializedTree.treeName } - daoTree.rootNode = serializedTree.rootNode?.toNodeEntity(daoTree) + daoTree.rootNode = serializedTree?.rootNode?.toNodeDao(daoTree) } } -} -//fun main(){ -// val test_data = BSTree(121, "dgs") -// test_data.insert(110, "dafad") -// test_data.insert(118, "adfaf") -// test_data.insert(124, "fggsg") -// test_data.setName("afefadsf") -// val controller = SQLController() -//// val serializedTree = controller.serializeTree(test_data) -// controller.saveTreeToDB(test_data) -//} + private fun Node.getSerializedNode(treeDao: Tree): SerializableNode { + return SerializableNode( + this@getSerializedNode.key, + this@getSerializedNode.value, + this@getSerializedNode.x, + this@getSerializedNode.y, + this@getSerializedNode.left?.getSerializedNode(treeDao), + this@getSerializedNode.right?.getSerializedNode(treeDao), + ) + + } + + private fun findTree(treeName: String): SerializableTree?{ + connectDB() + val treeDAO = Tree.find{Trees.name eq treeName}.firstOrNull() ?: return null + return treeDAO.rootNode?.getSerializedNode(treeDAO)?.let { + SerializableTree( + treeName, + it + ) + } + } + + private fun isNumeric(s: String): Boolean { + return try { + s.toDouble() + true + } catch (e: NumberFormatException) { + false + } + } + + private fun deserializeNodeStringKey(node: SerializableNode?): BSTNode? { + return if (node == null){ + null + } else{ + + val deserializableNode = BSTNode(key = node.key, value = node.value) + deserializableNode.right = deserializeNodeStringKey(node.rightNode) + deserializableNode.left = deserializeNodeStringKey(node.leftNode) + deserializableNode + } + } + + private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { + return if (node == null){ + null + } else{ + val deserializableNode = BSTNode(key = node.key.toDouble(), value = node.value) + deserializableNode.right = deserializeNodeDoubleKey(node.rightNode) + deserializableNode.left = deserializeNodeDoubleKey(node.leftNode) + deserializableNode + } + } + + private fun deserializeTree(tree: SerializableTree?): BSTree<*, *>?{ + if (tree != null) { + return if (isNumeric(tree.rootNode.key)){ + val rootNode = deserializeNodeDoubleKey(tree.rootNode) + val deserializedTree = BSTree(rootNode?.key, rootNode?.value) + deserializedTree.rootNode = rootNode + deserializedTree.setName(tree.treeName) + deserializedTree + } else{ + val rootNode = deserializeNodeStringKey(tree.rootNode) + val deserializedTree = BSTree(rootNode?.key, rootNode?.value) + deserializedTree.rootNode = rootNode + deserializedTree.setName(tree.treeName) + deserializedTree + } + } + return null + } + + fun getTree(treeName: String): BSTree<*, *>? { + var undeserializedTree:SerializableTree? = null + transaction { + undeserializedTree = findTree(treeName) + + } + return deserializeTree(undeserializedTree) + } +} diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/SerializableTree.kt index b5e00b8..de3fed1 100644 --- a/trees/src/main/kotlin/bst/db/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/SerializableTree.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable class SerializableTree ( var treeName: String, - var rootNode: SerializableNode?, + var rootNode: SerializableNode, ) From d72fdf375c004f2c12b95f0b4d0129376459c9b7 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 24 Apr 2023 23:49:08 +0300 Subject: [PATCH 060/118] feat:add docker compose for lift postgresql db and small fixes --- trees/build.gradle.kts | 1 + trees/src/db/Node.kt | 17 ---- trees/src/db/Nodes.kt | 14 ---- trees/src/db/Tree.kt | 14 ---- trees/src/db/TreeSQLdbApp.kt | 83 ------------------- trees/src/db/Trees.kt | 10 --- .../src/main/kotlin/bst/db/JsonController.kt | 27 ++++-- trees/src/main/kotlin/bst/db/SQLController.kt | 32 +++++-- 8 files changed, 46 insertions(+), 152 deletions(-) delete mode 100644 trees/src/db/Node.kt delete mode 100644 trees/src/db/Nodes.kt delete mode 100644 trees/src/db/Tree.kt delete mode 100644 trees/src/db/TreeSQLdbApp.kt delete mode 100644 trees/src/db/Trees.kt diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index bba28bb..91c0018 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.postgresql:postgresql:42.5.4") } diff --git a/trees/src/db/Node.kt b/trees/src/db/Node.kt deleted file mode 100644 index 91789cf..0000000 --- a/trees/src/db/Node.kt +++ /dev/null @@ -1,17 +0,0 @@ -package db -import org.jetbrains.exposed.dao.* -import org.jetbrains.exposed.dao.id.EntityID - -class Node(id: EntityID): IntEntity(id) { - companion object: IntEntityClass(Nodes) - var key by Nodes.key - var value by Nodes.value - val x by Nodes.x - val y by Nodes.y - var left by Node optionalReferencedOn Nodes.left - var right by Node optionalReferencedOn Nodes.right - var tree by Tree referencedOn Nodes.tree - override fun toString(): String = "Node(key = $key, value=$value, x=$x, y=$y, left=$left, right=$right, tree=$tree)" - -} - diff --git a/trees/src/db/Nodes.kt b/trees/src/db/Nodes.kt deleted file mode 100644 index 5cee74b..0000000 --- a/trees/src/db/Nodes.kt +++ /dev/null @@ -1,14 +0,0 @@ -package db -import org.jetbrains.exposed.dao.id.IntIdTable -import org.jetbrains.exposed.sql.ReferenceOption - -object Nodes : IntIdTable("nodes") { - val key = varchar("key", length = 256) - val value = varchar("value", length = 256) - val x = integer("x_coordinate").nullable() - val y = integer("y_coordinate").nullable() - val left = reference("left", Nodes).nullable() - val right = reference("right", Nodes).nullable() - val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) -} - diff --git a/trees/src/db/Tree.kt b/trees/src/db/Tree.kt deleted file mode 100644 index b52af8f..0000000 --- a/trees/src/db/Tree.kt +++ /dev/null @@ -1,14 +0,0 @@ -package db - -import org.jetbrains.exposed.dao.IntEntity -import org.jetbrains.exposed.dao.IntEntityClass -import org.jetbrains.exposed.dao.id.EntityID - -class Tree(id: EntityID): IntEntity(id){ - companion object: IntEntityClass(Trees) - var name by Trees.name - var rootNode by Node optionalReferencedOn Trees.rootNode - override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" -} - -//fun createTree diff --git a/trees/src/db/TreeSQLdbApp.kt b/trees/src/db/TreeSQLdbApp.kt deleted file mode 100644 index 0d84738..0000000 --- a/trees/src/db/TreeSQLdbApp.kt +++ /dev/null @@ -1,83 +0,0 @@ -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger -import db.Trees -import db.Node -import db.Nodes -import db.Tree -import bst.BSTree -import bst.nodes.BSTNode - -private const val dbPath = "exposed_database.db" -//not finaly сompleted -fun treeSave(tree: BSTree<*, *>){ -// transaction { - val rootNode_ = Node.new { - key=tree.rootNode?.key.toString() - value=tree.rootNode?.value.toString() - } -// val TreeObject = Tree.new { -// name = tree.treeName -// } -// TreeObject.rootNode = rootNode_ -// saveNodes(tree.rootNode, tree.rootNode, TreeObject) -// } -} -//not finaly сompleted -fun saveNodes(node: BSTNode<*, *>?, parentNode:BSTNode<*, *>? = null, tree_: Tree){ - node?.let { - saveNodes(node.left, node, tree_) - saveNodes(node.right, node, tree_) -// transaction { -// if (parentNode == null){ - -// } - - val leftChildNode = Node.new { - key = parentNode?.left?.key.toString() - value = parentNode?.left?.value.toString() - tree = tree_ - } - val rightChildNode = Node.new { - key = parentNode?.right?.key.toString() - value = parentNode?.right?.value.toString() - tree = tree_ - - } - val parentNode_ = Node.new { - key = parentNode?.key.toString() - value = parentNode?.value.toString() - left = leftChildNode - right = rightChildNode - } -// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") - } -} - - -fun main() { - Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") - transaction { - addLogger(StdOutSqlLogger) - SchemaUtils.create(Trees) - SchemaUtils.create(Nodes) - val test_data = BSTree("121", "dgs", "tree_1") - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") - treeSave(test_data) - - val treeObj = Tree.new { - name = "Tree_1" - } - val rootNode = Node.new { - key = "test" - value = "234" - tree = treeObj - } - treeObj.rootNode = rootNode -// treeObj.delete() - } -} \ No newline at end of file diff --git a/trees/src/db/Trees.kt b/trees/src/db/Trees.kt deleted file mode 100644 index cd17c52..0000000 --- a/trees/src/db/Trees.kt +++ /dev/null @@ -1,10 +0,0 @@ -package db - -import db.Nodes -import org.jetbrains.exposed.dao.id.IntIdTable - -object Trees : IntIdTable("trees") { - val name = varchar("name", length = 128) - val rootNode = reference("rootNode", Nodes).nullable() -} - diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/JsonController.kt index 04b3134..e45bea4 100644 --- a/trees/src/main/kotlin/bst/db/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/JsonController.kt @@ -9,16 +9,25 @@ import java.io.FileWriter class JsonController{ fun saveTreeToJson(tree: AVLTree<*, *>){ val gson = Gson() - val writer = FileWriter("${tree.treeName}.json") - val treeToSave = tree - gson.toJson(treeToSave, writer) - writer.close() +// val writer: FileWriter + try { + val writer = FileWriter("${tree.treeName}.json") + gson.toJson(tree, writer) + writer.close() + }catch (e: Exception){ + println("Write error") + } } - fun readFromJson(treeName: String): BSTree<*, *> { + fun readFromJson(treeName: String): BSTree<*, *>? { val gson = Gson() - val reader = FileReader("$treeName.json") - val tree = gson.fromJson(reader, BSTree::class.java) - reader.close() - return tree + return try{ + val reader = FileReader("$treeName.json") + val tree = gson.fromJson(reader, BSTree::class.java) + reader.close() + tree + }catch (e: Exception){ + println("read error") + null + } } } diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index 187af38..78e0765 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -8,11 +8,22 @@ import db.Nodes import db.Tree import org.jetbrains.exposed.sql.transactions.transaction + private const val dbPath = "exposed_database.db" class SQLController { private fun connectDB() { - Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") + Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", + user = "test", password = "test") + } + + private fun deleteTree(treeName: String) { + transaction { + val treeEntity = + Tree.find { (Trees.name eq treeName)} + .firstOrNull() + treeEntity?.delete() + } } private fun serializeNode(node: BSTNode<*, *>?): SerializableNode?{ @@ -51,6 +62,7 @@ class SQLController { fun saveTreeToDB(tree: BSTree<*, *>){ connectDB() + deleteTree(tree.treeName) val serializedTree = serializeTree(tree) transaction { addLogger(StdOutSqlLogger) @@ -138,11 +150,21 @@ class SQLController { } fun getTree(treeName: String): BSTree<*, *>? { - var undeserializedTree:SerializableTree? = null + var deserializedTree:SerializableTree? = null transaction { - undeserializedTree = findTree(treeName) - + deserializedTree = findTree(treeName) } - return deserializeTree(undeserializedTree) + return deserializeTree( deserializedTree) } } +fun main(){ + val test_data = BSTree(121, "dgs") + test_data.insert(110, "dafad") + test_data.insert(118, "adfaf") + test_data.insert(124, "fggsg") + test_data.setName("afefadsf") + val controller = SQLController() +// val serializedTree = controller.serializeTree(test_data) + controller.saveTreeToDB(test_data) + val remTree = controller.getTree("afefadsf") +} \ No newline at end of file From de4844e7c4f631edfcdd7d1ec5cfbb3bdee84c1a Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 21:00:13 +0300 Subject: [PATCH 061/118] fix: Rework tree deserialization logic --- .../src/main/kotlin/bst/db/JsonController.kt | 8 +-- trees/src/main/kotlin/bst/db/SQLController.kt | 56 +++++-------------- trees/src/test/kotlin/bst/BstTest.kt | 1 - 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/JsonController.kt index e45bea4..221a114 100644 --- a/trees/src/main/kotlin/bst/db/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/JsonController.kt @@ -1,15 +1,13 @@ package bst.db import bst.AVLTree -import bst.BSTree import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter class JsonController{ - fun saveTreeToJson(tree: AVLTree<*, *>){ + fun saveTreeToJson(tree: AVLTree){ val gson = Gson() -// val writer: FileWriter try { val writer = FileWriter("${tree.treeName}.json") gson.toJson(tree, writer) @@ -18,11 +16,11 @@ class JsonController{ println("Write error") } } - fun readFromJson(treeName: String): BSTree<*, *>? { + fun readFromJson(treeName: String): AVLTree? { val gson = Gson() return try{ val reader = FileReader("$treeName.json") - val tree = gson.fromJson(reader, BSTree::class.java) + val tree = gson.fromJson(reader, AVLTree()::class.java) reader.close() tree }catch (e: Exception){ diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index 78e0765..a42d989 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -9,8 +9,6 @@ import db.Tree import org.jetbrains.exposed.sql.transactions.transaction -private const val dbPath = "exposed_database.db" - class SQLController { private fun connectDB() { Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", @@ -69,7 +67,7 @@ class SQLController { createTables() val daoTree = Tree.new { if(serializedTree!=null) - name = serializedTree.treeName + name = serializedTree.treeName } daoTree.rootNode = serializedTree?.rootNode?.toNodeDao(daoTree) } @@ -100,71 +98,43 @@ class SQLController { private fun isNumeric(s: String): Boolean { return try { - s.toDouble() + s.toInt() true } catch (e: NumberFormatException) { false } } + - private fun deserializeNodeStringKey(node: SerializableNode?): BSTNode? { + private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { return if (node == null){ null } else{ - - val deserializableNode = BSTNode(key = node.key, value = node.value) - deserializableNode.right = deserializeNodeStringKey(node.rightNode) - deserializableNode.left = deserializeNodeStringKey(node.leftNode) - deserializableNode - } - } - - private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { - return if (node == null){ - null - } else{ - val deserializableNode = BSTNode(key = node.key.toDouble(), value = node.value) + val deserializableNode = BSTNode(key = node.key.toInt(), value = node.value) deserializableNode.right = deserializeNodeDoubleKey(node.rightNode) deserializableNode.left = deserializeNodeDoubleKey(node.leftNode) deserializableNode } } - private fun deserializeTree(tree: SerializableTree?): BSTree<*, *>?{ + private fun deserializeTree(tree: SerializableTree?): BSTree?{ if (tree != null) { - return if (isNumeric(tree.rootNode.key)){ + if (isNumeric(tree.rootNode.key)){ val rootNode = deserializeNodeDoubleKey(tree.rootNode) - val deserializedTree = BSTree(rootNode?.key, rootNode?.value) - deserializedTree.rootNode = rootNode - deserializedTree.setName(tree.treeName) - deserializedTree - } else{ - val rootNode = deserializeNodeStringKey(tree.rootNode) - val deserializedTree = BSTree(rootNode?.key, rootNode?.value) + val deserializedTree:BSTree = BSTree(rootNode?.key, rootNode?.value) deserializedTree.rootNode = rootNode deserializedTree.setName(tree.treeName) - deserializedTree + return deserializedTree } } - return null + return null } - fun getTree(treeName: String): BSTree<*, *>? { + fun getTree(treeName: String): BSTree? { var deserializedTree:SerializableTree? = null transaction { - deserializedTree = findTree(treeName) + deserializedTree = findTree(treeName) } - return deserializeTree( deserializedTree) + return deserializeTree(deserializedTree) } -} -fun main(){ - val test_data = BSTree(121, "dgs") - test_data.insert(110, "dafad") - test_data.insert(118, "adfaf") - test_data.insert(124, "fggsg") - test_data.setName("afefadsf") - val controller = SQLController() -// val serializedTree = controller.serializeTree(test_data) - controller.saveTreeToDB(test_data) - val remTree = controller.getTree("afefadsf") } \ No newline at end of file diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt index 3d34086..e294f13 100644 --- a/trees/src/test/kotlin/bst/BstTest.kt +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -7,7 +7,6 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import bst.BSTree class BstTest { private fun , V> isBst(root: BSTNode?): Boolean { From d1e6d2ae5b0523762aa31a29f65d6a623ac899e1 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 21:21:07 +0300 Subject: [PATCH 062/118] add bat, sh files to up docker container --- .gitattributes | 8 ++++++++ docker-compose.yml | 12 ++++++++++++ postgres.env | 3 +++ start-db.bat | 3 +++ start-db.sh | 4 ++++ 5 files changed, 30 insertions(+) create mode 100644 .gitattributes create mode 100644 docker-compose.yml create mode 100644 postgres.env create mode 100644 start-db.bat create mode 100644 start-db.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a6e97c7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# +# 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 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2cd846e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3" +services: + local-database: + image: "postgres:15.2" + env_file: + - postgres.env + ports: + - "5432:5432" + volumes: + - pg-volume:/var/lib/postgresql/data +volumes: + pg-volume: {} diff --git a/postgres.env b/postgres.env new file mode 100644 index 0000000..ff9a750 --- /dev/null +++ b/postgres.env @@ -0,0 +1,3 @@ +POSTGRES_USER=test +POSTGRES_PASSWORD=test-test +POSTGRES_DB=test \ No newline at end of file diff --git a/start-db.bat b/start-db.bat new file mode 100644 index 0000000..43793b2 --- /dev/null +++ b/start-db.bat @@ -0,0 +1,3 @@ +@echo off +set BASEDIR=%~dp0 +docker-compose --file "%BASEDIR%docker-compose.yml" up diff --git a/start-db.sh b/start-db.sh new file mode 100644 index 0000000..e3a97f1 --- /dev/null +++ b/start-db.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +BASEDIR=$(dirname "$0") +docker-compose --file "${BASEDIR}"/docker-compose.yml up From 0b34f60da65cf93b9e95f6fd7621c8a949d85c03 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 21:28:58 +0300 Subject: [PATCH 063/118] add .gitignore --- .gitignore | 184 ++++++++++++++++++++++++++++++++ app/.DS_Store | Bin 6148 -> 0 bytes app/src/.DS_Store | Bin 6148 -> 0 bytes app/src/main/.DS_Store | Bin 6148 -> 0 bytes app/src/main/kotlin/.DS_Store | Bin 6148 -> 0 bytes gradle/.DS_Store | Bin 6148 -> 0 bytes trees/src/test/.DS_Store | Bin 6148 -> 0 bytes trees/src/test/kotlin/.DS_Store | Bin 6148 -> 0 bytes 8 files changed, 184 insertions(+) create mode 100644 .gitignore delete mode 100644 app/.DS_Store delete mode 100644 app/src/.DS_Store delete mode 100644 app/src/main/.DS_Store delete mode 100644 app/src/main/kotlin/.DS_Store delete mode 100644 gradle/.DS_Store delete mode 100644 trees/src/test/.DS_Store delete mode 100644 trees/src/test/kotlin/.DS_Store diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..395679d --- /dev/null +++ b/.gitignore @@ -0,0 +1,184 @@ +# Created by https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=kotlin,java,gradle,intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Kotlin ### +# Compiled class file + +# Log file + +# BlueJ files + +# Mobile Tools for Java (J2ME) + +# Package Files # + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof +### mac gabage files +.DS_Store +# End of https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij diff --git a/app/.DS_Store b/app/.DS_Store deleted file mode 100644 index 87c7697165a8298384e1bd2e62af01a35fb01b1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!A{#i5S>j@aD;Jh zT>B;b4!qr+fFSLaM%9co`(|fn*6X*{u7`+Jf1LJ+d?NCo1{-ZOe-JZnbU_;IX#tg; zBc&a>q!GoGE<}rC85uBp=Tkys$|%RKJ?&pkL-ekwqVh|wFOpm*HkRlRxOLA0yGbSX z{NhE_Ek<@+5Uj_jWKv%9T)&g+JS(ei_f9pMtv4&H-m16eeT}~BNmRwvxa`NHGt)cP zI#1?yoSbCC$-sa6Nf%X|6~mED$kHJ~&QG%<)swy+7inp8BRAl+z4pNGOs9Lj?sm}I z-<@p-(+}@q@9pl-W^Hd{^WDdz!BzgF(7)_@N#UEevd%b%8yNh~Kci8R>*5lgdGtI= zP#6#fgn{K{fKM!MZF!d{D-#BUfq%q+IUfS3!H}_bXpaswwgdp0-$*O4O)X(^0>+TB zc8C#(a;ZRhYKM<0Fzi)~SaTJ3pjO}y Xi~$T8YlrYa!G9^G@C= zI@kWan)st&G#<8s!6iMNDHVrvI|$FC-lS97JW|Ohh?3qwCq&&ILar{Nq^l+^HBP!i zo$H$kr{EMirSf#zs8uU&ty!N{-0A)v>_)vgn-!d`?cIaZ&O_W!)U%;p;=9zcVQ~&G zFc$SZ`GX`@$sK(2_<0;7Gr$Zm1FOh@J?os}Di+5zF$2uNuNa{7!G#if7E^=z=)j;` z0ALPoEwJ^kKhPxy(6g8tga;x`D$t}#+hR!DIY^U^dC$hD22Hw<`psC!zFFE1McT$g zx;k7)&mfP?05kBJ0qXrACA$BQfA0Tv5D&}%Gq9Qr$XwfRw@{nCTbHV&yVix?KuIVr mHMmHDp|4`Z(p6lBYJuM+1L#>y4Z;KA9|27R56r-yGVlSdPE@Y| diff --git a/app/src/main/.DS_Store b/app/src/main/.DS_Store deleted file mode 100644 index 6da861aba7f2e579e2a3925364895fa6818c65d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5S?wSO({YT3OxqA7Hw@Qh?f}a!K)EHsMN$14UO5-rshxzIqMJkCH{`i z>~4ipy@^Pff!Q~iok_B9!%hYOggXse05t%xPzeha9KI2nCtZ<(@e~n-x<>@bSuF2^ zSOzQ6?D&ri(B5sr5G14!!}9*oM{$h0_n}0+{!(m^O-5#~--1ybP10ubT@;F?jm@%E zwyM^x_b4+j_0#FZ^~cwAbs=ROENnlxj0Rc1wtXU#)Q^(ESS3W^07GuBq9l}=E2l{~ zQMsO;uqsxiU#rh%omR79x4P|l!=4=;pxtSA=ktoSv%7zE-XF$KiF{FS4FbPUC0hm; z@QTLLnx4IJ63gTP{fnGMEF&|(3@`)h!hkv7tm?Wfh}XdkFay8F0G$suDxqUA*QkyT z98?Q{SVFfFwCT5za%_W+!CWKGpa`9cs8fY$F@#P>zir|igSkeX4#G4a!aP}+4n?S^ zDzY5}k8DIw1lL1lcc|8}`W@_uoDyi1msCTF& p6qjrKN;( diff --git a/app/src/main/kotlin/.DS_Store b/app/src/main/kotlin/.DS_Store deleted file mode 100644 index 4873712bcd5ed43ba69a430b8224cac32042ecce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO-sW-5Z!I7O({YT3OxqA7Hw@Qh?iLF!K)EHsMN$34aRKghdq=+&iaS^CH@_K zv%3+BdhsAwW?=SBW@k3c+pwDvLdedv-w>iAgm9pQxe|tNgyu=VNX~llKuK$;+b~uk zo@hCi|G)t4U0wuYD26BT(*7M{X6IdtF2qKKv+>aG@@qJZqw%C(f0MaJJcdbrB_fHS4Hp(jV(w z-%L0qr_`%d=kr#hUUM7m=A!1#_xE78n(f7+Sci8{#FtNi7=|GkAev zt9kNANvx7Pc(Uj$ijf&$2AF}>Wx$?zPI+}pmgko zE~ICWM`nN-SZ08FKS+h{|Kp$ge;vdFGr$b2CIhn2^}8L^X7AQ7RnlGSLT{iX6qg$O kOo5@VV#Lx_T!m_Z-z5X+SxgPW1K}S5O#=_iz@IYk0d;0l%K!iX diff --git a/gradle/.DS_Store b/gradle/.DS_Store deleted file mode 100644 index 6d74a8a66e3ac9a2ae76d03ea04342774a32b691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T32orijpkLXQhxi`E}R@e*S_cr~I2m73V1!I%wcY7V84v%Zi|;_K+l z?pDz1O+;lT%zl&kNwQzUZUz8^H|(teGyuRsC9KqO_(Uj9IwckBnL!l#8Y%Q4JB-y; z7*A!h<2N!u@2&;~WDr1#$J_f;Ax7x4*h5`;(Sp`gc32G3FxJ^mVH!t+yxo2lm6_`7 zT+OLD^Uj5TqY6I{^5MV>vNJk6Rw@oBb`YLK{i54g+*e5+L`gr>1yQe$Dd(qA(o=<} zhDmRrYkd=N>Q24eXpTmkJKHU}w!SfL$-H| diff --git a/trees/src/test/.DS_Store b/trees/src/test/.DS_Store deleted file mode 100644 index 8928e9fc1ec7001f279804729b40923c49c94191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5S?wSO({YT3Oz1(Em~VGh?iLF!K)EHsMN$34UO5-ruI+@IqMJkCH{`i z>~4iny@^Pfftk0Nok=op!)^uuM0?WP1E>IigG!hyq4`E=o^(la){{pR`W_*~XOX%O zA{8t}v*SN9Kx?-S15l7a1ljeYkK!10=R=Er{Y-2y9S`kVzXii694GbqyU67WYwJa) z=#-sX?@>*?#7`#Uwm-V2s|%%~U@`WC%dkJ~RyI#mocLkfAL)d!*T;~Xt1#}VX4l*@8d!sl~@dNr7Ig40EW`G%B23CauyT3W*RVj$;U7PT1*Y< ztpkm20T2u5)`DaDZKND&(Y2Tw#2FM}QV~t6uq}o#>FAd>&b62tH0dC0^C9e$h3!y; zemdS?>TnRQLAJ~QGmvF~dOws3o&Tpl_y0PG4Ku(DtR@4Z(D6ELtj(USOI6ZYD^c%I qNhmHg_?3bKeH3FX9mQ2tE$EjtKy)pp2GN7UKLVNtHq5}EGVlpV8CXLA diff --git a/trees/src/test/kotlin/.DS_Store b/trees/src/test/kotlin/.DS_Store deleted file mode 100644 index ffbb7f5be6822db33d5d6147075e37b515c44754..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5Z!I7O(;SR3OxqA7LBbI#7m6z;MIs8RBB?124hy5)Er77XZ<0+#NW}G z-HlMH7Y~AE24>%8c4ou84Lcde822X84r3N$%m78KnJ|1KxQ@CYHEpQ_a@<4QhKUH_ z3AZEp4-DYh)mg|gmY*bZ=XVGzTOU$nBHAk~vL}7YZ{brCXSw6N^IE;Jvf4D7M$5SN z??vI~K|aa6V0?*BXF?=lH4nn`cvK9n^&^qyL7a}pG9Zpd;Bs{lr;#W;F-fCL#`8US65r9!ZPa1{!uLb+}+xb7Xe3Ws%9`=vONI l3W13%#o&vjxD6@=>^4pSU5%weuz=8yfTV#2V&G31_yoHfQsw{v From 4aa2948154e6e046ed91153f99df12c1375c736b Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 25 Apr 2023 21:44:45 +0300 Subject: [PATCH 064/118] fix: rework neo4j interaction --- trees/src/main/kotlin/bst/db/Neo4j.kt | 28 ---- .../kotlin/bst/db/Neo4j\320\241ontroller.kt" | 130 ++++++++++++++++++ trees/src/main/kotlin/bst/db/SQLController.kt | 3 +- .../main/kotlin/bst/db/SerializableNode.kt | 5 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 34 +---- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 1 + 6 files changed, 137 insertions(+), 64 deletions(-) delete mode 100644 trees/src/main/kotlin/bst/db/Neo4j.kt create mode 100644 "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" diff --git a/trees/src/main/kotlin/bst/db/Neo4j.kt b/trees/src/main/kotlin/bst/db/Neo4j.kt deleted file mode 100644 index f742d53..0000000 --- a/trees/src/main/kotlin/bst/db/Neo4j.kt +++ /dev/null @@ -1,28 +0,0 @@ -package bst.db - -import bst.* -import org.neo4j.ogm.config.Configuration -import org.neo4j.ogm.session.SessionFactory -import java.util.NoSuchElementException - -class Neo4j(config: Configuration) { - private val sessionFactory = SessionFactory(config, "bst") - private val session = sessionFactory.openSession() - - fun saveTree(tree: AbstractBST<*, *, *>) { - session.save(tree) - session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) - } - - fun removeTree(name: String) { - session.query("MATCH (n)-[r *0..]->(m) WHERE n.property = ${name} DETACH DELETE m", mapOf()) - ?: throw NoSuchElementException("No tree with that name has been found") - } - - fun getTree(name: String) = session.queryForObject( - AbstractBST::class.java, "MATCH (n)-[r *1..]-(m) " + - "WHERE n.property = ${name} RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") - - fun getNames() = session.query("MATCH (n: AbstractBST) return n", mapOf()) - -} \ No newline at end of file diff --git "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" new file mode 100644 index 0000000..283b3f1 --- /dev/null +++ "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" @@ -0,0 +1,130 @@ +package bst.db + +import bst.RedBlackTree +import bst.nodes.RBTNode +import bst.nodes.RBTNode.Color +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity +import org.neo4j.ogm.annotation.Relationship +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.session.SessionFactory +import java.util.NoSuchElementException + +@NodeEntity +class TreeEntity ( + var treeName: String = "", + var rootNode: TreeNodeEntity? = null, +) { + @Id + @GeneratedValue + var id: Long? = null +} + +@NodeEntity +class TreeNodeEntity( + val key: String, + val value : String, + val x : Double = 0.0, + val y: Double = 0.0, + var metadata: String? = null, + @Relationship(type = "LEFT") + var left: TreeNodeEntity? = null, + @Relationship(type = "RIGHT") + var right: TreeNodeEntity? = null +) { + @Id + @GeneratedValue + var id: Long? = null +} + +class Neo4j(config: Configuration) { + private val sessionFactory = SessionFactory(config, "bst") + private val session = sessionFactory.openSession() + + private fun RBTNode<*, *>.toSerializableNode(): SerializableNode { + return SerializableNode(key.toString(), value.toString(), 0.0, 0.0, + color.toString(), left?.toSerializableNode(), right?.toSerializableNode()) + } + + private fun RedBlackTree<*, *>.toSerializableTree(): SerializableTree { + return SerializableTree(treeName, rootNode?.toSerializableNode()) + } + + private fun TreeNodeEntity.toSerializableNode(): SerializableNode { + return SerializableNode(key, value, x, y, metadata, left?.toSerializableNode(), right?.toSerializableNode()) + } + + private fun TreeEntity.toSerializableTree(): SerializableTree { + return SerializableTree(treeName, rootNode?.toSerializableNode()) + } + + private fun SerializableNode.toNodeEntity(): TreeNodeEntity { + return TreeNodeEntity(key, value, x, y, metadata, leftNode?.toNodeEntity(), rightNode?.toNodeEntity()) + } + + private fun SerializableTree.toTreeEntity(): TreeEntity { + return TreeEntity(treeName, rootNode?.toNodeEntity()) + } + + private fun deserializeNode(node: SerializableNode?): RBTNode? { + node ?: return null + val rbtNode = RBTNode( + key = node.key.toInt(), + value = node.value, + color = deserializeMetadata(node.metadata) + ) + rbtNode.left = deserializeNode(node.leftNode) + rbtNode.right = deserializeNode(node.rightNode) + return rbtNode + } + + private fun deserializeTree(tree: SerializableTree): RedBlackTree { + val rbTree = RedBlackTree() + rbTree.rootNode = deserializeNode(tree.rootNode) + rbTree.treeName = tree.treeName + return rbTree + } + + private fun deserializeMetadata(metadata: String?): Color { + return when (metadata) { + "RED" -> Color.RED + "BLACK" -> Color.BLACK + else -> throw NoSuchFieldException("Node's color must be either RED or BLACK") + } + } + + fun saveTree(tree: RedBlackTree<*, *>) { + removeTree(tree.treeName) + val entityTree = tree.toSerializableTree().toTreeEntity() + session.save(entityTree) + session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) + } + + fun removeTree(name: String) { + session.query( + "MATCH (n)-[r *0..]->(m) " + + "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name)) + } + + fun loadDeserialized(name: String): RedBlackTree { + val tree = getTree(name) + return deserializeTree(tree.toSerializableTree()) + } + + // Could be useful for underlying logic of GUI + fun loadSerialized(name: String): SerializableTree { + val tree = getTree(name) + return tree.toSerializableTree() + } + + private fun getTree(name: String): TreeEntity { + return session.queryForObject( + TreeEntity::class.java, "MATCH (n)-[r *1..]-(m) " + + "WHERE n.treeName = \$name RETURN n, r, m", mapOf("name" to name)) ?: + throw NoSuchElementException("No tree with that name has been found") + } + + fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()). + flatMap { it.values.map { value -> value.toString() } } +} diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index a42d989..120e3f0 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -79,6 +79,7 @@ class SQLController { this@getSerializedNode.value, this@getSerializedNode.x, this@getSerializedNode.y, + null, this@getSerializedNode.left?.getSerializedNode(treeDao), this@getSerializedNode.right?.getSerializedNode(treeDao), ) @@ -119,7 +120,7 @@ class SQLController { private fun deserializeTree(tree: SerializableTree?): BSTree?{ if (tree != null) { - if (isNumeric(tree.rootNode.key)){ + if (isNumeric(tree.rootNode!!.key)){ val rootNode = deserializeNodeDoubleKey(tree.rootNode) val deserializedTree:BSTree = BSTree(rootNode?.key, rootNode?.value) deserializedTree.rootNode = rootNode diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/SerializableNode.kt index f9a5b8d..2d969af 100644 --- a/trees/src/main/kotlin/bst/db/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/SerializableNode.kt @@ -5,8 +5,9 @@ import kotlinx.serialization.* class SerializableNode( val key: String, val value : String, - val x : Double = 0.0, - val y: Double = 0.0, + var x : Double = 0.0, + var y: Double = 0.0, + var metadata: String? = null, var leftNode: SerializableNode? = null, var rightNode: SerializableNode? = null, ) diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index b49755a..facf88a 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,41 +1,9 @@ package bst.nodes -import org.neo4j.ogm.annotation.GeneratedValue -import org.neo4j.ogm.annotation.Id -import org.neo4j.ogm.annotation.NodeEntity -import org.neo4j.ogm.annotation.PostLoad -import org.neo4j.ogm.annotation.Property -@NodeEntity abstract class BinaryNode, V, Self : BinaryNode>( var key: K, var value: V ) { - @Id - @GeneratedValue - var id: Long? = null var left: Self? = null var right: Self? = null - var x: Double = 0.0 - var y: Double = 0.0 - - @Property(name = "key") - private val clavis = key as Any - - @PostLoad - fun keyInit() { - key = clavis as K - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as BinaryNode<*, *, *> - - if (id != other.id) return false - if (key != other.key) return false - - return true - } - -} +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 21e79f6..a6e6e86 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -6,6 +6,7 @@ class RBTNode, V> ( var color: Color = Color.RED ) : BinaryNode>(key, value) { internal fun child(f: Boolean) = if (f) right else left + enum class Color { RED, BLACK } From cd5e16b3d1dca4fa8dcbdf5c93d28af3af21fce1 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 25 Apr 2023 22:06:20 +0300 Subject: [PATCH 065/118] fix: null safety --- "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" | 5 ++++- trees/src/main/kotlin/bst/db/SerializableNode.kt | 2 +- trees/src/main/kotlin/bst/db/SerializableTree.kt | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" index 283b3f1..a837df9 100644 --- "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" +++ "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" @@ -42,7 +42,10 @@ class Neo4j(config: Configuration) { private val sessionFactory = SessionFactory(config, "bst") private val session = sessionFactory.openSession() - private fun RBTNode<*, *>.toSerializableNode(): SerializableNode { + private fun RBTNode<*, *>?.toSerializableNode(): SerializableNode? { + if (this == null) { + return null + } return SerializableNode(key.toString(), value.toString(), 0.0, 0.0, color.toString(), left?.toSerializableNode(), right?.toSerializableNode()) } diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/SerializableNode.kt index 2d969af..f8a77f8 100644 --- a/trees/src/main/kotlin/bst/db/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/SerializableNode.kt @@ -2,7 +2,7 @@ package bst.db import kotlinx.serialization.* @Serializable -class SerializableNode( +class SerializableNode ( val key: String, val value : String, var x : Double = 0.0, diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/SerializableTree.kt index de3fed1..b5e00b8 100644 --- a/trees/src/main/kotlin/bst/db/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/SerializableTree.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable class SerializableTree ( var treeName: String, - var rootNode: SerializableNode, + var rootNode: SerializableNode?, ) From d51701a7b21f5a1a7b7558d881d66f020d8a0198 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 22:44:24 +0300 Subject: [PATCH 066/118] fix:remove the db files in folders --- .../bst/db/{ => controllers}/JsonController.kt | 2 +- .../db/controllers/Neo4j\320\241ontroller.kt" | 4 +++- .../bst/db/{ => controllers}/SQLController.kt | 16 +++++++++------- .../src/main/kotlin/bst/db/{ => models}/Node.kt | 2 +- .../src/main/kotlin/bst/db/{ => models}/Nodes.kt | 3 +-- .../src/main/kotlin/bst/db/{ => models}/Tree.kt | 2 +- .../src/main/kotlin/bst/db/{ => models}/Trees.kt | 4 ++-- .../{ => serializeClasses}/SerializableNode.kt | 2 +- .../{ => serializeClasses}/SerializableTree.kt | 3 ++- 9 files changed, 21 insertions(+), 17 deletions(-) rename trees/src/main/kotlin/bst/db/{ => controllers}/JsonController.kt (96%) rename "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" => "trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" (97%) rename trees/src/main/kotlin/bst/db/{ => controllers}/SQLController.kt (92%) rename trees/src/main/kotlin/bst/db/{ => models}/Node.kt (96%) rename trees/src/main/kotlin/bst/db/{ => models}/Nodes.kt (93%) rename trees/src/main/kotlin/bst/db/{ => models}/Tree.kt (95%) rename trees/src/main/kotlin/bst/db/{ => models}/Trees.kt (81%) rename trees/src/main/kotlin/bst/db/{ => serializeClasses}/SerializableNode.kt (90%) rename trees/src/main/kotlin/bst/db/{ => serializeClasses}/SerializableTree.kt (65%) diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt similarity index 96% rename from trees/src/main/kotlin/bst/db/JsonController.kt rename to trees/src/main/kotlin/bst/db/controllers/JsonController.kt index 221a114..cdc3f32 100644 --- a/trees/src/main/kotlin/bst/db/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -1,4 +1,4 @@ -package bst.db +package bst.db.controllers import bst.AVLTree import com.google.gson.Gson diff --git "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" similarity index 97% rename from "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" rename to "trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" index a837df9..6442c7f 100644 --- "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" +++ "b/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" @@ -1,6 +1,8 @@ -package bst.db +package bst.db.controllers import bst.RedBlackTree +import bst.db.serializeClasses.SerializableNode +import bst.db.serializeClasses.SerializableTree import bst.nodes.RBTNode import bst.nodes.RBTNode.Color import org.neo4j.ogm.annotation.GeneratedValue diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt similarity index 92% rename from trees/src/main/kotlin/bst/db/SQLController.kt rename to trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 120e3f0..e779295 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -1,11 +1,13 @@ -package bst.db +package bst.db.controllers import bst.BSTree +import bst.db.serializeClasses.SerializableNode +import bst.db.serializeClasses.SerializableTree import bst.nodes.BSTNode -import db.Node +import bst.db.models.Node import org.jetbrains.exposed.sql.* -import db.Trees -import db.Nodes -import db.Tree +import bst.db.models.Trees +import bst.db.models.Nodes +import bst.db.models.Tree import org.jetbrains.exposed.sql.transactions.transaction @@ -88,7 +90,7 @@ class SQLController { private fun findTree(treeName: String): SerializableTree?{ connectDB() - val treeDAO = Tree.find{Trees.name eq treeName}.firstOrNull() ?: return null + val treeDAO = Tree.find{ Trees.name eq treeName}.firstOrNull() ?: return null return treeDAO.rootNode?.getSerializedNode(treeDAO)?.let { SerializableTree( treeName, @@ -132,7 +134,7 @@ class SQLController { } fun getTree(treeName: String): BSTree? { - var deserializedTree:SerializableTree? = null + var deserializedTree: SerializableTree? = null transaction { deserializedTree = findTree(treeName) } diff --git a/trees/src/main/kotlin/bst/db/Node.kt b/trees/src/main/kotlin/bst/db/models/Node.kt similarity index 96% rename from trees/src/main/kotlin/bst/db/Node.kt rename to trees/src/main/kotlin/bst/db/models/Node.kt index 15727d6..4606481 100644 --- a/trees/src/main/kotlin/bst/db/Node.kt +++ b/trees/src/main/kotlin/bst/db/models/Node.kt @@ -1,4 +1,4 @@ -package db +package bst.db.models import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.dao.id.EntityID diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/models/Nodes.kt similarity index 93% rename from trees/src/main/kotlin/bst/db/Nodes.kt rename to trees/src/main/kotlin/bst/db/models/Nodes.kt index fca7eb1..78c51fd 100644 --- a/trees/src/main/kotlin/bst/db/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/models/Nodes.kt @@ -1,5 +1,4 @@ -package db -import db.Trees.default +package bst.db.models import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption diff --git a/trees/src/main/kotlin/bst/db/Tree.kt b/trees/src/main/kotlin/bst/db/models/Tree.kt similarity index 95% rename from trees/src/main/kotlin/bst/db/Tree.kt rename to trees/src/main/kotlin/bst/db/models/Tree.kt index b52af8f..246e3e6 100644 --- a/trees/src/main/kotlin/bst/db/Tree.kt +++ b/trees/src/main/kotlin/bst/db/models/Tree.kt @@ -1,4 +1,4 @@ -package db +package bst.db.models import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass diff --git a/trees/src/main/kotlin/bst/db/Trees.kt b/trees/src/main/kotlin/bst/db/models/Trees.kt similarity index 81% rename from trees/src/main/kotlin/bst/db/Trees.kt rename to trees/src/main/kotlin/bst/db/models/Trees.kt index df464e5..8502d91 100644 --- a/trees/src/main/kotlin/bst/db/Trees.kt +++ b/trees/src/main/kotlin/bst/db/models/Trees.kt @@ -1,6 +1,6 @@ -package db +package bst.db.models -import db.Nodes +import bst.db.models.Nodes import org.jetbrains.exposed.dao.id.IntIdTable object Trees : IntIdTable("trees") { diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt similarity index 90% rename from trees/src/main/kotlin/bst/db/SerializableNode.kt rename to trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt index f8a77f8..5baa597 100644 --- a/trees/src/main/kotlin/bst/db/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt @@ -1,4 +1,4 @@ -package bst.db +package bst.db.serializeClasses import kotlinx.serialization.* @Serializable diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt similarity index 65% rename from trees/src/main/kotlin/bst/db/SerializableTree.kt rename to trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt index b5e00b8..aabb3ba 100644 --- a/trees/src/main/kotlin/bst/db/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt @@ -1,5 +1,6 @@ -package bst.db +package bst.db.serializeClasses +import bst.db.serializeClasses.SerializableNode import kotlinx.serialization.Serializable @Serializable From cd176fd66455762856820f89925a919154ba5f30 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 23:40:07 +0300 Subject: [PATCH 067/118] fix warning associated with sf4j --- trees/build.gradle.kts | 5 +++-- trees/src/main/kotlin/bst/db/controllers/SQLController.kt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 91c0018..8e1edb2 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -19,8 +19,10 @@ dependencies { // Use the JUnit 5 integration. testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + implementation ("io.github.microutils:kotlin-logging-jvm:2.0.11") - implementation("org.slf4j", "slf4j-simple", "1.7.29") + + implementation("org.slf4j", "slf4j-simple", "2.0.0") implementation("org.neo4j:neo4j-ogm-core:4.0.5") @@ -36,7 +38,6 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.41.2.1") - implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.postgresql:postgresql:42.5.4") diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index e779295..ffb33e1 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -140,4 +140,4 @@ class SQLController { } return deserializeTree(deserializedTree) } -} \ No newline at end of file +} From e76b56c3edad6895241f7e0da6cd77b048cc0a7d Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Wed, 26 Apr 2023 00:09:46 +0300 Subject: [PATCH 068/118] feat: Initialize dependencies for UI --- app/build.gradle.kts | 22 ++++++++-------------- app/src/main/kotlin/app/App.kt | 10 ++++++++++ app/src/main/kotlin/app/view/MainView.kt | 10 ++++++++++ settings.gradle.kts | 9 --------- 4 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 app/src/main/kotlin/app/App.kt create mode 100644 app/src/main/kotlin/app/view/MainView.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4198206..7d3d9b4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,23 +1,22 @@ 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. + id("org.openjfx.javafxplugin") version "0.0.8" application } +javafx { + version = "11.0.2" + modules("javafx.controls") +} + 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") - + implementation("no.tornado:tornadofx:1.7.20") implementation(project(":trees")) } @@ -25,8 +24,3 @@ application { // Define the main class for the application. mainClass.set("app.AppKt") } - -tasks.named("test") { - // Use JUnit Platform for unit tests. - useJUnitPlatform() -} diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt new file mode 100644 index 0000000..75ce792 --- /dev/null +++ b/app/src/main/kotlin/app/App.kt @@ -0,0 +1,10 @@ +package app + +import app.view.MainView +import tornadofx.* + +class MainApp: App(MainView::class) + +fun main() { + launch() +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/MainView.kt b/app/src/main/kotlin/app/view/MainView.kt new file mode 100644 index 0000000..5255fa2 --- /dev/null +++ b/app/src/main/kotlin/app/view/MainView.kt @@ -0,0 +1,10 @@ +package app.view + +import tornadofx.* + +class MainView: View("Trees") { + override val root = vbox { + button("Hello, World!") + label("Waiting") + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index fbf2455..f368ad1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,12 +1,3 @@ -/* - * 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-3" include("app") include("trees") From 8fe29614e16bb3e426baf9589a5bac681e181ad4 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 26 Apr 2023 10:10:00 +0300 Subject: [PATCH 069/118] fix readme and add setName method to all trees --- README.md | 87 +++++++++++-------- trees/src/main/kotlin/bst/AVLTree.kt | 10 ++- trees/src/main/kotlin/bst/RedBlackTree.kt | 13 ++- .../db/controllers/Neo4j\320\241ontroller.kt" | 1 + 4 files changed, 70 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index c07fe87..6699035 100644 --- a/README.md +++ b/README.md @@ -2,61 +2,72 @@ > An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) -![Junit](https://img.shields.io/badge/-Junit-525A162?&style=for-the-badge) +![Junit](https://img.shields.io/badge/Tests-Junit-green) +![Postgresql](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white) +![Docker](https://img.shields.io/badge/Docker-316192?style=for-the-badge&logo=Docker&logoColor=white) ## :package: Getting started -Firstly, you need to clone repository - -```sh -https://github.com/spbu-coding-2022/trees-3.git -``` To build the library run ```sh ./gradlew build ``` -## Using BinarySearchTree +To run postgresql with docker: +``` +./run-db.sh +``` +or +``` +./run-db.bat +``` + +## Using Binary search tree Any `Comparable` data can be stored in trees. -You can start from create bst by: +Example: ```kotlin import bst.BSTree - val test_data = BSTree("121", "dgs", "tree_1") - //also we support add and remove elements from bst. You can do this by: - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") + val testData = BSTree(121, "dgs") + testData.insert(110, "dafad") + testData.insert(118, "adfaf") + testData.insert(124, "fggsg") ``` +BSTree constructor gets two params: key and value. +insert method also gets same params and add node with key and value in tree. +Method setName allows you to set name of tree, as argument it takes the string. -find or remove element from tree: +Find or remove element from tree: ```kotlin - test_data.remove("124") - test_data.find("118") + testData.remove(124) + testData.find(118) ``` -Same operations support RBT and AVL trees. +As a parameter to the find and remove methods take a key-value node + +Same operations support red black tree and AVL tree. -BinarySearchTree supports save tree object to json file, and up tree from json. +AVL tree supports conservation tree object to json file, and receiving tree from json. For example: ```kotlin - test_data.saveTreeToJson() - readFromJson("tree_1.json") + val test = AVLTree(1231, "afea") + test.insert(2123, "adf") + test.insert(2123, "adf") + test.setName("test_1") + val control = JsonController() + control.saveTreeToJson(test) + println(control.readFromJson("test_1")?.treeName) ``` -In development saving nodes to databases: neo4j and sql. - -To save BST in SQL database we propose you to use models in directory main/db. -for example: -```kotlin - val treeObj = Tree.new { - name = "Tree_1" - } - val rootNode = Node.new { - key = "test" - value = "234" - tree = treeObj - } - treeObj.rootNode = rootNode -``` - -## Storing BSTs -`bst` provides \ No newline at end of file +Also you can save binary search tree to sql database: +``` + val test_data = BSTree(121, "dgs") + test_data.insert(110, "dafad") + test_data.insert(118, "adfaf") + test_data.insert(124, "fggsg") + test_data.setName("afefadsf") + val controller = SQLController() + controller.saveTreeToDB(test_data) + val remTree = controller.getTree("afefadsf") +``` +And you can save red black tree to neo4j database +``` +``` \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index e135fd9..3153ca4 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -3,9 +3,17 @@ package bst import bst.nodes.AVLNode import kotlin.math.max -class AVLTree, V> : BalancingTree>() { +class AVLTree, V>(@Transient val key: K? = null, @Transient val value: V? = null) : BalancingTree>() { override fun initNode(key: K, value: V): AVLNode = AVLNode(key, value) + init { + if (key != null && value != null) { + rootNode = initNode(key, value) + } + } + fun setName(treeName: String){ + this.treeName = treeName + } override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { if (node == null) return initNode(key, value) if (key < node.key) { diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 680b9dd..52789d4 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -1,8 +1,17 @@ package bst - import bst.nodes.RBTNode -class RedBlackTree, V> : BalancingTree>() { +class RedBlackTree, V>(@Transient val key: K? = null, @Transient val value: V? = null) : BalancingTree>() { + + init { + if (key != null && value != null) { + rootNode = initNode(key, value) + } + } + + fun setName(treeName: String){ + this.treeName = treeName + } private fun isRed(node: RBTNode?): Boolean { return node != null && node.color == RBTNode.Color.RED } diff --git "a/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" index 6442c7f..96e9261 100644 --- "a/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" +++ "b/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" @@ -133,3 +133,4 @@ class Neo4j(config: Configuration) { fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()). flatMap { it.values.map { value -> value.toString() } } } + From f83512fbfa14c7d49ff9f06bd98e6033df944f9b Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 26 Apr 2023 14:06:41 +0300 Subject: [PATCH 070/118] fix:fix readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6699035..79ee016 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) ![Junit](https://img.shields.io/badge/Tests-Junit-green) ![Postgresql](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white) +![Neo4j](https://img.shields.io/badge/Neo4j-008CC1?style=for-the-badge&logo=neo4j&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-316192?style=for-the-badge&logo=Docker&logoColor=white) ## :package: Getting started To build the library run From c52816b7a33f8d4ed58b644b0e56e3c257f487e2 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Thu, 27 Apr 2023 20:02:42 +0300 Subject: [PATCH 071/118] feat: add the functionality of switching between views for different trees --- app/build.gradle.kts | 6 ++--- app/src/main/kotlin/app/App.kt | 11 ++++++++- app/src/main/kotlin/app/view/MainView.kt | 15 ++++++++---- .../kotlin/app/view/treeView/AVLTreeView.kt | 16 +++++++++++++ .../app/view/treeView/BinarySearchTreeView.kt | 17 ++++++++++++++ .../app/view/treeView/RedBlackTreeView.kt | 23 +++++++++++++++++++ 6 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 app/src/main/kotlin/app/view/treeView/AVLTreeView.kt create mode 100644 app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt create mode 100644 app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7d3d9b4..208bbfa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,12 +1,12 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.8.10" - id("org.openjfx.javafxplugin") version "0.0.8" + id("org.openjfx.javafxplugin") version "0.0.12" application } javafx { - version = "11.0.2" - modules("javafx.controls") + version = "17" + modules ( "javafx.controls", "javafx.fxml" ) } repositories { diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt index 75ce792..2af3768 100644 --- a/app/src/main/kotlin/app/App.kt +++ b/app/src/main/kotlin/app/App.kt @@ -1,9 +1,18 @@ package app import app.view.MainView +import javafx.stage.Stage import tornadofx.* -class MainApp: App(MainView::class) +class MainApp: App(MainView::class){ + override fun start(stage: Stage) { + with(stage){ + width = 600.0 + height = 400.0 + } + super.start(stage) + } +} fun main() { launch() diff --git a/app/src/main/kotlin/app/view/MainView.kt b/app/src/main/kotlin/app/view/MainView.kt index 5255fa2..751c32a 100644 --- a/app/src/main/kotlin/app/view/MainView.kt +++ b/app/src/main/kotlin/app/view/MainView.kt @@ -1,10 +1,15 @@ package app.view - +import app.view.treeView.BinarySearchTreeView import tornadofx.* -class MainView: View("Trees") { - override val root = vbox { - button("Hello, World!") - label("Waiting") +class MainView: View(){ + private val tree = BinarySearchTreeView() + override val root = borderpane { + center{ + add(tree) + } + left{ + button("reset") + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt new file mode 100644 index 0000000..c2a8bf9 --- /dev/null +++ b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt @@ -0,0 +1,16 @@ +package app.view.treeView +import tornadofx.* +class AVLTreeView: View() { + override val root = vbox { + button("Binary Search Tree") { + action { + replaceWith() + } + } + button("Red Black Tree") { + action { + replaceWith() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt new file mode 100644 index 0000000..2224fc7 --- /dev/null +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -0,0 +1,17 @@ +package app.view.treeView +import tornadofx.* + +class BinarySearchTreeView: View() { + override val root = vbox { + button("AVL Tree") { + action { + replaceWith() + } + } + button("Red Black Tree") { + action { + replaceWith() + } + } + } +} diff --git a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt new file mode 100644 index 0000000..787f2e6 --- /dev/null +++ b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt @@ -0,0 +1,23 @@ +package app.view.treeView +import app.view.treeView.AVLTreeView +import app.view.treeView.BinarySearchTreeView +import tornadofx.View +import tornadofx.action +import tornadofx.button +import tornadofx.vbox + +class RedBlackTreeView: View() { + override val root = vbox { + button("AVL Tree") { + action { + replaceWith() + } + } + + button("Binary Search Tree") { + action { + replaceWith() + } + } + } +} \ No newline at end of file From 62cfba7612856ab12789c6b6cfa0021dd5b16135 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Sat, 29 Apr 2023 14:25:26 +0300 Subject: [PATCH 072/118] feat: Add the display of the binary search tree, add the ability to insert a node and clear the tree. --- app/src/main/kotlin/app/App.kt | 13 +--- .../kotlin/app/controller/BSTController.kt | 68 +++++++++++++++++++ .../kotlin/app/view/AdditionErrorFragment.kt | 8 +++ app/src/main/kotlin/app/view/MainView.kt | 13 ++-- .../kotlin/app/view/treeView/AVLTreeView.kt | 4 +- .../app/view/treeView/BinarySearchTreeView.kt | 51 +++++++++++++- .../app/view/treeView/RedBlackTreeView.kt | 11 ++- trees/src/main/kotlin/bst/BSTree.kt | 34 ++-------- .../bst/db/controllers/SQLController.kt | 10 +++ 9 files changed, 154 insertions(+), 58 deletions(-) create mode 100644 app/src/main/kotlin/app/controller/BSTController.kt create mode 100644 app/src/main/kotlin/app/view/AdditionErrorFragment.kt diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt index 2af3768..a717fbe 100644 --- a/app/src/main/kotlin/app/App.kt +++ b/app/src/main/kotlin/app/App.kt @@ -1,19 +1,10 @@ package app import app.view.MainView -import javafx.stage.Stage import tornadofx.* - -class MainApp: App(MainView::class){ - override fun start(stage: Stage) { - with(stage){ - width = 600.0 - height = 400.0 - } - super.start(stage) - } +class MyApp : App(MainView::class){ } fun main() { - launch() + launch() } \ No newline at end of file diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt new file mode 100644 index 0000000..c222cf8 --- /dev/null +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -0,0 +1,68 @@ +package app.controller +import bst.BSTree +import bst.nodes.BSTNode +import javafx.scene.layout.Pane +import javafx.scene.paint.Color +import javafx.scene.shape.Circle +import javafx.scene.shape.Line + +import tornadofx.Controller +import javafx.scene.control.Label + +class BSTController: Controller(){ + fun isNumeric(s: String): Boolean { + return try { + s.toInt() + true + } catch (e: NumberFormatException) { + false + } + } + fun insertNode(tree:BSTree, treePane: Pane, key: Int, value:String){ + tree.insert(key, value) + drawTree(tree, treePane) + } + fun clearTree(tree:BSTree, treePane: Pane) { + tree.clear() + treePane.children.clear() + } + + //make here not null check + private fun drawTree(tree:BSTree, treePane: Pane) { + treePane.children.clear() + if (tree.getRoot() != null) { + drawNode(tree.getRoot()!!, treePane, treePane.width / 2.0, 50.0, treePane.width / 4.0) + } + } + + private fun drawNode(node: BSTNode, treePane: Pane, x: Double, y: Double, offsetX: Double) { + val circleRadius = 20.0 + val circle = Circle(circleRadius) + circle.centerX = x + circle.centerY = y + circle.fill = Color.WHITE + circle.stroke = Color.BLACK + val nodeLabel = Label(node.key.toString()) + nodeLabel.layoutX = circle.centerX - (circle.radius / 3) + nodeLabel.layoutY = circle.centerY - (circle.radius / 3) + treePane.children.addAll(circle,nodeLabel ) + + if (node.left != null) { + val leftX = x - offsetX + val leftY = y + 50 + val leftLine = Line(x, y + circleRadius, leftX, leftY - circleRadius) + treePane.children.add(leftLine) + drawNode(node.left!!, treePane, leftX, leftY, offsetX / 2.0) + } + + if (node.right != null) { + val rightX = x + offsetX + val rightY = y + 50 + val rightLine = Line(x, y + circleRadius, rightX, rightY - circleRadius) + treePane.children.add(rightLine) + drawNode(node.right!!,treePane, rightX, rightY, offsetX / 2.0) + } + } +} + + diff --git a/app/src/main/kotlin/app/view/AdditionErrorFragment.kt b/app/src/main/kotlin/app/view/AdditionErrorFragment.kt new file mode 100644 index 0000000..d6fd567 --- /dev/null +++ b/app/src/main/kotlin/app/view/AdditionErrorFragment.kt @@ -0,0 +1,8 @@ +package app.view + +import tornadofx.Fragment +import tornadofx.label + +class AdditionErrorFragment: Fragment() { + override val root = label("Addition error") +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/MainView.kt b/app/src/main/kotlin/app/view/MainView.kt index 751c32a..0bd58cb 100644 --- a/app/src/main/kotlin/app/view/MainView.kt +++ b/app/src/main/kotlin/app/view/MainView.kt @@ -3,13 +3,8 @@ import app.view.treeView.BinarySearchTreeView import tornadofx.* class MainView: View(){ - private val tree = BinarySearchTreeView() - override val root = borderpane { - center{ - add(tree) - } - left{ - button("reset") - } + val tree: BinarySearchTreeView by inject() + override val root = vbox { + add(tree) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt index c2a8bf9..e1175b3 100644 --- a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt @@ -4,12 +4,12 @@ class AVLTreeView: View() { override val root = vbox { button("Binary Search Tree") { action { - replaceWith() + replaceWith(BinarySearchTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) } } button("Red Black Tree") { action { - replaceWith() + replaceWith(RedBlackTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) } } } diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index 2224fc7..7a5416b 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -1,17 +1,62 @@ package app.view.treeView import tornadofx.* +import app.controller.BSTController +import app.view.AdditionErrorFragment +import bst.BSTree +import javafx.beans.property.SimpleStringProperty +import javafx.scene.layout.Pane +import javafx.stage.StageStyle -class BinarySearchTreeView: View() { +class BinarySearchTreeView : View() { + private val controller: BSTController by inject() + private val tree = BSTree() + private val treePane = Pane() + val key = SimpleStringProperty() + val value = SimpleStringProperty() override val root = vbox { + hbox { + button("Clear") { + action { controller.clearTree(tree, treePane) } + } + form { + fieldset { + field("Key input") { + textfield(key) + } + field("Value input") { + textfield(value) + } + + button("Add Node") { + action { + if (controller.isNumeric(key.value)){ + controller.insertNode(tree, treePane, key.value.toInt(), value.value) + } + else{ + find().openModal(stageStyle = StageStyle.UTILITY) + } + key.value = "" + value.value = "" + } + } + } + } + } button("AVL Tree") { action { - replaceWith() + replaceWith(AVLTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) } } button("Red Black Tree") { action { - replaceWith() + replaceWith(RedBlackTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) } } + this += treePane + treePane.apply { + minWidth = 600.0 + minHeight = 400.0 + style = "-fx-border-color: black;" + } } } diff --git a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt index 787f2e6..82c754c 100644 --- a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt @@ -1,22 +1,21 @@ package app.view.treeView import app.view.treeView.AVLTreeView import app.view.treeView.BinarySearchTreeView -import tornadofx.View -import tornadofx.action -import tornadofx.button -import tornadofx.vbox +import tornadofx.* class RedBlackTreeView: View() { override val root = vbox { button("AVL Tree") { action { - replaceWith() + replaceWith(AVLTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) + } } button("Binary Search Tree") { action { - replaceWith() + replaceWith(BinarySearchTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) + } } } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index c62b7a9..4432969 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,11 +1,7 @@ package bst import bst.nodes.BSTNode -import com.google.gson.Gson -import java.io.FileReader -import java.io.FileWriter - -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { +class BSTree, V>(val key: K? = null, val value: V? = null): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) init { if (key != null && value != null) { @@ -16,27 +12,11 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val fun setName(treeName: String){ this.treeName = treeName } + fun clear(){ + this.rootNode = null + } - - -} - - - -fun main(){ - val test_data = BSTree("121", "dgs") - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") - test_data.insert("127", "fggsg") - test_data.insert("123", "fggsg") - test_data.setName("etst") -// test_data.saveTreeToJson() -// test_data.saveTree() -// readFromJson("tree_1.json") -// test_data.save_node(test_data.rootNode) -// val json = Gson().toJson(test_data) -// println(json) -// val ret_obj = Gson().fromJson, Any>>(json, BSTree::class.java) - + fun getRoot(): BSTNode?{ + return this.rootNode + } } diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index ffb33e1..1aa090d 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -141,3 +141,13 @@ class SQLController { return deserializeTree(deserializedTree) } } + +fun main() { + val controller = SQLController() + val tree = BSTree(1, "A") + tree.insert(2, "B") + tree.setName("asd") + controller.saveTreeToDB(tree) + val loadedTree = controller.getTree("asd") + loadedTree?.insert(3, "F") +} From 5c1f4502c9cb24c28ca8eee706f78fac0bddd704 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Sun, 30 Apr 2023 14:13:55 +0300 Subject: [PATCH 073/118] feat: add mapping trees list from database --- .../kotlin/app/controller/BSTController.kt | 18 ++++++++++++++++-- .../app/view/treeView/BinarySearchTreeView.kt | 17 +++++++++++++++-- .../kotlin/bst/db/controllers/SQLController.kt | 15 +++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index c222cf8..e16afb8 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -8,6 +8,10 @@ import javafx.scene.shape.Line import tornadofx.Controller import javafx.scene.control.Label +import bst.db.controllers.SQLController +import javafx.beans.property.ReadOnlyListProperty +import javafx.collections.FXCollections.observableArrayList +import javafx.collections.ObservableList class BSTController: Controller(){ fun isNumeric(s: String): Boolean { @@ -30,8 +34,9 @@ class BSTController: Controller(){ //make here not null check private fun drawTree(tree:BSTree, treePane: Pane) { treePane.children.clear() - if (tree.getRoot() != null) { - drawNode(tree.getRoot()!!, treePane, treePane.width / 2.0, 50.0, treePane.width / 4.0) + val root = tree.getRoot() + if (root != null) { + drawNode(root, treePane, treePane.width / 2.0, 50.0, treePane.width / 4.0) } } @@ -63,6 +68,15 @@ class BSTController: Controller(){ drawNode(node.right!!,treePane, rightX, rightY, offsetX / 2.0) } } + fun getTreesList(): ObservableList? { + val controller = SQLController() + val treeNames = controller.getAllTrees() + val values = observableArrayList() + treeNames.forEach{ + values.add(it) + } + return values + } } diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index 7a5416b..9a5d0a1 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -6,6 +6,7 @@ import bst.BSTree import javafx.beans.property.SimpleStringProperty import javafx.scene.layout.Pane import javafx.stage.StageStyle +//import tornadofx.Stylesheet.Companion.selected class BinarySearchTreeView : View() { private val controller: BSTController by inject() @@ -13,12 +14,19 @@ class BinarySearchTreeView : View() { private val treePane = Pane() val key = SimpleStringProperty() val value = SimpleStringProperty() + val trees = controller.getTreesList() + val selected = stringProperty("") override val root = vbox { hbox { button("Clear") { action { controller.clearTree(tree, treePane) } } + listview(trees).onUserSelect{ + selected.set(it) + log.info { selected.value } + } form { + fieldset { field("Key input") { textfield(key) @@ -29,8 +37,13 @@ class BinarySearchTreeView : View() { button("Add Node") { action { - if (controller.isNumeric(key.value)){ - controller.insertNode(tree, treePane, key.value.toInt(), value.value) + if ( key.value != null && value.value != null){ + if (controller.isNumeric(key.value)){ + controller.insertNode(tree, treePane, key.value.toInt(), value.value) + } + else{ + find().openModal(stageStyle = StageStyle.UTILITY) + } } else{ find().openModal(stageStyle = StageStyle.UTILITY) diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 1aa090d..6dad42d 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -140,6 +140,21 @@ class SQLController { } return deserializeTree(deserializedTree) } + + fun getAllTrees(): List { + val notes = mutableListOf() + connectDB() + transaction { + Trees.selectAll().forEach { + val name = it[Trees.name] + notes.add(name) + } + } + + return notes + } + + } fun main() { From 09e0cd6aebb1f0ff49706c0605fee5e4dca15b54 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Sun, 30 Apr 2023 16:31:05 +0300 Subject: [PATCH 074/118] Add display of the tree to choose from the list --- app/build.gradle.kts | 2 +- .../kotlin/app/controller/BSTController.kt | 14 ++++- .../app/view/treeView/BinarySearchTreeView.kt | 53 +++++++++++++------ .../bst/db/controllers/SQLController.kt | 2 +- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 208bbfa..1007c33 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } javafx { - version = "17" + version = "18" modules ( "javafx.controls", "javafx.fxml" ) } diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index e16afb8..475f7bf 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -9,9 +9,12 @@ import javafx.scene.shape.Line import tornadofx.Controller import javafx.scene.control.Label import bst.db.controllers.SQLController -import javafx.beans.property.ReadOnlyListProperty import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList +import javafx.scene.control.ListCell +import javafx.scene.input.MouseButton +import tornadofx.View +import tornadofx.listview class BSTController: Controller(){ fun isNumeric(s: String): Boolean { @@ -27,12 +30,14 @@ class BSTController: Controller(){ drawTree(tree, treePane) } fun clearTree(tree:BSTree, treePane: Pane) { + val controller = SQLController() +// controller.deleteTree(tree.treeName) tree.clear() treePane.children.clear() } //make here not null check - private fun drawTree(tree:BSTree, treePane: Pane) { + fun drawTree(tree:BSTree, treePane: Pane) { treePane.children.clear() val root = tree.getRoot() if (root != null) { @@ -77,6 +82,11 @@ class BSTController: Controller(){ } return values } + fun getTreeFromDB(name: String): BSTree?{ + val controller = SQLController() + val tree = controller.getTree(name) + return tree + } } diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index 9a5d0a1..cb7e45b 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -4,26 +4,47 @@ import app.controller.BSTController import app.view.AdditionErrorFragment import bst.BSTree import javafx.beans.property.SimpleStringProperty +import javafx.scene.control.ListCell +import javafx.scene.control.ListView +import javafx.scene.input.MouseButton import javafx.scene.layout.Pane import javafx.stage.StageStyle -//import tornadofx.Stylesheet.Companion.selected +import javafx.util.Callback class BinarySearchTreeView : View() { private val controller: BSTController by inject() - private val tree = BSTree() + private var tree = BSTree() private val treePane = Pane() - val key = SimpleStringProperty() - val value = SimpleStringProperty() - val trees = controller.getTreesList() - val selected = stringProperty("") + private val key = SimpleStringProperty() + private val value = SimpleStringProperty() + private var trees = controller.getTreesList() + var selectedItem: String? by singleAssign() + override val root = vbox { hbox { - button("Clear") { - action { controller.clearTree(tree, treePane) } + combobox { + this@BinarySearchTreeView.trees?.let { items.addAll(it) } + selectionModel.selectedItemProperty().addListener { _, _, newValue -> + this@BinarySearchTreeView.selectedItem = newValue + } } - listview(trees).onUserSelect{ - selected.set(it) - log.info { selected.value } + + button("Select") { + action { + println("Selected item: $selectedItem") + val loadedTree = selectedItem?.let { controller.getTreeFromDB(it) } + if (loadedTree != null) { + tree = loadedTree + controller.drawTree(tree, treePane) + } + } + } + + button("Clear") { + action { + controller.clearTree(tree, treePane) + + } } form { @@ -37,15 +58,13 @@ class BinarySearchTreeView : View() { button("Add Node") { action { - if ( key.value != null && value.value != null){ - if (controller.isNumeric(key.value)){ + if (key.value != null && value.value != null) { + if (controller.isNumeric(key.value)) { controller.insertNode(tree, treePane, key.value.toInt(), value.value) - } - else{ + } else { find().openModal(stageStyle = StageStyle.UTILITY) } - } - else{ + } else { find().openModal(stageStyle = StageStyle.UTILITY) } key.value = "" diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 6dad42d..50da2e1 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -17,7 +17,7 @@ class SQLController { user = "test", password = "test") } - private fun deleteTree(treeName: String) { + fun deleteTree(treeName: String) { transaction { val treeEntity = Tree.find { (Trees.name eq treeName)} From 1a3e9db451d459ba34d333bb1732c463ad3e525c Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Sun, 30 Apr 2023 19:49:38 +0300 Subject: [PATCH 075/118] feat: Add deletion of BST from DB and list of trees by click, little trees refactoring --- .../kotlin/app/controller/BSTController.kt | 10 +++--- .../kotlin/app/view/AdditionErrorFragment.kt | 8 ----- .../app/view/treeView/BinarySearchTreeView.kt | 32 +++++++++---------- start-db.sh | 0 trees/src/main/kotlin/bst/AVLTree.kt | 4 +-- trees/src/main/kotlin/bst/AbstractBST.kt | 12 ++++++- trees/src/main/kotlin/bst/BSTree.kt | 11 ------- trees/src/main/kotlin/bst/RedBlackTree.kt | 3 -- .../bst/db/controllers/JsonController.kt | 8 ++--- .../bst/db/controllers/SQLController.kt | 2 +- 10 files changed, 38 insertions(+), 52 deletions(-) delete mode 100644 app/src/main/kotlin/app/view/AdditionErrorFragment.kt mode change 100644 => 100755 start-db.sh diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index 475f7bf..8c430fc 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -11,10 +11,6 @@ import javafx.scene.control.Label import bst.db.controllers.SQLController import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList -import javafx.scene.control.ListCell -import javafx.scene.input.MouseButton -import tornadofx.View -import tornadofx.listview class BSTController: Controller(){ fun isNumeric(s: String): Boolean { @@ -31,7 +27,6 @@ class BSTController: Controller(){ } fun clearTree(tree:BSTree, treePane: Pane) { val controller = SQLController() -// controller.deleteTree(tree.treeName) tree.clear() treePane.children.clear() } @@ -87,6 +82,11 @@ class BSTController: Controller(){ val tree = controller.getTree(name) return tree } + fun deleteTreeFromDB(name: String) { + val controller = SQLController().run { + deleteTree(name) + } + } } diff --git a/app/src/main/kotlin/app/view/AdditionErrorFragment.kt b/app/src/main/kotlin/app/view/AdditionErrorFragment.kt deleted file mode 100644 index d6fd567..0000000 --- a/app/src/main/kotlin/app/view/AdditionErrorFragment.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.view - -import tornadofx.Fragment -import tornadofx.label - -class AdditionErrorFragment: Fragment() { - override val root = label("Addition error") -} \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index cb7e45b..546ed3a 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -1,15 +1,10 @@ package app.view.treeView import tornadofx.* import app.controller.BSTController -import app.view.AdditionErrorFragment import bst.BSTree import javafx.beans.property.SimpleStringProperty -import javafx.scene.control.ListCell -import javafx.scene.control.ListView -import javafx.scene.input.MouseButton import javafx.scene.layout.Pane -import javafx.stage.StageStyle -import javafx.util.Callback +import javafx.scene.control.Alert class BinarySearchTreeView : View() { private val controller: BSTController by inject() @@ -18,11 +13,11 @@ class BinarySearchTreeView : View() { private val key = SimpleStringProperty() private val value = SimpleStringProperty() private var trees = controller.getTreesList() - var selectedItem: String? by singleAssign() + var selectedItem: String? = "" override val root = vbox { hbox { - combobox { + val availableTrees = combobox { this@BinarySearchTreeView.trees?.let { items.addAll(it) } selectionModel.selectedItemProperty().addListener { _, _, newValue -> this@BinarySearchTreeView.selectedItem = newValue @@ -39,11 +34,20 @@ class BinarySearchTreeView : View() { } } } + button("Delete") { + action { + selectedItem?.let { + controller.clearTree(tree, treePane) + controller.deleteTreeFromDB(it) + } + availableTrees.items.remove(selectedItem) + println("Item deleted: $selectedItem") + } + } button("Clear") { action { controller.clearTree(tree, treePane) - } } form { @@ -58,14 +62,10 @@ class BinarySearchTreeView : View() { button("Add Node") { action { - if (key.value != null && value.value != null) { - if (controller.isNumeric(key.value)) { + if ( key.value != null && value.value != null && controller.isNumeric(key.value)){ controller.insertNode(tree, treePane, key.value.toInt(), value.value) - } else { - find().openModal(stageStyle = StageStyle.UTILITY) - } - } else { - find().openModal(stageStyle = StageStyle.UTILITY) + } else{ + alert(type = Alert.AlertType.ERROR, header = "Insertion Error") } key.value = "" value.value = "" diff --git a/start-db.sh b/start-db.sh old mode 100644 new mode 100755 diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index 3153ca4..ab7ec00 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -11,9 +11,7 @@ class AVLTree, V>(@Transient val key: K? = null, @Transient va rootNode = initNode(key, value) } } - fun setName(treeName: String){ - this.treeName = treeName - } + override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { if (node == null) return initNode(key, value) if (key < node.key) { diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index a95ff54..7192936 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -1,5 +1,6 @@ package bst +import bst.nodes.BSTNode import bst.nodes.BinaryNode import org.neo4j.ogm.annotation.GeneratedValue import org.neo4j.ogm.annotation.Id @@ -16,7 +17,16 @@ abstract class AbstractBST, V, Self: BinaryNode> : internal var rootNode: Self? = null - // factory method + fun setName(treeName: String){ + this.treeName = treeName + } + + fun getRoot(): Self? = this.rootNode + + fun clear() { + this.rootNode = null + } + protected abstract fun initNode(key: K, value: V): Self override fun insert(key: K, value: V) { diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 4432969..fbff5cd 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -8,15 +8,4 @@ class BSTree, V>(val key: K? = null, val value: V? = null): Abs rootNode = initNode(key, value) } } - - fun setName(treeName: String){ - this.treeName = treeName - } - fun clear(){ - this.rootNode = null - } - - fun getRoot(): BSTNode?{ - return this.rootNode - } } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 52789d4..6766b12 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -9,9 +9,6 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } } - fun setName(treeName: String){ - this.treeName = treeName - } private fun isRed(node: RBTNode?): Boolean { return node != null && node.color == RBTNode.Color.RED } diff --git a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt index cdc3f32..c0a8a4f 100644 --- a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -12,19 +12,19 @@ class JsonController{ val writer = FileWriter("${tree.treeName}.json") gson.toJson(tree, writer) writer.close() - }catch (e: Exception){ + } catch (e: Exception){ println("Write error") } } fun readFromJson(treeName: String): AVLTree? { val gson = Gson() - return try{ + return try { val reader = FileReader("$treeName.json") val tree = gson.fromJson(reader, AVLTree()::class.java) reader.close() tree - }catch (e: Exception){ - println("read error") + } catch (e: Exception){ + println("Read error") null } } diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 50da2e1..8e16744 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -14,7 +14,7 @@ import org.jetbrains.exposed.sql.transactions.transaction class SQLController { private fun connectDB() { Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", - user = "test", password = "test") + user = "test", password = "test-test") } fun deleteTree(treeName: String) { From 48c3e3c068ed81db6cb5ae2b15f1aedc5f61b705 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Sun, 30 Apr 2023 21:32:12 +0300 Subject: [PATCH 076/118] add ability to save bst in db with gui --- .../kotlin/app/controller/BSTController.kt | 28 ++++++++++++------ .../kotlin/app/view/SetTreeNameFragment.kt | 23 +++++++++++++++ .../app/view/treeView/BinarySearchTreeView.kt | 29 +++++++++++++++---- .../bst/db/controllers/SQLController.kt | 2 +- trees/src/main/kotlin/bst/db/models/Nodes.kt | 2 +- 5 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 app/src/main/kotlin/app/view/SetTreeNameFragment.kt diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index 8c430fc..4f96741 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -1,4 +1,5 @@ package app.controller + import bst.BSTree import bst.nodes.BSTNode import javafx.scene.layout.Pane @@ -12,7 +13,7 @@ import bst.db.controllers.SQLController import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList -class BSTController: Controller(){ +class BSTController : Controller() { fun isNumeric(s: String): Boolean { return try { s.toInt() @@ -21,18 +22,20 @@ class BSTController: Controller(){ false } } - fun insertNode(tree:BSTree, treePane: Pane, key: Int, value:String){ + + fun insertNode(tree: BSTree, treePane: Pane, key: Int, value: String) { tree.insert(key, value) drawTree(tree, treePane) } - fun clearTree(tree:BSTree, treePane: Pane) { + + fun clearTree(tree: BSTree, treePane: Pane) { val controller = SQLController() tree.clear() treePane.children.clear() } //make here not null check - fun drawTree(tree:BSTree, treePane: Pane) { + fun drawTree(tree: BSTree, treePane: Pane) { treePane.children.clear() val root = tree.getRoot() if (root != null) { @@ -50,7 +53,7 @@ class BSTController: Controller(){ val nodeLabel = Label(node.key.toString()) nodeLabel.layoutX = circle.centerX - (circle.radius / 3) nodeLabel.layoutY = circle.centerY - (circle.radius / 3) - treePane.children.addAll(circle,nodeLabel ) + treePane.children.addAll(circle, nodeLabel) if (node.left != null) { val leftX = x - offsetX @@ -65,28 +68,35 @@ class BSTController: Controller(){ val rightY = y + 50 val rightLine = Line(x, y + circleRadius, rightX, rightY - circleRadius) treePane.children.add(rightLine) - drawNode(node.right!!,treePane, rightX, rightY, offsetX / 2.0) + drawNode(node.right!!, treePane, rightX, rightY, offsetX / 2.0) } } + fun getTreesList(): ObservableList? { val controller = SQLController() val treeNames = controller.getAllTrees() val values = observableArrayList() - treeNames.forEach{ + treeNames.forEach { values.add(it) } return values } - fun getTreeFromDB(name: String): BSTree?{ + + fun getTreeFromDB(name: String): BSTree? { val controller = SQLController() val tree = controller.getTree(name) return tree } + fun deleteTreeFromDB(name: String) { - val controller = SQLController().run { + SQLController().run { deleteTree(name) } } + fun saveTree(tree: BSTree) { + val controller = SQLController() + controller.saveTreeToDB(tree) + } } diff --git a/app/src/main/kotlin/app/view/SetTreeNameFragment.kt b/app/src/main/kotlin/app/view/SetTreeNameFragment.kt new file mode 100644 index 0000000..ed2ea21 --- /dev/null +++ b/app/src/main/kotlin/app/view/SetTreeNameFragment.kt @@ -0,0 +1,23 @@ +package app.view + +import bst.BSTree +import javafx.beans.property.SimpleStringProperty +import tornadofx.* +import app.controller.BSTController + +class SetTreeNameFragment : Fragment() { + private val name = SimpleStringProperty() + private val controller = BSTController() + override val root = vbox { + field("Input tree name") { + textfield(name) + } + button("save") { + close() +// tree.setName(name.value) +// if (tree.getRoot() != null) { +// controller.saveTree(tree) +// } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index 546ed3a..514d2b7 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -1,4 +1,5 @@ package app.view.treeView + import tornadofx.* import app.controller.BSTController import bst.BSTree @@ -6,6 +7,7 @@ import javafx.beans.property.SimpleStringProperty import javafx.scene.layout.Pane import javafx.scene.control.Alert + class BinarySearchTreeView : View() { private val controller: BSTController by inject() private var tree = BSTree() @@ -13,8 +15,8 @@ class BinarySearchTreeView : View() { private val key = SimpleStringProperty() private val value = SimpleStringProperty() private var trees = controller.getTreesList() - var selectedItem: String? = "" - + private var selectedItem: String? = "" + private val treeName = SimpleStringProperty() override val root = vbox { hbox { val availableTrees = combobox { @@ -45,13 +47,13 @@ class BinarySearchTreeView : View() { println("Item deleted: $selectedItem") } } + button("Clear") { action { controller.clearTree(tree, treePane) } } form { - fieldset { field("Key input") { textfield(key) @@ -62,14 +64,29 @@ class BinarySearchTreeView : View() { button("Add Node") { action { - if ( key.value != null && value.value != null && controller.isNumeric(key.value)){ - controller.insertNode(tree, treePane, key.value.toInt(), value.value) - } else{ + if (key.value != null && value.value != null && controller.isNumeric(key.value)) { + controller.insertNode(tree, treePane, key.value.toInt(), value.value) + } else { alert(type = Alert.AlertType.ERROR, header = "Insertion Error") } key.value = "" value.value = "" } + + } + field("Input tree name") { + textfield(treeName) + } + button("Save tree") { + action { + if (tree.getRoot() != null) { + tree.treeName = treeName.value + controller.saveTree(tree) + if (!availableTrees.items.contains(treeName.value)) { + availableTrees.items.add(treeName.value) + } + } + } } } } diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 8e16744..50da2e1 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -14,7 +14,7 @@ import org.jetbrains.exposed.sql.transactions.transaction class SQLController { private fun connectDB() { Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", - user = "test", password = "test-test") + user = "test", password = "test") } fun deleteTree(treeName: String) { diff --git a/trees/src/main/kotlin/bst/db/models/Nodes.kt b/trees/src/main/kotlin/bst/db/models/Nodes.kt index 78c51fd..20c6cfc 100644 --- a/trees/src/main/kotlin/bst/db/models/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/models/Nodes.kt @@ -3,7 +3,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption object Nodes : IntIdTable("nodes") { - val key = varchar("key", length = 256).uniqueIndex() + val key = varchar("key", length = 256) val value = varchar("value", length = 256) val x = double("x_coordinate").default(0.0) val y = double("y_coordinate").default(0.0) From 6ee14b3812ed9c6647c0ca810667125d6e1415c7 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 21 Apr 2023 23:15:28 +0300 Subject: [PATCH 077/118] ci: build on multiple os, remove redundant cache --- .github/workflows/test.yml | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 718985c..634a8fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,36 +5,22 @@ on: jobs: build: name: Build and Run Tests - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] steps: - uses: actions/checkout@v3 - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Build with Gradle run: ./gradlew build - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle - - name: JaCoCo Coverage Report - env: - report_path: trees/build/jacoco/report.csv - run: | - awk -F"," '{ instructions += $4 + $5; covered += $5; branches += $6 + $7; branches_covered +=$7 } END { print "Instructions covered:", covered"/"instructions, "--", 100*covered/instructions"%"; print "Branches covered:", branches_covered"/"branches, "--", 100*branches_covered/branches"%" }' $report_path + - uses: actions/upload-artifact@v3 + if: ${{ github.ref == 'refs/heads/main' }} with: name: test-and-coverage-reports path: trees/build/reports - retention-days: 365 - - name: Cleanup Gradle Cache - - # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. - # Restoring these files from a GitHub Actions cache might cause problems for future builds. - run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties + retention-days: 365 \ No newline at end of file From 42d906ad8392387cf2ff9aaf19bfaad5457bc436 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 21 Apr 2023 23:45:55 +0300 Subject: [PATCH 078/118] refactor: better build.gradle.kts code style --- gradle.properties | 4 ---- trees/build.gradle.kts | 14 ++++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index b35f871..0000000 --- a/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -sqliteJdbcVersion=3.41.2.1 -kotlinVersion=1.8.10 -neo4jDriverVersion=5.6.0 -exposedVersion=0.40.1 \ No newline at end of file diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 08df151..f2f5c28 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -10,8 +10,6 @@ repositories { mavenCentral() } -val exposedVersion: String by project -val sqliteJdbcVersion: String by project dependencies { // Use the Kotlin JUnit 5 integration. testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") @@ -27,14 +25,14 @@ dependencies { implementation("com.google.code.gson:gson:2.10.1") // Use JetBrains Exposed - implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") - implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + 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", sqliteJdbcVersion) + implementation("org.xerial:sqlite-jdbc:3.41.2.1") - implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") - implementation("org.slf4j", "slf4j-simple", "1.7.29") + implementation("io.github.microutils:kotlin-logging-jvm:2.0.6") + implementation("org.slf4j:slf4j-simple:1.7.29") } From f85d29add9ad75c4c264a4a298810a0a35b439a4 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 21 Apr 2023 23:52:21 +0300 Subject: [PATCH 079/118] ci: Add dependabot --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..cb2d165 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file From d613138aa21f2aa3ab87334aab7e6cad9e0e6d9e Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 22 Apr 2023 19:43:48 +0300 Subject: [PATCH 080/118] fix: remove debugging tools --- trees/src/main/kotlin/bst/AbstractBST.kt | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 7d214f4..a95ff54 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -79,25 +79,5 @@ abstract class AbstractBST, V, Self: BinaryNode> : node.right == null -> node else -> findMax(node.right) } - - /* - - // for debug purposes only - fun printTree() = println(diagram(rootNode)) - private fun diagram(node: Self?, - top: String = "", - root: String = "", - bottom: String = ""): String { - return node?.let { - if (node.left == null && node.right == null) { - "$root${node.value}\n" - } else { - diagram(node.right, "$top ", "$top┌──", "$top│ ") + - root + "${node.value}\n" + diagram(node.left, "$bottom│ ", "$bottom└──", "$bottom ") - } - } ?: "${root}null\n" - } - - */ } From e4c8de21a868098d6af3f37f8e544bed25f974ed Mon Sep 17 00:00:00 2001 From: ancavar Date: Sat, 22 Apr 2023 19:55:19 +0300 Subject: [PATCH 081/118] fix: change color of RBT node to enum type --- trees/src/main/kotlin/bst/RedBlackTree.kt | 40 +++++++++++----------- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 5 ++- trees/src/test/kotlin/bst/RbtTest.kt | 2 +- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index c7bdcbc..fa7a165 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -4,7 +4,7 @@ import bst.nodes.RBTNode class RedBlackTree, V> : BalancingTree>() { private fun isRed(node: RBTNode?): Boolean { - return node != null && node.red + return node != null && node.color == RBTNode.Color.RED } override fun insert(key: K, value: V) { @@ -15,17 +15,17 @@ class RedBlackTree, V> : BalancingTree>() if (rootNode == null) { /* Empty tree case */ rootNode = initNode(key, value) - rootNode?.red = false + rootNode?.color = RBTNode.Color.BLACK return } else { val head = initNode(key, value) // False tree root - var grandparent: RBTNode? = null // Grandparent - var t: RBTNode = head // Parent - var parent: RBTNode? = null // Iterator + var grandparent: RBTNode? = null + var t: RBTNode = head // Iterator + var parent: RBTNode? = null t.right = rootNode - var q: RBTNode? = t.right // Parent + var q: RBTNode? = t.right var dir = false // false - left, true - right var last = false @@ -37,9 +37,9 @@ class RedBlackTree, V> : BalancingTree>() if (dir) parent?.right = q else parent?.left = q } else if (isRed(q.left) && isRed(q.right)) { // Color flip - q.red = true - q.left?.red = false - q.right?.red = false + q.color = RBTNode.Color.RED + q.left?.color = RBTNode.Color.BLACK + q.right?.color = RBTNode.Color.BLACK } // Fix red violation if (isRed(q) && isRed(parent) && grandparent != null) { @@ -78,7 +78,7 @@ class RedBlackTree, V> : BalancingTree>() // Update root rootNode = head.right } - rootNode?.red = false + rootNode?.color = RBTNode.Color.BLACK } override fun remove(key: K) { removeNode(key) @@ -129,9 +129,9 @@ class RedBlackTree, V> : BalancingTree>() if (s != null) { if (!isRed(s.child(!last)) && !isRed(s.child(last))) { /* Color flip */ - parent.red = false - s.red = true - q.red = true + parent.color = RBTNode.Color.BLACK + s.color = RBTNode.Color.RED + q.color = RBTNode.Color.RED } else { val dir2 = (grandparent?.right ?: throw IllegalStateException("Grandparent node cannot be null")) == parent @@ -149,10 +149,10 @@ class RedBlackTree, V> : BalancingTree>() } } /* Ensure correct coloring */ - q.red = true - grandparent.child(dir2)?.red = true - grandparent.child(dir2)?.left?.red = false - grandparent.child(dir2)?.right?.red = false + q.color = RBTNode.Color.RED + grandparent.child(dir2)?.color = RBTNode.Color.RED + grandparent.child(dir2)?.left?.color = RBTNode.Color.BLACK + grandparent.child(dir2)?.right?.color = RBTNode.Color.BLACK } } } @@ -174,7 +174,7 @@ class RedBlackTree, V> : BalancingTree>() rootNode = head.child(true) /* Make the root black for simplified logic */ - rootNode?.red = false + rootNode?.color = RBTNode.Color.BLACK } return 1 } @@ -185,8 +185,8 @@ class RedBlackTree, V> : BalancingTree>() } else { rotateLeft(node) } - node.red = true - save.red = false + node.color = RBTNode.Color.RED + save.color = RBTNode.Color.BLACK return save } diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 46193a8..21e79f6 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -3,7 +3,10 @@ package bst.nodes class RBTNode, V> ( key: K, value: V, - var red: Boolean = true + var color: Color = Color.RED ) : BinaryNode>(key, value) { internal fun child(f: Boolean) = if (f) right else left + enum class Color { + RED, BLACK + } } diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index c80d1d0..2445973 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -48,7 +48,7 @@ class RbtTest { } private fun , V> isRed(node: RBTNode?): Boolean { - return node != null && node.red + return node != null && node.color == RBTNode.Color.RED } private fun , V> countNodes(node: RBTNode?): Int { From b102436fac33383ddf411d11fb2fb18784a579f7 Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Sat, 22 Apr 2023 22:27:23 +0300 Subject: [PATCH 082/118] fix: Make methods for rotation abstract, fix flaky AVL test with shuffling values and other minor fixes... --- trees/src/main/kotlin/bst/AVLTree.kt | 30 +++++++++------------- trees/src/main/kotlin/bst/BSTree.kt | 4 --- trees/src/main/kotlin/bst/BalancingTree.kt | 14 ++-------- trees/src/main/kotlin/bst/RedBlackTree.kt | 14 ++++++++++ trees/src/test/kotlin/bst/AvlTest.kt | 21 +++++---------- 5 files changed, 34 insertions(+), 49 deletions(-) diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index e1c9bd0..e135fd9 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -1,14 +1,3 @@ -// GPL-3.0-or-later -// -// This file is part of Trees-3. -// -// Trees-3 is free software: you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the license or (at your option) any later version. -// -// Trees-3 is distributed in the hope that it will be useful, but WITHOUT ANY GUARANTEES; even without an implicit guarantee of merchantability or FITNESS FOR A PARTICULAR PURPOSE. For more information, see the GNU General Public License. -// -// You should have obtained a copy of the GNU General Public License with this program. If it is not, see . -// Copyright (C) <2023> - package bst import bst.nodes.AVLNode @@ -71,7 +60,8 @@ class AVLTree, V> : BalancingTree>() { } else if (node.right == null) { return node.left } else { - val tmp: AVLNode = findMax(node.left)!! + val tmp: AVLNode = findMax(node.left) ?: + throw IllegalStateException("Left subtree must contain elements") node.key = tmp.key node.value = tmp.value node.left = removeNode(node.left, tmp.key) @@ -83,15 +73,19 @@ class AVLTree, V> : BalancingTree>() { } override fun rotateRight(node: AVLNode): AVLNode { - val tmp = super.rotateRight(node) + val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") + node.left = left.right + left.right = node updateHeight(node) - updateHeight(tmp) - return tmp + updateHeight(left) + return left } override fun rotateLeft(node: AVLNode): AVLNode { - val tmp = super.rotateLeft(node) + val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") + node.right = right.left + right.left = node updateHeight(node) - updateHeight(tmp) - return tmp + updateHeight(right) + return right } } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 4b3a893..21d751a 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,8 +1,4 @@ package bst -import org.neo4j.ogm.annotation.Labels -import org.neo4j.ogm.config.Configuration -import org.neo4j.ogm.session.SessionFactory -import org.neo4j.ogm.session.query import bst.nodes.BSTNode import com.google.gson.Gson diff --git a/trees/src/main/kotlin/bst/BalancingTree.kt b/trees/src/main/kotlin/bst/BalancingTree.kt index 8622300..5956ff1 100644 --- a/trees/src/main/kotlin/bst/BalancingTree.kt +++ b/trees/src/main/kotlin/bst/BalancingTree.kt @@ -3,16 +3,6 @@ package bst import bst.nodes.BinaryNode abstract class BalancingTree, V, Self : BinaryNode> : AbstractBST() { - protected open fun rotateLeft(node: Self): Self { - val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") - node.right = right.left - right.left = node - return right - } - protected open fun rotateRight(node: Self): Self { - val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") - node.left = left.right - left.right = node - return left - } + protected abstract fun rotateLeft(node: Self): Self + protected abstract fun rotateRight(node: Self): Self } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index fa7a165..680b9dd 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -200,4 +200,18 @@ class RedBlackTree, V> : BalancingTree>() } override fun initNode(key: K, value: V): RBTNode = RBTNode(key, value) + + override fun rotateLeft(node: RBTNode): RBTNode { + val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") + node.right = right.left + right.left = node + return right + } + + override fun rotateRight(node: RBTNode): RBTNode { + val left = node.left ?: throw IllegalStateException("Node's left child cannot be null") + node.left = left.right + left.right = node + return left + } } diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 97ad944..03836ba 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -31,7 +31,6 @@ class AvlTest { @BeforeEach fun initializeObjects() { tree = AVLTree() - values.shuffle() } @Test @@ -44,15 +43,7 @@ class AvlTest { @Nested inner class InsertionTests { @Test - fun `Single insertion`() { - tree.insert(1, 1) - assertEquals(1, tree.rootNode?.key) - assertEquals(1, countNodes(tree.rootNode)) - assertTrue(isAvl(tree.rootNode)) - } - - @Test - fun `Bigger insertion`() { + fun `Simple insertion`() { tree.insert(2, 2) tree.insert(3, 3) tree.insert(1, 1) @@ -66,7 +57,7 @@ class AvlTest { } @Test - fun `Left-left case`() { + fun `Left-left rotation case`() { tree.insert(3, 3) tree.insert(2, 2) tree.insert(1, 1) @@ -80,7 +71,7 @@ class AvlTest { } @Test - fun `Left-right case`() { + fun `Left-right rotation case`() { tree.insert(5, 5) tree.insert(3, 3) tree.insert(4, 4) @@ -94,7 +85,7 @@ class AvlTest { } @Test - fun `Right-right case`() { + fun `Right-right rotation case`() { tree.insert(3, 3) tree.insert(4, 4) tree.insert(5, 5) @@ -108,7 +99,7 @@ class AvlTest { } @Test - fun `Right-left case`() { + fun `Right-left rotation case`() { tree.insert(3, 3) tree.insert(5, 5) tree.insert(4, 4) @@ -168,6 +159,7 @@ class AvlTest { tree.insert(4, 4) tree.insert(1, 1) tree.remove(-1) + assertEquals(3, countNodes(tree.rootNode)) } @Test @@ -177,6 +169,5 @@ class AvlTest { assertEquals(500, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) } - // TODO: Add tests on deletions that cause rotations } } From 9df571ced8640de52d159cbbf1d5ad352507f600 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Sun, 23 Apr 2023 20:41:31 +0300 Subject: [PATCH 083/118] implemented saving data to sqlite db --- trees/build.gradle.kts | 10 ++- trees/src/main/kotlin/bst/BSTree.kt | 34 ++++++-- trees/src/main/kotlin/bst/db/Node.kt | 7 +- trees/src/main/kotlin/bst/db/Nodes.kt | 6 +- trees/src/main/kotlin/bst/db/SQLController.kt | 77 +++++++++++++++++ .../main/kotlin/bst/db/SerializableNode.kt | 12 +++ .../main/kotlin/bst/db/SerializableTree.kt | 9 ++ trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt | 83 ------------------- trees/src/main/kotlin/bst/db/Trees.kt | 4 +- 9 files changed, 142 insertions(+), 100 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/SQLController.kt create mode 100644 trees/src/main/kotlin/bst/db/SerializableNode.kt create mode 100644 trees/src/main/kotlin/bst/db/SerializableTree.kt delete mode 100644 trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index f2f5c28..bba28bb 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -3,6 +3,8 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.8.10" id("jacoco") id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" + id ("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" + } repositories { @@ -18,6 +20,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + implementation("org.slf4j", "slf4j-simple", "1.7.29") + + implementation("org.neo4j:neo4j-ogm-core:4.0.5") runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") @@ -31,8 +36,9 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.41.2.1") - implementation("io.github.microutils:kotlin-logging-jvm:2.0.6") - implementation("org.slf4j:slf4j-simple:1.7.29") + implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + } diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 21d751a..0b5b376 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -5,20 +5,34 @@ import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null, treeName_: String=""): AbstractBST>() { +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) init { if (key != null && value != null) { rootNode = initNode(key, value) - treeName = treeName_ } } + fun setName(treeName: String){ + this.treeName = treeName + } + //save_node is method for check recursive addition elements to db - fun save_node(node: BSTNode?, parentNode:BSTNode? = null){ + fun saveTree(node: BSTNode? = this.rootNode, parentNode:BSTNode? = this.rootNode){ node?.let { - save_node(node.left, node) - save_node(node.right, node) + saveTree(node.left, node) +// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") + +// println("${node.key}: ${node.value}") + saveTree(node.right, node) +// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") + +// println("${node.key}: ${node.value}") +// println("parent node: ${null}: ${null} saving node: ${node.key}: ${node.value}") +// if (parentNode?.key == node.key){ +// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") +// } +// else println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") } } @@ -42,12 +56,16 @@ fun readFromJson(treeName: String): BSTree<*, *>{ fun main(){ - val test_data = BSTree("121", "dgs", "tree_1") + val test_data = BSTree("121", "dgs") test_data.insert("110", "dafad") test_data.insert("118", "adfaf") test_data.insert("124", "fggsg") - test_data.saveTreeToJson() - readFromJson("tree_1.json") + test_data.insert("127", "fggsg") + test_data.insert("123", "fggsg") + test_data.setName("etst") +// test_data.saveTreeToJson() + test_data.saveTree() +// readFromJson("tree_1.json") // test_data.save_node(test_data.rootNode) // val json = Gson().toJson(test_data) // println(json) diff --git a/trees/src/main/kotlin/bst/db/Node.kt b/trees/src/main/kotlin/bst/db/Node.kt index 91789cf..15727d6 100644 --- a/trees/src/main/kotlin/bst/db/Node.kt +++ b/trees/src/main/kotlin/bst/db/Node.kt @@ -6,8 +6,8 @@ class Node(id: EntityID): IntEntity(id) { companion object: IntEntityClass(Nodes) var key by Nodes.key var value by Nodes.value - val x by Nodes.x - val y by Nodes.y + var x by Nodes.x + var y by Nodes.y var left by Node optionalReferencedOn Nodes.left var right by Node optionalReferencedOn Nodes.right var tree by Tree referencedOn Nodes.tree @@ -15,3 +15,6 @@ class Node(id: EntityID): IntEntity(id) { } +//class NodeSerialize(){ +// +//} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/Nodes.kt index 5cee74b..fddd210 100644 --- a/trees/src/main/kotlin/bst/db/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/Nodes.kt @@ -3,10 +3,10 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption object Nodes : IntIdTable("nodes") { - val key = varchar("key", length = 256) + val key = varchar("key", length = 256).uniqueIndex() val value = varchar("value", length = 256) - val x = integer("x_coordinate").nullable() - val y = integer("y_coordinate").nullable() + val x = double("x_coordinate").nullable() + val y = double("y_coordinate").nullable() val left = reference("left", Nodes).nullable() val right = reference("right", Nodes).nullable() val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt new file mode 100644 index 0000000..32eba24 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -0,0 +1,77 @@ +package bst.db +import bst.BSTree +import bst.nodes.BSTNode +import db.Node +import org.jetbrains.exposed.sql.* +import db.Trees +import db.Nodes +import db.Tree +import org.jetbrains.exposed.sql.transactions.transaction + +private const val dbPath = "exposed_database.db" + +class SQLController { + private fun connectDB() { + Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") + } + + private fun serializeNode(node: BSTNode<*, *>?): SerializableNode?{ + return if (node == null){ + null + } else{ + val serializableNode = SerializableNode(key = node.key.toString(), value = node.value.toString(), leftNode = null, rightNode = null) + serializableNode.rightNode = serializeNode(node.right) + serializableNode.leftNode = serializeNode(node.left) + serializableNode + } + } + + private fun serializeTree(tree: BSTree<*, *>): SerializableTree{ + val serializedTree = SerializableTree(treeName = tree.treeName, rootNode = tree.rootNode?.let { serializeNode(it) }) + serializedTree.treeName = tree.treeName + serializedTree.rootNode = serializeNode(tree.rootNode) + return serializedTree + } + + private fun createTables(){ + SchemaUtils.create(Trees) + SchemaUtils.create(Nodes) + } + + + private fun SerializableNode.toNodeEntity(treeEntity: Tree): Node { + return Node.new { + key = this@toNodeEntity.key + value = this@toNodeEntity.value + x = this@toNodeEntity.x + y = this@toNodeEntity.y + left = this@toNodeEntity.leftNode?.toNodeEntity(treeEntity) + right = this@toNodeEntity.rightNode?.toNodeEntity(treeEntity) + tree = treeEntity + } + } + + fun saveTreeToDB(tree: BSTree<*, *>){ + connectDB() + val serializedTree = serializeTree(tree) + transaction { + addLogger(StdOutSqlLogger) + createTables() + val daoTree = Tree.new { + name = serializedTree.treeName + } + daoTree.rootNode = serializedTree.rootNode?.toNodeEntity(daoTree) + } + } +} + +//fun main(){ +// val test_data = BSTree(121, "dgs") +// test_data.insert(110, "dafad") +// test_data.insert(118, "adfaf") +// test_data.insert(124, "fggsg") +// test_data.setName("afefadsf") +// val controller = SQLController() +//// val serializedTree = controller.serializeTree(test_data) +// controller.saveTreeToDB(test_data) +//} diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/SerializableNode.kt new file mode 100644 index 0000000..f9a5b8d --- /dev/null +++ b/trees/src/main/kotlin/bst/db/SerializableNode.kt @@ -0,0 +1,12 @@ +package bst.db +import kotlinx.serialization.* + +@Serializable +class SerializableNode( + val key: String, + val value : String, + val x : Double = 0.0, + val y: Double = 0.0, + var leftNode: SerializableNode? = null, + var rightNode: SerializableNode? = null, +) diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/SerializableTree.kt new file mode 100644 index 0000000..b5e00b8 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/SerializableTree.kt @@ -0,0 +1,9 @@ +package bst.db + +import kotlinx.serialization.Serializable + +@Serializable +class SerializableTree ( + var treeName: String, + var rootNode: SerializableNode?, + ) diff --git a/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt b/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt deleted file mode 100644 index 0d84738..0000000 --- a/trees/src/main/kotlin/bst/db/TreeSQLdbApp.kt +++ /dev/null @@ -1,83 +0,0 @@ -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger -import db.Trees -import db.Node -import db.Nodes -import db.Tree -import bst.BSTree -import bst.nodes.BSTNode - -private const val dbPath = "exposed_database.db" -//not finaly сompleted -fun treeSave(tree: BSTree<*, *>){ -// transaction { - val rootNode_ = Node.new { - key=tree.rootNode?.key.toString() - value=tree.rootNode?.value.toString() - } -// val TreeObject = Tree.new { -// name = tree.treeName -// } -// TreeObject.rootNode = rootNode_ -// saveNodes(tree.rootNode, tree.rootNode, TreeObject) -// } -} -//not finaly сompleted -fun saveNodes(node: BSTNode<*, *>?, parentNode:BSTNode<*, *>? = null, tree_: Tree){ - node?.let { - saveNodes(node.left, node, tree_) - saveNodes(node.right, node, tree_) -// transaction { -// if (parentNode == null){ - -// } - - val leftChildNode = Node.new { - key = parentNode?.left?.key.toString() - value = parentNode?.left?.value.toString() - tree = tree_ - } - val rightChildNode = Node.new { - key = parentNode?.right?.key.toString() - value = parentNode?.right?.value.toString() - tree = tree_ - - } - val parentNode_ = Node.new { - key = parentNode?.key.toString() - value = parentNode?.value.toString() - left = leftChildNode - right = rightChildNode - } -// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") - } -} - - -fun main() { - Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") - transaction { - addLogger(StdOutSqlLogger) - SchemaUtils.create(Trees) - SchemaUtils.create(Nodes) - val test_data = BSTree("121", "dgs", "tree_1") - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") - treeSave(test_data) - - val treeObj = Tree.new { - name = "Tree_1" - } - val rootNode = Node.new { - key = "test" - value = "234" - tree = treeObj - } - treeObj.rootNode = rootNode -// treeObj.delete() - } -} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/Trees.kt b/trees/src/main/kotlin/bst/db/Trees.kt index cd17c52..df464e5 100644 --- a/trees/src/main/kotlin/bst/db/Trees.kt +++ b/trees/src/main/kotlin/bst/db/Trees.kt @@ -4,7 +4,7 @@ import db.Nodes import org.jetbrains.exposed.dao.id.IntIdTable object Trees : IntIdTable("trees") { - val name = varchar("name", length = 128) - val rootNode = reference("rootNode", Nodes).nullable() + val name = varchar("name", length = 128).uniqueIndex() + val rootNode = reference("rootNode", Nodes.id).nullable() } From 6f47c8e15a0c33d09dc5a1377acd94507246f45e Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 24 Apr 2023 03:01:51 +0300 Subject: [PATCH 084/118] feat: implemented reading tree object from sqlite database --- trees/src/main/kotlin/bst/BSTree.kt | 36 +----- .../src/main/kotlin/bst/db/JsonController.kt | 24 ++++ trees/src/main/kotlin/bst/db/Nodes.kt | 5 +- trees/src/main/kotlin/bst/db/SQLController.kt | 121 ++++++++++++++---- .../main/kotlin/bst/db/SerializableTree.kt | 2 +- 5 files changed, 126 insertions(+), 62 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/JsonController.kt diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 0b5b376..c62b7a9 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -17,44 +17,12 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val this.treeName = treeName } - //save_node is method for check recursive addition elements to db - fun saveTree(node: BSTNode? = this.rootNode, parentNode:BSTNode? = this.rootNode){ - node?.let { - saveTree(node.left, node) -// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") -// println("${node.key}: ${node.value}") - saveTree(node.right, node) -// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") -// println("${node.key}: ${node.value}") -// println("parent node: ${null}: ${null} saving node: ${node.key}: ${node.value}") -// if (parentNode?.key == node.key){ -// println("parent node: ${node.key}: ${node.value} saving left node: ${node.left?.key}: ${node.left?.value} saving right node: ${node.right?.key}: ${node.right?.value}") -// } -// else - println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") - } - } - - fun saveTreeToJson(){ - val gson = Gson() - val writer = FileWriter("${this.treeName}.json") - val treeToSave = this - gson.toJson(treeToSave, writer) - writer.close() - } - -} -fun readFromJson(treeName: String): BSTree<*, *>{ - val gson = Gson() - val reader = FileReader(treeName) - val tree = gson.fromJson(reader, BSTree::class.java) - reader.close() - return tree } + fun main(){ val test_data = BSTree("121", "dgs") test_data.insert("110", "dafad") @@ -64,7 +32,7 @@ fun main(){ test_data.insert("123", "fggsg") test_data.setName("etst") // test_data.saveTreeToJson() - test_data.saveTree() +// test_data.saveTree() // readFromJson("tree_1.json") // test_data.save_node(test_data.rootNode) // val json = Gson().toJson(test_data) diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/JsonController.kt new file mode 100644 index 0000000..04b3134 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/JsonController.kt @@ -0,0 +1,24 @@ +package bst.db + +import bst.AVLTree +import bst.BSTree +import com.google.gson.Gson +import java.io.FileReader +import java.io.FileWriter + +class JsonController{ + fun saveTreeToJson(tree: AVLTree<*, *>){ + val gson = Gson() + val writer = FileWriter("${tree.treeName}.json") + val treeToSave = tree + gson.toJson(treeToSave, writer) + writer.close() + } + fun readFromJson(treeName: String): BSTree<*, *> { + val gson = Gson() + val reader = FileReader("$treeName.json") + val tree = gson.fromJson(reader, BSTree::class.java) + reader.close() + return tree + } +} diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/Nodes.kt index fddd210..fca7eb1 100644 --- a/trees/src/main/kotlin/bst/db/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/Nodes.kt @@ -1,12 +1,13 @@ package db +import db.Trees.default import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption object Nodes : IntIdTable("nodes") { val key = varchar("key", length = 256).uniqueIndex() val value = varchar("value", length = 256) - val x = double("x_coordinate").nullable() - val y = double("y_coordinate").nullable() + val x = double("x_coordinate").default(0.0) + val y = double("y_coordinate").default(0.0) val left = reference("left", Nodes).nullable() val right = reference("right", Nodes).nullable() val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index 32eba24..187af38 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -26,11 +26,9 @@ class SQLController { } } - private fun serializeTree(tree: BSTree<*, *>): SerializableTree{ - val serializedTree = SerializableTree(treeName = tree.treeName, rootNode = tree.rootNode?.let { serializeNode(it) }) - serializedTree.treeName = tree.treeName - serializedTree.rootNode = serializeNode(tree.rootNode) - return serializedTree + private fun serializeTree(tree: BSTree<*, *>): SerializableTree? { + return tree.rootNode?.let { serializeNode(it) } + ?.let { SerializableTree(treeName = tree.treeName, rootNode = it) } } private fun createTables(){ @@ -39,15 +37,15 @@ class SQLController { } - private fun SerializableNode.toNodeEntity(treeEntity: Tree): Node { + private fun SerializableNode.toNodeDao(treeDao: Tree): Node { return Node.new { - key = this@toNodeEntity.key - value = this@toNodeEntity.value - x = this@toNodeEntity.x - y = this@toNodeEntity.y - left = this@toNodeEntity.leftNode?.toNodeEntity(treeEntity) - right = this@toNodeEntity.rightNode?.toNodeEntity(treeEntity) - tree = treeEntity + key = this@toNodeDao.key + value = this@toNodeDao.value + x = this@toNodeDao.x + y = this@toNodeDao.y + left = this@toNodeDao.leftNode?.toNodeDao(treeDao) + right = this@toNodeDao.rightNode?.toNodeDao(treeDao) + tree = treeDao } } @@ -58,20 +56,93 @@ class SQLController { addLogger(StdOutSqlLogger) createTables() val daoTree = Tree.new { + if(serializedTree!=null) name = serializedTree.treeName } - daoTree.rootNode = serializedTree.rootNode?.toNodeEntity(daoTree) + daoTree.rootNode = serializedTree?.rootNode?.toNodeDao(daoTree) } } -} -//fun main(){ -// val test_data = BSTree(121, "dgs") -// test_data.insert(110, "dafad") -// test_data.insert(118, "adfaf") -// test_data.insert(124, "fggsg") -// test_data.setName("afefadsf") -// val controller = SQLController() -//// val serializedTree = controller.serializeTree(test_data) -// controller.saveTreeToDB(test_data) -//} + private fun Node.getSerializedNode(treeDao: Tree): SerializableNode { + return SerializableNode( + this@getSerializedNode.key, + this@getSerializedNode.value, + this@getSerializedNode.x, + this@getSerializedNode.y, + this@getSerializedNode.left?.getSerializedNode(treeDao), + this@getSerializedNode.right?.getSerializedNode(treeDao), + ) + + } + + private fun findTree(treeName: String): SerializableTree?{ + connectDB() + val treeDAO = Tree.find{Trees.name eq treeName}.firstOrNull() ?: return null + return treeDAO.rootNode?.getSerializedNode(treeDAO)?.let { + SerializableTree( + treeName, + it + ) + } + } + + private fun isNumeric(s: String): Boolean { + return try { + s.toDouble() + true + } catch (e: NumberFormatException) { + false + } + } + + private fun deserializeNodeStringKey(node: SerializableNode?): BSTNode? { + return if (node == null){ + null + } else{ + + val deserializableNode = BSTNode(key = node.key, value = node.value) + deserializableNode.right = deserializeNodeStringKey(node.rightNode) + deserializableNode.left = deserializeNodeStringKey(node.leftNode) + deserializableNode + } + } + + private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { + return if (node == null){ + null + } else{ + val deserializableNode = BSTNode(key = node.key.toDouble(), value = node.value) + deserializableNode.right = deserializeNodeDoubleKey(node.rightNode) + deserializableNode.left = deserializeNodeDoubleKey(node.leftNode) + deserializableNode + } + } + + private fun deserializeTree(tree: SerializableTree?): BSTree<*, *>?{ + if (tree != null) { + return if (isNumeric(tree.rootNode.key)){ + val rootNode = deserializeNodeDoubleKey(tree.rootNode) + val deserializedTree = BSTree(rootNode?.key, rootNode?.value) + deserializedTree.rootNode = rootNode + deserializedTree.setName(tree.treeName) + deserializedTree + } else{ + val rootNode = deserializeNodeStringKey(tree.rootNode) + val deserializedTree = BSTree(rootNode?.key, rootNode?.value) + deserializedTree.rootNode = rootNode + deserializedTree.setName(tree.treeName) + deserializedTree + } + } + return null + } + + fun getTree(treeName: String): BSTree<*, *>? { + var undeserializedTree:SerializableTree? = null + transaction { + undeserializedTree = findTree(treeName) + + } + return deserializeTree(undeserializedTree) + } +} diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/SerializableTree.kt index b5e00b8..de3fed1 100644 --- a/trees/src/main/kotlin/bst/db/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/SerializableTree.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable class SerializableTree ( var treeName: String, - var rootNode: SerializableNode?, + var rootNode: SerializableNode, ) From 6e769bf9eb2723b2e3f09f0f951c306c2ffb2e33 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 24 Apr 2023 23:49:08 +0300 Subject: [PATCH 085/118] feat:add docker compose for lift postgresql db and small fixes --- trees/build.gradle.kts | 1 + trees/src/db/Node.kt | 17 ---- trees/src/db/Nodes.kt | 14 ---- trees/src/db/Tree.kt | 14 ---- trees/src/db/TreeSQLdbApp.kt | 83 ------------------- trees/src/db/Trees.kt | 10 --- .../src/main/kotlin/bst/db/JsonController.kt | 27 ++++-- trees/src/main/kotlin/bst/db/SQLController.kt | 32 +++++-- 8 files changed, 46 insertions(+), 152 deletions(-) delete mode 100644 trees/src/db/Node.kt delete mode 100644 trees/src/db/Nodes.kt delete mode 100644 trees/src/db/Tree.kt delete mode 100644 trees/src/db/TreeSQLdbApp.kt delete mode 100644 trees/src/db/Trees.kt diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index bba28bb..91c0018 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + implementation("org.postgresql:postgresql:42.5.4") } diff --git a/trees/src/db/Node.kt b/trees/src/db/Node.kt deleted file mode 100644 index 91789cf..0000000 --- a/trees/src/db/Node.kt +++ /dev/null @@ -1,17 +0,0 @@ -package db -import org.jetbrains.exposed.dao.* -import org.jetbrains.exposed.dao.id.EntityID - -class Node(id: EntityID): IntEntity(id) { - companion object: IntEntityClass(Nodes) - var key by Nodes.key - var value by Nodes.value - val x by Nodes.x - val y by Nodes.y - var left by Node optionalReferencedOn Nodes.left - var right by Node optionalReferencedOn Nodes.right - var tree by Tree referencedOn Nodes.tree - override fun toString(): String = "Node(key = $key, value=$value, x=$x, y=$y, left=$left, right=$right, tree=$tree)" - -} - diff --git a/trees/src/db/Nodes.kt b/trees/src/db/Nodes.kt deleted file mode 100644 index 5cee74b..0000000 --- a/trees/src/db/Nodes.kt +++ /dev/null @@ -1,14 +0,0 @@ -package db -import org.jetbrains.exposed.dao.id.IntIdTable -import org.jetbrains.exposed.sql.ReferenceOption - -object Nodes : IntIdTable("nodes") { - val key = varchar("key", length = 256) - val value = varchar("value", length = 256) - val x = integer("x_coordinate").nullable() - val y = integer("y_coordinate").nullable() - val left = reference("left", Nodes).nullable() - val right = reference("right", Nodes).nullable() - val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) -} - diff --git a/trees/src/db/Tree.kt b/trees/src/db/Tree.kt deleted file mode 100644 index b52af8f..0000000 --- a/trees/src/db/Tree.kt +++ /dev/null @@ -1,14 +0,0 @@ -package db - -import org.jetbrains.exposed.dao.IntEntity -import org.jetbrains.exposed.dao.IntEntityClass -import org.jetbrains.exposed.dao.id.EntityID - -class Tree(id: EntityID): IntEntity(id){ - companion object: IntEntityClass(Trees) - var name by Trees.name - var rootNode by Node optionalReferencedOn Trees.rootNode - override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" -} - -//fun createTree diff --git a/trees/src/db/TreeSQLdbApp.kt b/trees/src/db/TreeSQLdbApp.kt deleted file mode 100644 index 0d84738..0000000 --- a/trees/src/db/TreeSQLdbApp.kt +++ /dev/null @@ -1,83 +0,0 @@ -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger -import db.Trees -import db.Node -import db.Nodes -import db.Tree -import bst.BSTree -import bst.nodes.BSTNode - -private const val dbPath = "exposed_database.db" -//not finaly сompleted -fun treeSave(tree: BSTree<*, *>){ -// transaction { - val rootNode_ = Node.new { - key=tree.rootNode?.key.toString() - value=tree.rootNode?.value.toString() - } -// val TreeObject = Tree.new { -// name = tree.treeName -// } -// TreeObject.rootNode = rootNode_ -// saveNodes(tree.rootNode, tree.rootNode, TreeObject) -// } -} -//not finaly сompleted -fun saveNodes(node: BSTNode<*, *>?, parentNode:BSTNode<*, *>? = null, tree_: Tree){ - node?.let { - saveNodes(node.left, node, tree_) - saveNodes(node.right, node, tree_) -// transaction { -// if (parentNode == null){ - -// } - - val leftChildNode = Node.new { - key = parentNode?.left?.key.toString() - value = parentNode?.left?.value.toString() - tree = tree_ - } - val rightChildNode = Node.new { - key = parentNode?.right?.key.toString() - value = parentNode?.right?.value.toString() - tree = tree_ - - } - val parentNode_ = Node.new { - key = parentNode?.key.toString() - value = parentNode?.value.toString() - left = leftChildNode - right = rightChildNode - } -// println("parent node: ${parentNode?.key}: ${parentNode?.value} saving node: ${node.key}: ${node.value}") - } -} - - -fun main() { - Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") - transaction { - addLogger(StdOutSqlLogger) - SchemaUtils.create(Trees) - SchemaUtils.create(Nodes) - val test_data = BSTree("121", "dgs", "tree_1") - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") - treeSave(test_data) - - val treeObj = Tree.new { - name = "Tree_1" - } - val rootNode = Node.new { - key = "test" - value = "234" - tree = treeObj - } - treeObj.rootNode = rootNode -// treeObj.delete() - } -} \ No newline at end of file diff --git a/trees/src/db/Trees.kt b/trees/src/db/Trees.kt deleted file mode 100644 index cd17c52..0000000 --- a/trees/src/db/Trees.kt +++ /dev/null @@ -1,10 +0,0 @@ -package db - -import db.Nodes -import org.jetbrains.exposed.dao.id.IntIdTable - -object Trees : IntIdTable("trees") { - val name = varchar("name", length = 128) - val rootNode = reference("rootNode", Nodes).nullable() -} - diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/JsonController.kt index 04b3134..e45bea4 100644 --- a/trees/src/main/kotlin/bst/db/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/JsonController.kt @@ -9,16 +9,25 @@ import java.io.FileWriter class JsonController{ fun saveTreeToJson(tree: AVLTree<*, *>){ val gson = Gson() - val writer = FileWriter("${tree.treeName}.json") - val treeToSave = tree - gson.toJson(treeToSave, writer) - writer.close() +// val writer: FileWriter + try { + val writer = FileWriter("${tree.treeName}.json") + gson.toJson(tree, writer) + writer.close() + }catch (e: Exception){ + println("Write error") + } } - fun readFromJson(treeName: String): BSTree<*, *> { + fun readFromJson(treeName: String): BSTree<*, *>? { val gson = Gson() - val reader = FileReader("$treeName.json") - val tree = gson.fromJson(reader, BSTree::class.java) - reader.close() - return tree + return try{ + val reader = FileReader("$treeName.json") + val tree = gson.fromJson(reader, BSTree::class.java) + reader.close() + tree + }catch (e: Exception){ + println("read error") + null + } } } diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index 187af38..78e0765 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -8,11 +8,22 @@ import db.Nodes import db.Tree import org.jetbrains.exposed.sql.transactions.transaction + private const val dbPath = "exposed_database.db" class SQLController { private fun connectDB() { - Database.connect("jdbc:sqlite:$dbPath", driver = "org.sqlite.JDBC") + Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", + user = "test", password = "test") + } + + private fun deleteTree(treeName: String) { + transaction { + val treeEntity = + Tree.find { (Trees.name eq treeName)} + .firstOrNull() + treeEntity?.delete() + } } private fun serializeNode(node: BSTNode<*, *>?): SerializableNode?{ @@ -51,6 +62,7 @@ class SQLController { fun saveTreeToDB(tree: BSTree<*, *>){ connectDB() + deleteTree(tree.treeName) val serializedTree = serializeTree(tree) transaction { addLogger(StdOutSqlLogger) @@ -138,11 +150,21 @@ class SQLController { } fun getTree(treeName: String): BSTree<*, *>? { - var undeserializedTree:SerializableTree? = null + var deserializedTree:SerializableTree? = null transaction { - undeserializedTree = findTree(treeName) - + deserializedTree = findTree(treeName) } - return deserializeTree(undeserializedTree) + return deserializeTree( deserializedTree) } } +fun main(){ + val test_data = BSTree(121, "dgs") + test_data.insert(110, "dafad") + test_data.insert(118, "adfaf") + test_data.insert(124, "fggsg") + test_data.setName("afefadsf") + val controller = SQLController() +// val serializedTree = controller.serializeTree(test_data) + controller.saveTreeToDB(test_data) + val remTree = controller.getTree("afefadsf") +} \ No newline at end of file From cf5d1f3098b8de328754c69aad52e27ba0520959 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 21:00:13 +0300 Subject: [PATCH 086/118] fix: Rework tree deserialization logic --- .../src/main/kotlin/bst/db/JsonController.kt | 8 +-- trees/src/main/kotlin/bst/db/SQLController.kt | 56 +++++-------------- trees/src/test/kotlin/bst/BstTest.kt | 1 - 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/JsonController.kt index e45bea4..221a114 100644 --- a/trees/src/main/kotlin/bst/db/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/JsonController.kt @@ -1,15 +1,13 @@ package bst.db import bst.AVLTree -import bst.BSTree import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter class JsonController{ - fun saveTreeToJson(tree: AVLTree<*, *>){ + fun saveTreeToJson(tree: AVLTree){ val gson = Gson() -// val writer: FileWriter try { val writer = FileWriter("${tree.treeName}.json") gson.toJson(tree, writer) @@ -18,11 +16,11 @@ class JsonController{ println("Write error") } } - fun readFromJson(treeName: String): BSTree<*, *>? { + fun readFromJson(treeName: String): AVLTree? { val gson = Gson() return try{ val reader = FileReader("$treeName.json") - val tree = gson.fromJson(reader, BSTree::class.java) + val tree = gson.fromJson(reader, AVLTree()::class.java) reader.close() tree }catch (e: Exception){ diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index 78e0765..a42d989 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -9,8 +9,6 @@ import db.Tree import org.jetbrains.exposed.sql.transactions.transaction -private const val dbPath = "exposed_database.db" - class SQLController { private fun connectDB() { Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", @@ -69,7 +67,7 @@ class SQLController { createTables() val daoTree = Tree.new { if(serializedTree!=null) - name = serializedTree.treeName + name = serializedTree.treeName } daoTree.rootNode = serializedTree?.rootNode?.toNodeDao(daoTree) } @@ -100,71 +98,43 @@ class SQLController { private fun isNumeric(s: String): Boolean { return try { - s.toDouble() + s.toInt() true } catch (e: NumberFormatException) { false } } + - private fun deserializeNodeStringKey(node: SerializableNode?): BSTNode? { + private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { return if (node == null){ null } else{ - - val deserializableNode = BSTNode(key = node.key, value = node.value) - deserializableNode.right = deserializeNodeStringKey(node.rightNode) - deserializableNode.left = deserializeNodeStringKey(node.leftNode) - deserializableNode - } - } - - private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { - return if (node == null){ - null - } else{ - val deserializableNode = BSTNode(key = node.key.toDouble(), value = node.value) + val deserializableNode = BSTNode(key = node.key.toInt(), value = node.value) deserializableNode.right = deserializeNodeDoubleKey(node.rightNode) deserializableNode.left = deserializeNodeDoubleKey(node.leftNode) deserializableNode } } - private fun deserializeTree(tree: SerializableTree?): BSTree<*, *>?{ + private fun deserializeTree(tree: SerializableTree?): BSTree?{ if (tree != null) { - return if (isNumeric(tree.rootNode.key)){ + if (isNumeric(tree.rootNode.key)){ val rootNode = deserializeNodeDoubleKey(tree.rootNode) - val deserializedTree = BSTree(rootNode?.key, rootNode?.value) - deserializedTree.rootNode = rootNode - deserializedTree.setName(tree.treeName) - deserializedTree - } else{ - val rootNode = deserializeNodeStringKey(tree.rootNode) - val deserializedTree = BSTree(rootNode?.key, rootNode?.value) + val deserializedTree:BSTree = BSTree(rootNode?.key, rootNode?.value) deserializedTree.rootNode = rootNode deserializedTree.setName(tree.treeName) - deserializedTree + return deserializedTree } } - return null + return null } - fun getTree(treeName: String): BSTree<*, *>? { + fun getTree(treeName: String): BSTree? { var deserializedTree:SerializableTree? = null transaction { - deserializedTree = findTree(treeName) + deserializedTree = findTree(treeName) } - return deserializeTree( deserializedTree) + return deserializeTree(deserializedTree) } -} -fun main(){ - val test_data = BSTree(121, "dgs") - test_data.insert(110, "dafad") - test_data.insert(118, "adfaf") - test_data.insert(124, "fggsg") - test_data.setName("afefadsf") - val controller = SQLController() -// val serializedTree = controller.serializeTree(test_data) - controller.saveTreeToDB(test_data) - val remTree = controller.getTree("afefadsf") } \ No newline at end of file diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt index 3d34086..e294f13 100644 --- a/trees/src/test/kotlin/bst/BstTest.kt +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -7,7 +7,6 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import bst.BSTree class BstTest { private fun , V> isBst(root: BSTNode?): Boolean { From 6ba15468e78eb71da912004ba68ec62e1d9b5c80 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 21:21:07 +0300 Subject: [PATCH 087/118] add bat, sh files to up docker container --- .gitattributes | 8 ++++++++ docker-compose.yml | 12 ++++++++++++ postgres.env | 3 +++ start-db.bat | 3 +++ start-db.sh | 4 ++++ 5 files changed, 30 insertions(+) create mode 100644 .gitattributes create mode 100644 docker-compose.yml create mode 100644 postgres.env create mode 100644 start-db.bat create mode 100644 start-db.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a6e97c7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# +# 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 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2cd846e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3" +services: + local-database: + image: "postgres:15.2" + env_file: + - postgres.env + ports: + - "5432:5432" + volumes: + - pg-volume:/var/lib/postgresql/data +volumes: + pg-volume: {} diff --git a/postgres.env b/postgres.env new file mode 100644 index 0000000..ff9a750 --- /dev/null +++ b/postgres.env @@ -0,0 +1,3 @@ +POSTGRES_USER=test +POSTGRES_PASSWORD=test-test +POSTGRES_DB=test \ No newline at end of file diff --git a/start-db.bat b/start-db.bat new file mode 100644 index 0000000..43793b2 --- /dev/null +++ b/start-db.bat @@ -0,0 +1,3 @@ +@echo off +set BASEDIR=%~dp0 +docker-compose --file "%BASEDIR%docker-compose.yml" up diff --git a/start-db.sh b/start-db.sh new file mode 100644 index 0000000..e3a97f1 --- /dev/null +++ b/start-db.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +BASEDIR=$(dirname "$0") +docker-compose --file "${BASEDIR}"/docker-compose.yml up From dab9995d0263bb040dd30b3af6a239ea91ccfc03 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 21:28:58 +0300 Subject: [PATCH 088/118] add .gitignore --- .gitignore | 184 ++++++++++++++++++++++++++++++++ app/.DS_Store | Bin 6148 -> 0 bytes app/src/.DS_Store | Bin 6148 -> 0 bytes app/src/main/.DS_Store | Bin 6148 -> 0 bytes app/src/main/kotlin/.DS_Store | Bin 6148 -> 0 bytes gradle/.DS_Store | Bin 6148 -> 0 bytes trees/src/test/.DS_Store | Bin 6148 -> 0 bytes trees/src/test/kotlin/.DS_Store | Bin 6148 -> 0 bytes 8 files changed, 184 insertions(+) create mode 100644 .gitignore delete mode 100644 app/.DS_Store delete mode 100644 app/src/.DS_Store delete mode 100644 app/src/main/.DS_Store delete mode 100644 app/src/main/kotlin/.DS_Store delete mode 100644 gradle/.DS_Store delete mode 100644 trees/src/test/.DS_Store delete mode 100644 trees/src/test/kotlin/.DS_Store diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..395679d --- /dev/null +++ b/.gitignore @@ -0,0 +1,184 @@ +# Created by https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=kotlin,java,gradle,intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Kotlin ### +# Compiled class file + +# Log file + +# BlueJ files + +# Mobile Tools for Java (J2ME) + +# Package Files # + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof +### mac gabage files +.DS_Store +# End of https://www.toptal.com/developers/gitignore/api/kotlin,java,gradle,intellij diff --git a/app/.DS_Store b/app/.DS_Store deleted file mode 100644 index 87c7697165a8298384e1bd2e62af01a35fb01b1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!A{#i5S>j@aD;Jh zT>B;b4!qr+fFSLaM%9co`(|fn*6X*{u7`+Jf1LJ+d?NCo1{-ZOe-JZnbU_;IX#tg; zBc&a>q!GoGE<}rC85uBp=Tkys$|%RKJ?&pkL-ekwqVh|wFOpm*HkRlRxOLA0yGbSX z{NhE_Ek<@+5Uj_jWKv%9T)&g+JS(ei_f9pMtv4&H-m16eeT}~BNmRwvxa`NHGt)cP zI#1?yoSbCC$-sa6Nf%X|6~mED$kHJ~&QG%<)swy+7inp8BRAl+z4pNGOs9Lj?sm}I z-<@p-(+}@q@9pl-W^Hd{^WDdz!BzgF(7)_@N#UEevd%b%8yNh~Kci8R>*5lgdGtI= zP#6#fgn{K{fKM!MZF!d{D-#BUfq%q+IUfS3!H}_bXpaswwgdp0-$*O4O)X(^0>+TB zc8C#(a;ZRhYKM<0Fzi)~SaTJ3pjO}y Xi~$T8YlrYa!G9^G@C= zI@kWan)st&G#<8s!6iMNDHVrvI|$FC-lS97JW|Ohh?3qwCq&&ILar{Nq^l+^HBP!i zo$H$kr{EMirSf#zs8uU&ty!N{-0A)v>_)vgn-!d`?cIaZ&O_W!)U%;p;=9zcVQ~&G zFc$SZ`GX`@$sK(2_<0;7Gr$Zm1FOh@J?os}Di+5zF$2uNuNa{7!G#if7E^=z=)j;` z0ALPoEwJ^kKhPxy(6g8tga;x`D$t}#+hR!DIY^U^dC$hD22Hw<`psC!zFFE1McT$g zx;k7)&mfP?05kBJ0qXrACA$BQfA0Tv5D&}%Gq9Qr$XwfRw@{nCTbHV&yVix?KuIVr mHMmHDp|4`Z(p6lBYJuM+1L#>y4Z;KA9|27R56r-yGVlSdPE@Y| diff --git a/app/src/main/.DS_Store b/app/src/main/.DS_Store deleted file mode 100644 index 6da861aba7f2e579e2a3925364895fa6818c65d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5S?wSO({YT3OxqA7Hw@Qh?f}a!K)EHsMN$14UO5-rshxzIqMJkCH{`i z>~4ipy@^Pff!Q~iok_B9!%hYOggXse05t%xPzeha9KI2nCtZ<(@e~n-x<>@bSuF2^ zSOzQ6?D&ri(B5sr5G14!!}9*oM{$h0_n}0+{!(m^O-5#~--1ybP10ubT@;F?jm@%E zwyM^x_b4+j_0#FZ^~cwAbs=ROENnlxj0Rc1wtXU#)Q^(ESS3W^07GuBq9l}=E2l{~ zQMsO;uqsxiU#rh%omR79x4P|l!=4=;pxtSA=ktoSv%7zE-XF$KiF{FS4FbPUC0hm; z@QTLLnx4IJ63gTP{fnGMEF&|(3@`)h!hkv7tm?Wfh}XdkFay8F0G$suDxqUA*QkyT z98?Q{SVFfFwCT5za%_W+!CWKGpa`9cs8fY$F@#P>zir|igSkeX4#G4a!aP}+4n?S^ zDzY5}k8DIw1lL1lcc|8}`W@_uoDyi1msCTF& p6qjrKN;( diff --git a/app/src/main/kotlin/.DS_Store b/app/src/main/kotlin/.DS_Store deleted file mode 100644 index 4873712bcd5ed43ba69a430b8224cac32042ecce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO-sW-5Z!I7O({YT3OxqA7Hw@Qh?iLF!K)EHsMN$34aRKghdq=+&iaS^CH@_K zv%3+BdhsAwW?=SBW@k3c+pwDvLdedv-w>iAgm9pQxe|tNgyu=VNX~llKuK$;+b~uk zo@hCi|G)t4U0wuYD26BT(*7M{X6IdtF2qKKv+>aG@@qJZqw%C(f0MaJJcdbrB_fHS4Hp(jV(w z-%L0qr_`%d=kr#hUUM7m=A!1#_xE78n(f7+Sci8{#FtNi7=|GkAev zt9kNANvx7Pc(Uj$ijf&$2AF}>Wx$?zPI+}pmgko zE~ICWM`nN-SZ08FKS+h{|Kp$ge;vdFGr$b2CIhn2^}8L^X7AQ7RnlGSLT{iX6qg$O kOo5@VV#Lx_T!m_Z-z5X+SxgPW1K}S5O#=_iz@IYk0d;0l%K!iX diff --git a/gradle/.DS_Store b/gradle/.DS_Store deleted file mode 100644 index 6d74a8a66e3ac9a2ae76d03ea04342774a32b691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T32orijpkLXQhxi`E}R@e*S_cr~I2m73V1!I%wcY7V84v%Zi|;_K+l z?pDz1O+;lT%zl&kNwQzUZUz8^H|(teGyuRsC9KqO_(Uj9IwckBnL!l#8Y%Q4JB-y; z7*A!h<2N!u@2&;~WDr1#$J_f;Ax7x4*h5`;(Sp`gc32G3FxJ^mVH!t+yxo2lm6_`7 zT+OLD^Uj5TqY6I{^5MV>vNJk6Rw@oBb`YLK{i54g+*e5+L`gr>1yQe$Dd(qA(o=<} zhDmRrYkd=N>Q24eXpTmkJKHU}w!SfL$-H| diff --git a/trees/src/test/.DS_Store b/trees/src/test/.DS_Store deleted file mode 100644 index 8928e9fc1ec7001f279804729b40923c49c94191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5S?wSO({YT3Oz1(Em~VGh?iLF!K)EHsMN$34UO5-ruI+@IqMJkCH{`i z>~4iny@^Pfftk0Nok=op!)^uuM0?WP1E>IigG!hyq4`E=o^(la){{pR`W_*~XOX%O zA{8t}v*SN9Kx?-S15l7a1ljeYkK!10=R=Er{Y-2y9S`kVzXii694GbqyU67WYwJa) z=#-sX?@>*?#7`#Uwm-V2s|%%~U@`WC%dkJ~RyI#mocLkfAL)d!*T;~Xt1#}VX4l*@8d!sl~@dNr7Ig40EW`G%B23CauyT3W*RVj$;U7PT1*Y< ztpkm20T2u5)`DaDZKND&(Y2Tw#2FM}QV~t6uq}o#>FAd>&b62tH0dC0^C9e$h3!y; zemdS?>TnRQLAJ~QGmvF~dOws3o&Tpl_y0PG4Ku(DtR@4Z(D6ELtj(USOI6ZYD^c%I qNhmHg_?3bKeH3FX9mQ2tE$EjtKy)pp2GN7UKLVNtHq5}EGVlpV8CXLA diff --git a/trees/src/test/kotlin/.DS_Store b/trees/src/test/kotlin/.DS_Store deleted file mode 100644 index ffbb7f5be6822db33d5d6147075e37b515c44754..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5Z!I7O(;SR3OxqA7LBbI#7m6z;MIs8RBB?124hy5)Er77XZ<0+#NW}G z-HlMH7Y~AE24>%8c4ou84Lcde822X84r3N$%m78KnJ|1KxQ@CYHEpQ_a@<4QhKUH_ z3AZEp4-DYh)mg|gmY*bZ=XVGzTOU$nBHAk~vL}7YZ{brCXSw6N^IE;Jvf4D7M$5SN z??vI~K|aa6V0?*BXF?=lH4nn`cvK9n^&^qyL7a}pG9Zpd;Bs{lr;#W;F-fCL#`8US65r9!ZPa1{!uLb+}+xb7Xe3Ws%9`=vONI l3W13%#o&vjxD6@=>^4pSU5%weuz=8yfTV#2V&G31_yoHfQsw{v From 25a6fb2e34e858060fd319d4fd86908bc2751182 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 25 Apr 2023 21:44:45 +0300 Subject: [PATCH 089/118] fix: rework neo4j interaction --- trees/src/main/kotlin/bst/db/Neo4j.kt | 28 ---- .../kotlin/bst/db/Neo4j\320\241ontroller.kt" | 130 ++++++++++++++++++ trees/src/main/kotlin/bst/db/SQLController.kt | 3 +- .../main/kotlin/bst/db/SerializableNode.kt | 5 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 34 +---- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 1 + 6 files changed, 137 insertions(+), 64 deletions(-) delete mode 100644 trees/src/main/kotlin/bst/db/Neo4j.kt create mode 100644 "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" diff --git a/trees/src/main/kotlin/bst/db/Neo4j.kt b/trees/src/main/kotlin/bst/db/Neo4j.kt deleted file mode 100644 index f742d53..0000000 --- a/trees/src/main/kotlin/bst/db/Neo4j.kt +++ /dev/null @@ -1,28 +0,0 @@ -package bst.db - -import bst.* -import org.neo4j.ogm.config.Configuration -import org.neo4j.ogm.session.SessionFactory -import java.util.NoSuchElementException - -class Neo4j(config: Configuration) { - private val sessionFactory = SessionFactory(config, "bst") - private val session = sessionFactory.openSession() - - fun saveTree(tree: AbstractBST<*, *, *>) { - session.save(tree) - session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) - } - - fun removeTree(name: String) { - session.query("MATCH (n)-[r *0..]->(m) WHERE n.property = ${name} DETACH DELETE m", mapOf()) - ?: throw NoSuchElementException("No tree with that name has been found") - } - - fun getTree(name: String) = session.queryForObject( - AbstractBST::class.java, "MATCH (n)-[r *1..]-(m) " + - "WHERE n.property = ${name} RETURN n, r, m", mapOf("name" to name)) ?: throw NoSuchElementException("No tree with that name has been found") - - fun getNames() = session.query("MATCH (n: AbstractBST) return n", mapOf()) - -} \ No newline at end of file diff --git "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" new file mode 100644 index 0000000..283b3f1 --- /dev/null +++ "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" @@ -0,0 +1,130 @@ +package bst.db + +import bst.RedBlackTree +import bst.nodes.RBTNode +import bst.nodes.RBTNode.Color +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity +import org.neo4j.ogm.annotation.Relationship +import org.neo4j.ogm.config.Configuration +import org.neo4j.ogm.session.SessionFactory +import java.util.NoSuchElementException + +@NodeEntity +class TreeEntity ( + var treeName: String = "", + var rootNode: TreeNodeEntity? = null, +) { + @Id + @GeneratedValue + var id: Long? = null +} + +@NodeEntity +class TreeNodeEntity( + val key: String, + val value : String, + val x : Double = 0.0, + val y: Double = 0.0, + var metadata: String? = null, + @Relationship(type = "LEFT") + var left: TreeNodeEntity? = null, + @Relationship(type = "RIGHT") + var right: TreeNodeEntity? = null +) { + @Id + @GeneratedValue + var id: Long? = null +} + +class Neo4j(config: Configuration) { + private val sessionFactory = SessionFactory(config, "bst") + private val session = sessionFactory.openSession() + + private fun RBTNode<*, *>.toSerializableNode(): SerializableNode { + return SerializableNode(key.toString(), value.toString(), 0.0, 0.0, + color.toString(), left?.toSerializableNode(), right?.toSerializableNode()) + } + + private fun RedBlackTree<*, *>.toSerializableTree(): SerializableTree { + return SerializableTree(treeName, rootNode?.toSerializableNode()) + } + + private fun TreeNodeEntity.toSerializableNode(): SerializableNode { + return SerializableNode(key, value, x, y, metadata, left?.toSerializableNode(), right?.toSerializableNode()) + } + + private fun TreeEntity.toSerializableTree(): SerializableTree { + return SerializableTree(treeName, rootNode?.toSerializableNode()) + } + + private fun SerializableNode.toNodeEntity(): TreeNodeEntity { + return TreeNodeEntity(key, value, x, y, metadata, leftNode?.toNodeEntity(), rightNode?.toNodeEntity()) + } + + private fun SerializableTree.toTreeEntity(): TreeEntity { + return TreeEntity(treeName, rootNode?.toNodeEntity()) + } + + private fun deserializeNode(node: SerializableNode?): RBTNode? { + node ?: return null + val rbtNode = RBTNode( + key = node.key.toInt(), + value = node.value, + color = deserializeMetadata(node.metadata) + ) + rbtNode.left = deserializeNode(node.leftNode) + rbtNode.right = deserializeNode(node.rightNode) + return rbtNode + } + + private fun deserializeTree(tree: SerializableTree): RedBlackTree { + val rbTree = RedBlackTree() + rbTree.rootNode = deserializeNode(tree.rootNode) + rbTree.treeName = tree.treeName + return rbTree + } + + private fun deserializeMetadata(metadata: String?): Color { + return when (metadata) { + "RED" -> Color.RED + "BLACK" -> Color.BLACK + else -> throw NoSuchFieldException("Node's color must be either RED or BLACK") + } + } + + fun saveTree(tree: RedBlackTree<*, *>) { + removeTree(tree.treeName) + val entityTree = tree.toSerializableTree().toTreeEntity() + session.save(entityTree) + session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) + } + + fun removeTree(name: String) { + session.query( + "MATCH (n)-[r *0..]->(m) " + + "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name)) + } + + fun loadDeserialized(name: String): RedBlackTree { + val tree = getTree(name) + return deserializeTree(tree.toSerializableTree()) + } + + // Could be useful for underlying logic of GUI + fun loadSerialized(name: String): SerializableTree { + val tree = getTree(name) + return tree.toSerializableTree() + } + + private fun getTree(name: String): TreeEntity { + return session.queryForObject( + TreeEntity::class.java, "MATCH (n)-[r *1..]-(m) " + + "WHERE n.treeName = \$name RETURN n, r, m", mapOf("name" to name)) ?: + throw NoSuchElementException("No tree with that name has been found") + } + + fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()). + flatMap { it.values.map { value -> value.toString() } } +} diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/SQLController.kt index a42d989..120e3f0 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/SQLController.kt @@ -79,6 +79,7 @@ class SQLController { this@getSerializedNode.value, this@getSerializedNode.x, this@getSerializedNode.y, + null, this@getSerializedNode.left?.getSerializedNode(treeDao), this@getSerializedNode.right?.getSerializedNode(treeDao), ) @@ -119,7 +120,7 @@ class SQLController { private fun deserializeTree(tree: SerializableTree?): BSTree?{ if (tree != null) { - if (isNumeric(tree.rootNode.key)){ + if (isNumeric(tree.rootNode!!.key)){ val rootNode = deserializeNodeDoubleKey(tree.rootNode) val deserializedTree:BSTree = BSTree(rootNode?.key, rootNode?.value) deserializedTree.rootNode = rootNode diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/SerializableNode.kt index f9a5b8d..2d969af 100644 --- a/trees/src/main/kotlin/bst/db/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/SerializableNode.kt @@ -5,8 +5,9 @@ import kotlinx.serialization.* class SerializableNode( val key: String, val value : String, - val x : Double = 0.0, - val y: Double = 0.0, + var x : Double = 0.0, + var y: Double = 0.0, + var metadata: String? = null, var leftNode: SerializableNode? = null, var rightNode: SerializableNode? = null, ) diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index b49755a..facf88a 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -1,41 +1,9 @@ package bst.nodes -import org.neo4j.ogm.annotation.GeneratedValue -import org.neo4j.ogm.annotation.Id -import org.neo4j.ogm.annotation.NodeEntity -import org.neo4j.ogm.annotation.PostLoad -import org.neo4j.ogm.annotation.Property -@NodeEntity abstract class BinaryNode, V, Self : BinaryNode>( var key: K, var value: V ) { - @Id - @GeneratedValue - var id: Long? = null var left: Self? = null var right: Self? = null - var x: Double = 0.0 - var y: Double = 0.0 - - @Property(name = "key") - private val clavis = key as Any - - @PostLoad - fun keyInit() { - key = clavis as K - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as BinaryNode<*, *, *> - - if (id != other.id) return false - if (key != other.key) return false - - return true - } - -} +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index 21e79f6..a6e6e86 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -6,6 +6,7 @@ class RBTNode, V> ( var color: Color = Color.RED ) : BinaryNode>(key, value) { internal fun child(f: Boolean) = if (f) right else left + enum class Color { RED, BLACK } From 4df220485e848a0be31d85a447b99d2e7d6bf8dc Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 25 Apr 2023 22:06:20 +0300 Subject: [PATCH 090/118] fix: null safety --- "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" | 5 ++++- trees/src/main/kotlin/bst/db/SerializableNode.kt | 2 +- trees/src/main/kotlin/bst/db/SerializableTree.kt | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" index 283b3f1..a837df9 100644 --- "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" +++ "b/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" @@ -42,7 +42,10 @@ class Neo4j(config: Configuration) { private val sessionFactory = SessionFactory(config, "bst") private val session = sessionFactory.openSession() - private fun RBTNode<*, *>.toSerializableNode(): SerializableNode { + private fun RBTNode<*, *>?.toSerializableNode(): SerializableNode? { + if (this == null) { + return null + } return SerializableNode(key.toString(), value.toString(), 0.0, 0.0, color.toString(), left?.toSerializableNode(), right?.toSerializableNode()) } diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/SerializableNode.kt index 2d969af..f8a77f8 100644 --- a/trees/src/main/kotlin/bst/db/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/SerializableNode.kt @@ -2,7 +2,7 @@ package bst.db import kotlinx.serialization.* @Serializable -class SerializableNode( +class SerializableNode ( val key: String, val value : String, var x : Double = 0.0, diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/SerializableTree.kt index de3fed1..b5e00b8 100644 --- a/trees/src/main/kotlin/bst/db/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/SerializableTree.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable class SerializableTree ( var treeName: String, - var rootNode: SerializableNode, + var rootNode: SerializableNode?, ) From 0ee304e20341e225bec8ae3df4c969bc6e71a5ce Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 22:44:24 +0300 Subject: [PATCH 091/118] fix:remove the db files in folders --- .../bst/db/{ => controllers}/JsonController.kt | 2 +- .../db/controllers/Neo4j\320\241ontroller.kt" | 4 +++- .../bst/db/{ => controllers}/SQLController.kt | 16 +++++++++------- .../src/main/kotlin/bst/db/{ => models}/Node.kt | 2 +- .../src/main/kotlin/bst/db/{ => models}/Nodes.kt | 3 +-- .../src/main/kotlin/bst/db/{ => models}/Tree.kt | 2 +- .../src/main/kotlin/bst/db/{ => models}/Trees.kt | 4 ++-- .../{ => serializeClasses}/SerializableNode.kt | 2 +- .../{ => serializeClasses}/SerializableTree.kt | 3 ++- 9 files changed, 21 insertions(+), 17 deletions(-) rename trees/src/main/kotlin/bst/db/{ => controllers}/JsonController.kt (96%) rename "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" => "trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" (97%) rename trees/src/main/kotlin/bst/db/{ => controllers}/SQLController.kt (92%) rename trees/src/main/kotlin/bst/db/{ => models}/Node.kt (96%) rename trees/src/main/kotlin/bst/db/{ => models}/Nodes.kt (93%) rename trees/src/main/kotlin/bst/db/{ => models}/Tree.kt (95%) rename trees/src/main/kotlin/bst/db/{ => models}/Trees.kt (81%) rename trees/src/main/kotlin/bst/db/{ => serializeClasses}/SerializableNode.kt (90%) rename trees/src/main/kotlin/bst/db/{ => serializeClasses}/SerializableTree.kt (65%) diff --git a/trees/src/main/kotlin/bst/db/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt similarity index 96% rename from trees/src/main/kotlin/bst/db/JsonController.kt rename to trees/src/main/kotlin/bst/db/controllers/JsonController.kt index 221a114..cdc3f32 100644 --- a/trees/src/main/kotlin/bst/db/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -1,4 +1,4 @@ -package bst.db +package bst.db.controllers import bst.AVLTree import com.google.gson.Gson diff --git "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" "b/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" similarity index 97% rename from "trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" rename to "trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" index a837df9..6442c7f 100644 --- "a/trees/src/main/kotlin/bst/db/Neo4j\320\241ontroller.kt" +++ "b/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" @@ -1,6 +1,8 @@ -package bst.db +package bst.db.controllers import bst.RedBlackTree +import bst.db.serializeClasses.SerializableNode +import bst.db.serializeClasses.SerializableTree import bst.nodes.RBTNode import bst.nodes.RBTNode.Color import org.neo4j.ogm.annotation.GeneratedValue diff --git a/trees/src/main/kotlin/bst/db/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt similarity index 92% rename from trees/src/main/kotlin/bst/db/SQLController.kt rename to trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 120e3f0..e779295 100644 --- a/trees/src/main/kotlin/bst/db/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -1,11 +1,13 @@ -package bst.db +package bst.db.controllers import bst.BSTree +import bst.db.serializeClasses.SerializableNode +import bst.db.serializeClasses.SerializableTree import bst.nodes.BSTNode -import db.Node +import bst.db.models.Node import org.jetbrains.exposed.sql.* -import db.Trees -import db.Nodes -import db.Tree +import bst.db.models.Trees +import bst.db.models.Nodes +import bst.db.models.Tree import org.jetbrains.exposed.sql.transactions.transaction @@ -88,7 +90,7 @@ class SQLController { private fun findTree(treeName: String): SerializableTree?{ connectDB() - val treeDAO = Tree.find{Trees.name eq treeName}.firstOrNull() ?: return null + val treeDAO = Tree.find{ Trees.name eq treeName}.firstOrNull() ?: return null return treeDAO.rootNode?.getSerializedNode(treeDAO)?.let { SerializableTree( treeName, @@ -132,7 +134,7 @@ class SQLController { } fun getTree(treeName: String): BSTree? { - var deserializedTree:SerializableTree? = null + var deserializedTree: SerializableTree? = null transaction { deserializedTree = findTree(treeName) } diff --git a/trees/src/main/kotlin/bst/db/Node.kt b/trees/src/main/kotlin/bst/db/models/Node.kt similarity index 96% rename from trees/src/main/kotlin/bst/db/Node.kt rename to trees/src/main/kotlin/bst/db/models/Node.kt index 15727d6..4606481 100644 --- a/trees/src/main/kotlin/bst/db/Node.kt +++ b/trees/src/main/kotlin/bst/db/models/Node.kt @@ -1,4 +1,4 @@ -package db +package bst.db.models import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.dao.id.EntityID diff --git a/trees/src/main/kotlin/bst/db/Nodes.kt b/trees/src/main/kotlin/bst/db/models/Nodes.kt similarity index 93% rename from trees/src/main/kotlin/bst/db/Nodes.kt rename to trees/src/main/kotlin/bst/db/models/Nodes.kt index fca7eb1..78c51fd 100644 --- a/trees/src/main/kotlin/bst/db/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/models/Nodes.kt @@ -1,5 +1,4 @@ -package db -import db.Trees.default +package bst.db.models import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption diff --git a/trees/src/main/kotlin/bst/db/Tree.kt b/trees/src/main/kotlin/bst/db/models/Tree.kt similarity index 95% rename from trees/src/main/kotlin/bst/db/Tree.kt rename to trees/src/main/kotlin/bst/db/models/Tree.kt index b52af8f..246e3e6 100644 --- a/trees/src/main/kotlin/bst/db/Tree.kt +++ b/trees/src/main/kotlin/bst/db/models/Tree.kt @@ -1,4 +1,4 @@ -package db +package bst.db.models import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass diff --git a/trees/src/main/kotlin/bst/db/Trees.kt b/trees/src/main/kotlin/bst/db/models/Trees.kt similarity index 81% rename from trees/src/main/kotlin/bst/db/Trees.kt rename to trees/src/main/kotlin/bst/db/models/Trees.kt index df464e5..8502d91 100644 --- a/trees/src/main/kotlin/bst/db/Trees.kt +++ b/trees/src/main/kotlin/bst/db/models/Trees.kt @@ -1,6 +1,6 @@ -package db +package bst.db.models -import db.Nodes +import bst.db.models.Nodes import org.jetbrains.exposed.dao.id.IntIdTable object Trees : IntIdTable("trees") { diff --git a/trees/src/main/kotlin/bst/db/SerializableNode.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt similarity index 90% rename from trees/src/main/kotlin/bst/db/SerializableNode.kt rename to trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt index f8a77f8..5baa597 100644 --- a/trees/src/main/kotlin/bst/db/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt @@ -1,4 +1,4 @@ -package bst.db +package bst.db.serializeClasses import kotlinx.serialization.* @Serializable diff --git a/trees/src/main/kotlin/bst/db/SerializableTree.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt similarity index 65% rename from trees/src/main/kotlin/bst/db/SerializableTree.kt rename to trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt index b5e00b8..aabb3ba 100644 --- a/trees/src/main/kotlin/bst/db/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt @@ -1,5 +1,6 @@ -package bst.db +package bst.db.serializeClasses +import bst.db.serializeClasses.SerializableNode import kotlinx.serialization.Serializable @Serializable From 737623ca0efa60dd8ab21fa5369297c6ebe87ac6 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Tue, 25 Apr 2023 23:40:07 +0300 Subject: [PATCH 092/118] fix warning associated with sf4j --- trees/build.gradle.kts | 5 +++-- trees/src/main/kotlin/bst/db/controllers/SQLController.kt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 91c0018..8e1edb2 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -19,8 +19,10 @@ dependencies { // Use the JUnit 5 integration. testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + implementation ("io.github.microutils:kotlin-logging-jvm:2.0.11") - implementation("org.slf4j", "slf4j-simple", "1.7.29") + + implementation("org.slf4j", "slf4j-simple", "2.0.0") implementation("org.neo4j:neo4j-ogm-core:4.0.5") @@ -36,7 +38,6 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.41.2.1") - implementation("io.github.microutils", "kotlin-logging-jvm", "2.0.6") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.postgresql:postgresql:42.5.4") diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index e779295..ffb33e1 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -140,4 +140,4 @@ class SQLController { } return deserializeTree(deserializedTree) } -} \ No newline at end of file +} From ad8989056192f2a31ad3fd841127d8cd9eb8e829 Mon Sep 17 00:00:00 2001 From: ancavar Date: Tue, 25 Apr 2023 23:33:15 +0300 Subject: [PATCH 093/118] refactor: better test names, remove debugging info --- trees/src/main/kotlin/bst/BSTree.kt | 27 ------------ .../bst/db/controllers/Neo4jController.kt | 41 +++---------------- .../kotlin/bst/db/models/neo4j/TreeEntity.kt | 15 +++++++ .../bst/db/models/neo4j/TreeNodeEntity.kt | 23 +++++++++++ .../kotlin/bst/db/models/{ => sql}/Node.kt | 6 +-- .../kotlin/bst/db/models/{ => sql}/Nodes.kt | 2 +- .../kotlin/bst/db/models/{ => sql}/Tree.kt | 3 +- .../kotlin/bst/db/models/{ => sql}/Trees.kt | 3 +- trees/src/test/kotlin/bst/AvlTest.kt | 34 +++++++-------- trees/src/test/kotlin/bst/BstTest.kt | 4 +- trees/src/test/kotlin/bst/RbtTest.kt | 18 ++++---- 11 files changed, 77 insertions(+), 99 deletions(-) rename "trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" => trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt (78%) create mode 100644 trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt create mode 100644 trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt rename trees/src/main/kotlin/bst/db/models/{ => sql}/Node.kt (91%) rename trees/src/main/kotlin/bst/db/models/{ => sql}/Nodes.kt (95%) rename trees/src/main/kotlin/bst/db/models/{ => sql}/Tree.kt (91%) rename trees/src/main/kotlin/bst/db/models/{ => sql}/Trees.kt (81%) diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index c62b7a9..04067e7 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,10 +1,6 @@ package bst import bst.nodes.BSTNode -import com.google.gson.Gson -import java.io.FileReader -import java.io.FileWriter - class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) init { @@ -16,27 +12,4 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val fun setName(treeName: String){ this.treeName = treeName } - - - -} - - - -fun main(){ - val test_data = BSTree("121", "dgs") - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") - test_data.insert("127", "fggsg") - test_data.insert("123", "fggsg") - test_data.setName("etst") -// test_data.saveTreeToJson() -// test_data.saveTree() -// readFromJson("tree_1.json") -// test_data.save_node(test_data.rootNode) -// val json = Gson().toJson(test_data) -// println(json) -// val ret_obj = Gson().fromJson, Any>>(json, BSTree::class.java) - } diff --git "a/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt similarity index 78% rename from "trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" rename to trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index 6442c7f..cadb499 100644 --- "a/trees/src/main/kotlin/bst/db/controllers/Neo4j\320\241ontroller.kt" +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -1,46 +1,17 @@ package bst.db.controllers import bst.RedBlackTree +import bst.db.models.neo4j.TreeEntity +import bst.db.models.neo4j.TreeNodeEntity import bst.db.serializeClasses.SerializableNode import bst.db.serializeClasses.SerializableTree import bst.nodes.RBTNode import bst.nodes.RBTNode.Color -import org.neo4j.ogm.annotation.GeneratedValue -import org.neo4j.ogm.annotation.Id -import org.neo4j.ogm.annotation.NodeEntity -import org.neo4j.ogm.annotation.Relationship import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory import java.util.NoSuchElementException -@NodeEntity -class TreeEntity ( - var treeName: String = "", - var rootNode: TreeNodeEntity? = null, -) { - @Id - @GeneratedValue - var id: Long? = null -} - -@NodeEntity -class TreeNodeEntity( - val key: String, - val value : String, - val x : Double = 0.0, - val y: Double = 0.0, - var metadata: String? = null, - @Relationship(type = "LEFT") - var left: TreeNodeEntity? = null, - @Relationship(type = "RIGHT") - var right: TreeNodeEntity? = null -) { - @Id - @GeneratedValue - var id: Long? = null -} - -class Neo4j(config: Configuration) { +class Neo4jController(config: Configuration) { private val sessionFactory = SessionFactory(config, "bst") private val session = sessionFactory.openSession() @@ -109,7 +80,7 @@ class Neo4j(config: Configuration) { fun removeTree(name: String) { session.query( "MATCH (n)-[r *0..]->(m) " + - "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name)) + "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name)) } fun loadDeserialized(name: String): RedBlackTree { @@ -127,9 +98,9 @@ class Neo4j(config: Configuration) { return session.queryForObject( TreeEntity::class.java, "MATCH (n)-[r *1..]-(m) " + "WHERE n.treeName = \$name RETURN n, r, m", mapOf("name" to name)) ?: - throw NoSuchElementException("No tree with that name has been found") + throw NoSuchElementException("No tree with that name has been found") } fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()). - flatMap { it.values.map { value -> value.toString() } } + flatMap { it.values.map { value -> value.toString() } } } diff --git a/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt b/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt new file mode 100644 index 0000000..9359aa7 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt @@ -0,0 +1,15 @@ +package bst.db.models.neo4j + +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity + +@NodeEntity +class TreeEntity ( + var treeName: String = "", + var rootNode: TreeNodeEntity? = null, +) { + @Id + @GeneratedValue + var id: Long? = null +} diff --git a/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt b/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt new file mode 100644 index 0000000..dd9a9ed --- /dev/null +++ b/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt @@ -0,0 +1,23 @@ +package bst.db.models.neo4j + +import org.neo4j.ogm.annotation.GeneratedValue +import org.neo4j.ogm.annotation.Id +import org.neo4j.ogm.annotation.NodeEntity +import org.neo4j.ogm.annotation.Relationship + +@NodeEntity +class TreeNodeEntity( + val key: String, + val value : String, + val x : Double = 0.0, + val y: Double = 0.0, + var metadata: String? = null, + @Relationship(type = "LEFT") + var left: TreeNodeEntity? = null, + @Relationship(type = "RIGHT") + var right: TreeNodeEntity? = null +) { + @Id + @GeneratedValue + var id: Long? = null +} diff --git a/trees/src/main/kotlin/bst/db/models/Node.kt b/trees/src/main/kotlin/bst/db/models/sql/Node.kt similarity index 91% rename from trees/src/main/kotlin/bst/db/models/Node.kt rename to trees/src/main/kotlin/bst/db/models/sql/Node.kt index 4606481..722df7a 100644 --- a/trees/src/main/kotlin/bst/db/models/Node.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Node.kt @@ -1,4 +1,4 @@ -package bst.db.models +package bst.db.models.sql import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.dao.id.EntityID @@ -14,7 +14,3 @@ class Node(id: EntityID): IntEntity(id) { override fun toString(): String = "Node(key = $key, value=$value, x=$x, y=$y, left=$left, right=$right, tree=$tree)" } - -//class NodeSerialize(){ -// -//} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/models/Nodes.kt b/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt similarity index 95% rename from trees/src/main/kotlin/bst/db/models/Nodes.kt rename to trees/src/main/kotlin/bst/db/models/sql/Nodes.kt index 78c51fd..ee59825 100644 --- a/trees/src/main/kotlin/bst/db/models/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt @@ -1,4 +1,4 @@ -package bst.db.models +package bst.db.models.sql import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption diff --git a/trees/src/main/kotlin/bst/db/models/Tree.kt b/trees/src/main/kotlin/bst/db/models/sql/Tree.kt similarity index 91% rename from trees/src/main/kotlin/bst/db/models/Tree.kt rename to trees/src/main/kotlin/bst/db/models/sql/Tree.kt index 246e3e6..e86095c 100644 --- a/trees/src/main/kotlin/bst/db/models/Tree.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Tree.kt @@ -1,4 +1,4 @@ -package bst.db.models +package bst.db.models.sql import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass @@ -11,4 +11,3 @@ class Tree(id: EntityID): IntEntity(id){ override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" } -//fun createTree diff --git a/trees/src/main/kotlin/bst/db/models/Trees.kt b/trees/src/main/kotlin/bst/db/models/sql/Trees.kt similarity index 81% rename from trees/src/main/kotlin/bst/db/models/Trees.kt rename to trees/src/main/kotlin/bst/db/models/sql/Trees.kt index 8502d91..f7a3fae 100644 --- a/trees/src/main/kotlin/bst/db/models/Trees.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Trees.kt @@ -1,6 +1,5 @@ -package bst.db.models +package bst.db.models.sql -import bst.db.models.Nodes import org.jetbrains.exposed.dao.id.IntIdTable object Trees : IntIdTable("trees") { diff --git a/trees/src/test/kotlin/bst/AvlTest.kt b/trees/src/test/kotlin/bst/AvlTest.kt index 03836ba..fba8807 100644 --- a/trees/src/test/kotlin/bst/AvlTest.kt +++ b/trees/src/test/kotlin/bst/AvlTest.kt @@ -10,17 +10,19 @@ import org.junit.jupiter.api.Test import kotlin.math.max class AvlTest { - private fun, V> isAvl(node: AVLNode?): Boolean { + private fun , V> isAvl(node: AVLNode?): Boolean { if (node == null) return true return isAvl(node.left) && isAvl(node.right) && (subtreeHeight(node.left) - subtreeHeight(node.right) in -1..1) } - private fun, V> subtreeHeight(node: AVLNode?): Int { + + private fun , V> subtreeHeight(node: AVLNode?): Int { if (node == null) return 0 return max(subtreeHeight(node.left), subtreeHeight(node.right)) + 1 } - private fun, V> countNodes(node: AVLNode?): Int { + + private fun , V> countNodes(node: AVLNode?): Int { if (node == null) return 0 return countNodes(node.left) + countNodes(node.right) + 1 } @@ -34,7 +36,7 @@ class AvlTest { } @Test - fun `Empty tree`() { + fun `validate tree if empty`() { assertNull(tree.rootNode) assertEquals(0, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) @@ -43,7 +45,7 @@ class AvlTest { @Nested inner class InsertionTests { @Test - fun `Simple insertion`() { + fun `insert a few key-value pairs`() { tree.insert(2, 2) tree.insert(3, 3) tree.insert(1, 1) @@ -57,7 +59,7 @@ class AvlTest { } @Test - fun `Left-left rotation case`() { + fun `check left-left rotation case`() { tree.insert(3, 3) tree.insert(2, 2) tree.insert(1, 1) @@ -71,7 +73,7 @@ class AvlTest { } @Test - fun `Left-right rotation case`() { + fun `check left-right rotation case`() { tree.insert(5, 5) tree.insert(3, 3) tree.insert(4, 4) @@ -85,7 +87,7 @@ class AvlTest { } @Test - fun `Right-right rotation case`() { + fun `check right-right rotation case`() { tree.insert(3, 3) tree.insert(4, 4) tree.insert(5, 5) @@ -99,7 +101,7 @@ class AvlTest { } @Test - fun `Right-left rotation case`() { + fun `check right-left rotation case`() { tree.insert(3, 3) tree.insert(5, 5) tree.insert(4, 4) @@ -113,7 +115,7 @@ class AvlTest { } @Test - fun `Same key inserted twice`() { + fun `insert same key twice`() { tree.insert(1, 1) tree.insert(1, 2) assertEquals(1, countNodes(tree.rootNode)) @@ -121,7 +123,7 @@ class AvlTest { } @Test - fun `Multiple insertions`() { + fun `insert many key-value pairs`() { values.forEach { tree.insert(it, it) } assertEquals(1000, countNodes(tree.rootNode)) assertTrue(isAvl(tree.rootNode)) @@ -131,13 +133,13 @@ class AvlTest { @Nested inner class FindTests { @Test - fun `Find test`() { + fun `find a node by its key`() { values.forEach { tree.insert(it, it) } values.forEach { assertEquals(it, tree.find(it)) } } @Test - fun `Find by non-existing key`() { + fun `find by non-existing key`() { values.forEach { tree.insert(it, it) } assertNull(tree.find(-1)) } @@ -146,7 +148,7 @@ class AvlTest { @Nested inner class DeletionTests { @Test - fun `Single deletion`() { + fun `delete one key-value pair`() { tree.insert(1, 1) tree.remove(1) assertEquals(0, countNodes(tree.rootNode)) @@ -154,7 +156,7 @@ class AvlTest { } @Test - fun `Non-existent key deletion`() { + fun `delete non-existent key`() { tree.insert(3, 3) tree.insert(4, 4) tree.insert(1, 1) @@ -163,7 +165,7 @@ class AvlTest { } @Test - fun `Multiple deletions`() { + fun `delete many key-value pairs`() { values.forEach { tree.insert(it, it) } values.take(500).forEach { tree.remove(it) } assertEquals(500, countNodes(tree.rootNode)) diff --git a/trees/src/test/kotlin/bst/BstTest.kt b/trees/src/test/kotlin/bst/BstTest.kt index e294f13..63013d2 100644 --- a/trees/src/test/kotlin/bst/BstTest.kt +++ b/trees/src/test/kotlin/bst/BstTest.kt @@ -34,14 +34,14 @@ class BstTest { } @Test - fun `Empty tree`() { + fun `validate tree if empty`() { assertNull(tree.rootNode) assertEquals(0, countNodes(tree.rootNode)) assertTrue(isBst(tree.rootNode)) } @Test - fun `Check invariants after each action`() { + fun `verify invariants after each action`() { val values = IntArray(1000) { it + 1 } values.shuffle() values.forEach { diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index 2445973..c76b9bb 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -65,14 +65,14 @@ class RbtTest { } @Test - fun `Empty tree`() { + fun `validate tree if empty`() { assertNull(tree.rootNode) assertEquals(0, countNodes(tree.rootNode)) assertNotEquals(0, isRbt(tree.rootNode)) } @Test - fun `Check invariants after each action`() { + fun `verify invariants after each action`() { values.take(1000).forEach { tree.insert(it, it.toString()) assertNotEquals(0, isRbt(tree.rootNode)) @@ -87,7 +87,7 @@ class RbtTest { @Nested inner class InsertionTests { @Test - fun `Single insertion`() { + fun `insert single key-value pair`() { tree.insert(1, "A") assertEquals(1, tree.rootNode?.key) assertEquals(1, countNodes(tree.rootNode)) @@ -95,7 +95,7 @@ class RbtTest { } @Test - fun `Bigger insertion`() { + fun `insert a few key-value pairs`() { tree.insert(19, "S") tree.insert(5, "E") tree.insert(1, "A") @@ -109,7 +109,7 @@ class RbtTest { } @Test - fun `Same key inserted twice`() { + fun `insert same key twice`() { tree.insert(1, "A") tree.insert(1, "B") assertEquals(1, countNodes(tree.rootNode)) @@ -121,7 +121,7 @@ class RbtTest { @Nested inner class DeleteTests { @Test - fun `Single deletion`() { + fun `delete one key-value pair`() { tree.insert(2, "B") tree.remove(2) assertEquals(0, countNodes(tree.rootNode)) @@ -129,7 +129,7 @@ class RbtTest { } @Test - fun `Non-existent key deletion`() { + fun `delete non-existent key-value pair`() { tree.insert(2, "B") tree.remove(3) assertEquals(1, countNodes(tree.rootNode)) @@ -137,7 +137,7 @@ class RbtTest { } @Test - fun `Multiple deletions`() { + fun `delete a few key-value pairs`() { tree.insert(19, "S") tree.insert(5, "E") tree.insert(1, "A") @@ -153,7 +153,7 @@ class RbtTest { } @Test - fun `Many deletions`() { + fun `delete many key-value pairs`() { values.shuffle() values.forEach { tree.insert(it, it.toString()) } values.take(500_000).forEach { tree.remove(it) } From 10ad5bbe8fe8b8ba6156f29d3c9905cafa775ac0 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 26 Apr 2023 00:24:51 +0300 Subject: [PATCH 094/118] feat: add logger to neo4j --- trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt | 1 + trees/src/main/resources/simplelogger.properties | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 trees/src/main/resources/simplelogger.properties diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index cadb499..cc5b7ba 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -15,6 +15,7 @@ class Neo4jController(config: Configuration) { private val sessionFactory = SessionFactory(config, "bst") private val session = sessionFactory.openSession() + private fun RBTNode<*, *>?.toSerializableNode(): SerializableNode? { if (this == null) { return null diff --git a/trees/src/main/resources/simplelogger.properties b/trees/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..8c3588c --- /dev/null +++ b/trees/src/main/resources/simplelogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.defaultLogLevel=info +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.log.org.neo4j.ogm.drivers.bolt.request.BoltRequest=debug From 4a940a4df55c5770df76f9fe9fd0a66c517b1ae6 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 26 Apr 2023 01:02:15 +0300 Subject: [PATCH 095/118] refactor: ctrl+shift+alt+l all files --- .github/workflows/linter.yml | 2 +- trees/build.gradle.kts | 4 +- trees/src/main/kotlin/bst/AVLTree.kt | 9 +- trees/src/main/kotlin/bst/AbstractBST.kt | 13 +-- trees/src/main/kotlin/bst/BSTree.kt | 7 +- trees/src/main/kotlin/bst/RedBlackTree.kt | 110 ++++++++++-------- .../bst/db/controllers/JsonController.kt | 11 +- .../bst/db/controllers/Neo4jController.kt | 31 ++--- .../bst/db/controllers/SQLController.kt | 61 +++++----- .../kotlin/bst/db/models/neo4j/TreeEntity.kt | 2 +- .../bst/db/models/neo4j/TreeNodeEntity.kt | 10 +- .../src/main/kotlin/bst/db/models/sql/Node.kt | 6 +- .../main/kotlin/bst/db/models/sql/Nodes.kt | 1 + .../src/main/kotlin/bst/db/models/sql/Tree.kt | 5 +- .../db/serializeClasses/SerializableNode.kt | 9 +- .../db/serializeClasses/SerializableTree.kt | 5 +- trees/src/main/kotlin/bst/nodes/RBTNode.kt | 2 +- trees/src/test/kotlin/bst/RbtTest.kt | 2 +- 18 files changed, 160 insertions(+), 130 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index bd49cee..fc119eb 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,7 +1,7 @@ name: Lint Codebase on: - push: + push: pull_request: jobs: diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index 8e1edb2..b061174 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -3,7 +3,7 @@ plugins { id("org.jetbrains.kotlin.jvm") version "1.8.10" id("jacoco") id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" - id ("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" + id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" } @@ -19,7 +19,7 @@ dependencies { // Use the JUnit 5 integration. testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") - implementation ("io.github.microutils:kotlin-logging-jvm:2.0.11") + implementation("io.github.microutils:kotlin-logging-jvm:2.0.11") implementation("org.slf4j", "slf4j-simple", "2.0.0") diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index e135fd9..7252fc0 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -22,9 +22,11 @@ class AVLTree, V> : BalancingTree>() { private fun getHeight(node: AVLNode?): Int { return node?.height ?: -1 } + private fun updateHeight(node: AVLNode) { node.height = max(getHeight(node.left), getHeight(node.right)) + 1 } + private fun getBalanceFactor(node: AVLNode?): Int = when (node) { null -> 0 else -> getHeight(node.right) - getHeight(node.left) @@ -38,12 +40,14 @@ class AVLTree, V> : BalancingTree>() { } return rotateRight(node) } + 2 -> { if (getBalanceFactor(node.right) == -1) { node.right = node.right?.let { rotateRight(it) } } return rotateLeft(node) } + else -> node } } @@ -60,8 +64,8 @@ class AVLTree, V> : BalancingTree>() { } else if (node.right == null) { return node.left } else { - val tmp: AVLNode = findMax(node.left) ?: - throw IllegalStateException("Left subtree must contain elements") + val tmp: AVLNode = + findMax(node.left) ?: throw IllegalStateException("Left subtree must contain elements") node.key = tmp.key node.value = tmp.value node.left = removeNode(node.left, tmp.key) @@ -80,6 +84,7 @@ class AVLTree, V> : BalancingTree>() { updateHeight(left) return left } + override fun rotateLeft(node: AVLNode): AVLNode { val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") node.right = right.left diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index a95ff54..c82e270 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -1,17 +1,8 @@ package bst import bst.nodes.BinaryNode -import org.neo4j.ogm.annotation.GeneratedValue -import org.neo4j.ogm.annotation.Id -import org.neo4j.ogm.annotation.Labels -import org.neo4j.ogm.annotation.NodeEntity -@NodeEntity - -abstract class AbstractBST, V, Self: BinaryNode> : Tree { - @Id - @GeneratedValue - val id: Long? = null +abstract class AbstractBST, V, Self : BinaryNode> : Tree { var treeName: String = "" internal var rootNode: Self? = null @@ -22,6 +13,7 @@ abstract class AbstractBST, V, Self: BinaryNode> : override fun insert(key: K, value: V) { rootNode = insertNode(rootNode, key, value) } + protected open fun insertNode(node: Self?, key: K, value: V): Self { if (node == null) return initNode(key, value) if (key < node.key) { @@ -38,6 +30,7 @@ abstract class AbstractBST, V, Self: BinaryNode> : override fun remove(key: K) { rootNode = removeNode(rootNode, key) } + protected open fun removeNode(node: Self?, key: K): Self? { if (node == null) return null if (key < node.key) { diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 04067e7..6337ec3 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -1,15 +1,18 @@ package bst + import bst.nodes.BSTNode -class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null): AbstractBST>() { +class BSTree, V>(@Transient val key: K? = null, @Transient val value: V? = null) : + AbstractBST>() { override fun initNode(key: K, value: V): BSTNode = BSTNode(key, value) + init { if (key != null && value != null) { rootNode = initNode(key, value) } } - fun setName(treeName: String){ + fun setName(treeName: String) { this.treeName = treeName } } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 680b9dd..4ea4010 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -21,37 +21,42 @@ class RedBlackTree, V> : BalancingTree>() val head = initNode(key, value) // False tree root var grandparent: RBTNode? = null - var t: RBTNode = head // Iterator + var t: RBTNode = head // Helper var parent: RBTNode? = null t.right = rootNode - var q: RBTNode? = t.right - var dir = false // false - left, true - right + var iter: RBTNode? = t.right + /* + true is for right child + false is for left child + */ + var direction = false + /* Last direction */ var last = false // Search down the tree while (true) { - if (q == null) { + if (iter == null) { // Insert new node at the bottom - q = initNode(key, value) - if (dir) parent?.right = q else parent?.left = q - } else if (isRed(q.left) && isRed(q.right)) { + iter = initNode(key, value) + if (direction) parent?.right = iter else parent?.left = iter + } else if (isRed(iter.left) && isRed(iter.right)) { // Color flip - q.color = RBTNode.Color.RED - q.left?.color = RBTNode.Color.BLACK - q.right?.color = RBTNode.Color.BLACK + iter.color = RBTNode.Color.RED + iter.left?.color = RBTNode.Color.BLACK + iter.right?.color = RBTNode.Color.BLACK } // Fix red violation - if (isRed(q) && isRed(parent) && grandparent != null) { + if (isRed(iter) && isRed(parent) && grandparent != null) { val dir2 = t.child(true) == grandparent if (dir2) { - if (q == parent?.child(last)) { + if (iter == parent?.child(last)) { t.right = rotate(grandparent, !last) } else { t.right = doubleRotate(grandparent, !last) } } else { - if (q == parent?.child(last)) { + if (iter == parent?.child(last)) { t.left = rotate(grandparent, !last) } else { t.left = doubleRotate(grandparent, !last) @@ -60,19 +65,19 @@ class RedBlackTree, V> : BalancingTree>() } // Stop if found - if (q.key == key) { - q.value = value + if (iter.key == key) { + iter.value = value break } - last = dir - dir = q.key < key + last = direction + direction = iter.key < key // Update helpers t = grandparent ?: t grandparent = parent - parent = q - q = q.child(dir) + parent = iter + iter = iter.child(direction) } // Update root @@ -80,50 +85,62 @@ class RedBlackTree, V> : BalancingTree>() } rootNode?.color = RBTNode.Color.BLACK } + override fun remove(key: K) { removeNode(key) } + + private fun removeNode(key: K): Int { if (rootNode != null) { - val head = initNode(key, "" as V) // False tree root - var q: RBTNode = head + /* + False tree root. + If someone knows how to do it + without ugly cast make a pull request please + */ + val head = initNode(key, "" as V) + var iter: RBTNode = head var parent: RBTNode? = null var grandparent: RBTNode? - var f: RBTNode? = null /* Found item's parent */ - var dir = true + var nodeToDelete: RBTNode? = null + /* + true is for right child + false is for left child + */ + var direction = true - q.right = rootNode + iter.right = rootNode /* Search and push a red node down to fix red violations as we go */ - while (q.child(dir) != null) { - val last = dir + while (iter.child(direction) != null) { + val last = direction /* Move the helpers down */ grandparent = parent - parent = q - q = parent.child(dir) ?: throw IllegalStateException("Parent node cannot be null") - dir = q.key < key + parent = iter + iter = parent.child(direction) ?: throw IllegalStateException("Parent node cannot be null") + direction = iter.key < key /* - Save parent of the node with matching data and keep + Save the node with matching data and keep going; we'll do removal tasks at the end */ - if (q.key == key) { - f = q + if (iter.key == key) { + nodeToDelete = iter } /* Push the red node down with rotations and color flips */ - if (!isRed(q) && !isRed(q.child(dir))) { - if (isRed(q.child(!dir))) { + if (!isRed(iter) && !isRed(iter.child(direction))) { + if (isRed(iter.child(!direction))) { if (last) { - parent.right = rotate(q, dir) + parent.right = rotate(iter, direction) } else { - parent.left = rotate(q, dir) + parent.left = rotate(iter, direction) } parent = parent.child(last) - } else if (!isRed(q.child(!dir))) { + } else if (!isRed(iter.child(!direction))) { val s = parent.child(!last) if (s != null) { @@ -131,9 +148,10 @@ class RedBlackTree, V> : BalancingTree>() /* Color flip */ parent.color = RBTNode.Color.BLACK s.color = RBTNode.Color.RED - q.color = RBTNode.Color.RED + iter.color = RBTNode.Color.RED } else { - val dir2 = (grandparent?.right ?: throw IllegalStateException("Grandparent node cannot be null")) == parent + val dir2 = (grandparent?.right + ?: throw IllegalStateException("Grandparent node cannot be null")) == parent if (isRed(s.child(last))) { if (dir2) { @@ -149,7 +167,7 @@ class RedBlackTree, V> : BalancingTree>() } } /* Ensure correct coloring */ - q.color = RBTNode.Color.RED + iter.color = RBTNode.Color.RED grandparent.child(dir2)?.color = RBTNode.Color.RED grandparent.child(dir2)?.left?.color = RBTNode.Color.BLACK grandparent.child(dir2)?.right?.color = RBTNode.Color.BLACK @@ -160,13 +178,13 @@ class RedBlackTree, V> : BalancingTree>() } /* Replace and remove the saved node */ - if (f != null) { - f.key = q.key - f.value = q.value - if (parent?.right == q) { - parent.right = q.child(q.left == null) + if (nodeToDelete != null) { + nodeToDelete.key = iter.key + nodeToDelete.value = iter.value + if (parent?.right == iter) { + parent.right = iter.child(iter.left == null) } else { - parent?.left = q.child(q.left == null) + parent?.left = iter.child(iter.left == null) } } diff --git a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt index cdc3f32..a25257d 100644 --- a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -5,25 +5,26 @@ import com.google.gson.Gson import java.io.FileReader import java.io.FileWriter -class JsonController{ - fun saveTreeToJson(tree: AVLTree){ +class JsonController { + fun saveTreeToJson(tree: AVLTree) { val gson = Gson() try { val writer = FileWriter("${tree.treeName}.json") gson.toJson(tree, writer) writer.close() - }catch (e: Exception){ + } catch (e: Exception) { println("Write error") } } + fun readFromJson(treeName: String): AVLTree? { val gson = Gson() - return try{ + return try { val reader = FileReader("$treeName.json") val tree = gson.fromJson(reader, AVLTree()::class.java) reader.close() tree - }catch (e: Exception){ + } catch (e: Exception) { println("read error") null } diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index cc5b7ba..057479c 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -9,7 +9,6 @@ import bst.nodes.RBTNode import bst.nodes.RBTNode.Color import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory -import java.util.NoSuchElementException class Neo4jController(config: Configuration) { private val sessionFactory = SessionFactory(config, "bst") @@ -20,8 +19,15 @@ class Neo4jController(config: Configuration) { if (this == null) { return null } - return SerializableNode(key.toString(), value.toString(), 0.0, 0.0, - color.toString(), left?.toSerializableNode(), right?.toSerializableNode()) + return SerializableNode( + key.toString(), + value.toString(), + 0.0, + 0.0, + color.toString(), + left?.toSerializableNode(), + right?.toSerializableNode() + ) } private fun RedBlackTree<*, *>.toSerializableTree(): SerializableTree { @@ -47,9 +53,7 @@ class Neo4jController(config: Configuration) { private fun deserializeNode(node: SerializableNode?): RBTNode? { node ?: return null val rbtNode = RBTNode( - key = node.key.toInt(), - value = node.value, - color = deserializeMetadata(node.metadata) + key = node.key.toInt(), value = node.value, color = deserializeMetadata(node.metadata) ) rbtNode.left = deserializeNode(node.leftNode) rbtNode.right = deserializeNode(node.rightNode) @@ -80,8 +84,8 @@ class Neo4jController(config: Configuration) { fun removeTree(name: String) { session.query( - "MATCH (n)-[r *0..]->(m) " + - "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name)) + "MATCH (n)-[r *0..]->(m) " + "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name) + ) } fun loadDeserialized(name: String): RedBlackTree { @@ -97,11 +101,12 @@ class Neo4jController(config: Configuration) { private fun getTree(name: String): TreeEntity { return session.queryForObject( - TreeEntity::class.java, "MATCH (n)-[r *1..]-(m) " + - "WHERE n.treeName = \$name RETURN n, r, m", mapOf("name" to name)) ?: - throw NoSuchElementException("No tree with that name has been found") + TreeEntity::class.java, + "MATCH (n)-[r *1..]-(m) " + "WHERE n.treeName = \$name RETURN n, r, m", + mapOf("name" to name) + ) ?: throw NoSuchElementException("No tree with that name has been found") } - fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()). - flatMap { it.values.map { value -> value.toString() } } + fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()) + .flatMap { it.values.map { value -> value.toString() } } } diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index ffb33e1..e212ab8 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -1,36 +1,41 @@ package bst.db.controllers + import bst.BSTree import bst.db.serializeClasses.SerializableNode import bst.db.serializeClasses.SerializableTree import bst.nodes.BSTNode -import bst.db.models.Node +import bst.db.models.sql.Node import org.jetbrains.exposed.sql.* -import bst.db.models.Trees -import bst.db.models.Nodes -import bst.db.models.Tree +import bst.db.models.sql.Trees +import bst.db.models.sql.Nodes +import bst.db.models.sql.Tree import org.jetbrains.exposed.sql.transactions.transaction class SQLController { private fun connectDB() { - Database.connect("jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", - user = "test", password = "test") + Database.connect( + "jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", user = "test", password = "test" + ) } private fun deleteTree(treeName: String) { transaction { - val treeEntity = - Tree.find { (Trees.name eq treeName)} - .firstOrNull() + val treeEntity = Tree.find { (Trees.name eq treeName) }.firstOrNull() treeEntity?.delete() } } - private fun serializeNode(node: BSTNode<*, *>?): SerializableNode?{ - return if (node == null){ + private fun serializeNode(node: BSTNode<*, *>?): SerializableNode? { + return if (node == null) { null - } else{ - val serializableNode = SerializableNode(key = node.key.toString(), value = node.value.toString(), leftNode = null, rightNode = null) + } else { + val serializableNode = SerializableNode( + key = node.key.toString(), + value = node.value.toString(), + leftNode = null, + rightNode = null + ) serializableNode.rightNode = serializeNode(node.right) serializableNode.leftNode = serializeNode(node.left) serializableNode @@ -42,7 +47,7 @@ class SQLController { ?.let { SerializableTree(treeName = tree.treeName, rootNode = it) } } - private fun createTables(){ + private fun createTables() { SchemaUtils.create(Trees) SchemaUtils.create(Nodes) } @@ -60,7 +65,7 @@ class SQLController { } } - fun saveTreeToDB(tree: BSTree<*, *>){ + fun saveTreeToDB(tree: BSTree<*, *>) { connectDB() deleteTree(tree.treeName) val serializedTree = serializeTree(tree) @@ -68,8 +73,7 @@ class SQLController { addLogger(StdOutSqlLogger) createTables() val daoTree = Tree.new { - if(serializedTree!=null) - name = serializedTree.treeName + if (serializedTree != null) name = serializedTree.treeName } daoTree.rootNode = serializedTree?.rootNode?.toNodeDao(daoTree) } @@ -88,13 +92,12 @@ class SQLController { } - private fun findTree(treeName: String): SerializableTree?{ + private fun findTree(treeName: String): SerializableTree? { connectDB() - val treeDAO = Tree.find{ Trees.name eq treeName}.firstOrNull() ?: return null + val treeDAO = Tree.find { Trees.name eq treeName }.firstOrNull() ?: return null return treeDAO.rootNode?.getSerializedNode(treeDAO)?.let { SerializableTree( - treeName, - it + treeName, it ) } } @@ -107,12 +110,12 @@ class SQLController { false } } - + private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { - return if (node == null){ + return if (node == null) { null - } else{ + } else { val deserializableNode = BSTNode(key = node.key.toInt(), value = node.value) deserializableNode.right = deserializeNodeDoubleKey(node.rightNode) deserializableNode.left = deserializeNodeDoubleKey(node.leftNode) @@ -120,21 +123,21 @@ class SQLController { } } - private fun deserializeTree(tree: SerializableTree?): BSTree?{ + private fun deserializeTree(tree: SerializableTree?): BSTree? { if (tree != null) { - if (isNumeric(tree.rootNode!!.key)){ + if (isNumeric(tree.rootNode!!.key)) { val rootNode = deserializeNodeDoubleKey(tree.rootNode) - val deserializedTree:BSTree = BSTree(rootNode?.key, rootNode?.value) + val deserializedTree: BSTree = BSTree(rootNode?.key, rootNode?.value) deserializedTree.rootNode = rootNode deserializedTree.setName(tree.treeName) - return deserializedTree + return deserializedTree } } return null } fun getTree(treeName: String): BSTree? { - var deserializedTree: SerializableTree? = null + var deserializedTree: SerializableTree? = null transaction { deserializedTree = findTree(treeName) } diff --git a/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt b/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt index 9359aa7..4bff1e3 100644 --- a/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt +++ b/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt @@ -5,7 +5,7 @@ import org.neo4j.ogm.annotation.Id import org.neo4j.ogm.annotation.NodeEntity @NodeEntity -class TreeEntity ( +class TreeEntity( var treeName: String = "", var rootNode: TreeNodeEntity? = null, ) { diff --git a/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt b/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt index dd9a9ed..9a84185 100644 --- a/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt +++ b/trees/src/main/kotlin/bst/db/models/neo4j/TreeNodeEntity.kt @@ -8,14 +8,12 @@ import org.neo4j.ogm.annotation.Relationship @NodeEntity class TreeNodeEntity( val key: String, - val value : String, - val x : Double = 0.0, + val value: String, + val x: Double = 0.0, val y: Double = 0.0, var metadata: String? = null, - @Relationship(type = "LEFT") - var left: TreeNodeEntity? = null, - @Relationship(type = "RIGHT") - var right: TreeNodeEntity? = null + @Relationship(type = "LEFT") var left: TreeNodeEntity? = null, + @Relationship(type = "RIGHT") var right: TreeNodeEntity? = null ) { @Id @GeneratedValue diff --git a/trees/src/main/kotlin/bst/db/models/sql/Node.kt b/trees/src/main/kotlin/bst/db/models/sql/Node.kt index 722df7a..fa1a764 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Node.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Node.kt @@ -1,9 +1,11 @@ package bst.db.models.sql + import org.jetbrains.exposed.dao.* import org.jetbrains.exposed.dao.id.EntityID -class Node(id: EntityID): IntEntity(id) { - companion object: IntEntityClass(Nodes) +class Node(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Nodes) + var key by Nodes.key var value by Nodes.value var x by Nodes.x diff --git a/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt b/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt index ee59825..aeb1e42 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt @@ -1,4 +1,5 @@ package bst.db.models.sql + import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ReferenceOption diff --git a/trees/src/main/kotlin/bst/db/models/sql/Tree.kt b/trees/src/main/kotlin/bst/db/models/sql/Tree.kt index e86095c..7352500 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Tree.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Tree.kt @@ -4,8 +4,9 @@ import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID -class Tree(id: EntityID): IntEntity(id){ - companion object: IntEntityClass(Trees) +class Tree(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Trees) + var name by Trees.name var rootNode by Node optionalReferencedOn Trees.rootNode override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" diff --git a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt index 5baa597..c92ced6 100644 --- a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt @@ -1,11 +1,12 @@ package bst.db.serializeClasses -import kotlinx.serialization.* + +import kotlinx.serialization.Serializable @Serializable -class SerializableNode ( +class SerializableNode( val key: String, - val value : String, - var x : Double = 0.0, + val value: String, + var x: Double = 0.0, var y: Double = 0.0, var metadata: String? = null, var leftNode: SerializableNode? = null, diff --git a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt index aabb3ba..1cbd32c 100644 --- a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt @@ -1,10 +1,9 @@ package bst.db.serializeClasses -import bst.db.serializeClasses.SerializableNode import kotlinx.serialization.Serializable @Serializable -class SerializableTree ( +class SerializableTree( var treeName: String, var rootNode: SerializableNode?, - ) +) diff --git a/trees/src/main/kotlin/bst/nodes/RBTNode.kt b/trees/src/main/kotlin/bst/nodes/RBTNode.kt index a6e6e86..f5f46b7 100644 --- a/trees/src/main/kotlin/bst/nodes/RBTNode.kt +++ b/trees/src/main/kotlin/bst/nodes/RBTNode.kt @@ -1,6 +1,6 @@ package bst.nodes -class RBTNode, V> ( +class RBTNode, V>( key: K, value: V, var color: Color = Color.RED diff --git a/trees/src/test/kotlin/bst/RbtTest.kt b/trees/src/test/kotlin/bst/RbtTest.kt index c76b9bb..b960b81 100644 --- a/trees/src/test/kotlin/bst/RbtTest.kt +++ b/trees/src/test/kotlin/bst/RbtTest.kt @@ -15,7 +15,7 @@ class RbtTest { the tree is invalid red-black tree, and any other number is the black height of the entire tree. */ - private fun , V>isRbt(root: RBTNode?): Int { + private fun , V> isRbt(root: RBTNode?): Int { if (root == null) { return 1 } From fc1ba2768107b15ac5f86928b5b7e9c14f246d95 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Wed, 26 Apr 2023 10:10:00 +0300 Subject: [PATCH 096/118] fix readme and add setName method to all trees --- README.md | 87 +++++++++++-------- trees/src/main/kotlin/bst/AVLTree.kt | 10 ++- trees/src/main/kotlin/bst/RedBlackTree.kt | 13 ++- .../bst/db/controllers/Neo4jController.kt | 1 + 4 files changed, 70 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index c07fe87..6699035 100644 --- a/README.md +++ b/README.md @@ -2,61 +2,72 @@ > An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) -![Junit](https://img.shields.io/badge/-Junit-525A162?&style=for-the-badge) +![Junit](https://img.shields.io/badge/Tests-Junit-green) +![Postgresql](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white) +![Docker](https://img.shields.io/badge/Docker-316192?style=for-the-badge&logo=Docker&logoColor=white) ## :package: Getting started -Firstly, you need to clone repository - -```sh -https://github.com/spbu-coding-2022/trees-3.git -``` To build the library run ```sh ./gradlew build ``` -## Using BinarySearchTree +To run postgresql with docker: +``` +./run-db.sh +``` +or +``` +./run-db.bat +``` + +## Using Binary search tree Any `Comparable` data can be stored in trees. -You can start from create bst by: +Example: ```kotlin import bst.BSTree - val test_data = BSTree("121", "dgs", "tree_1") - //also we support add and remove elements from bst. You can do this by: - test_data.insert("110", "dafad") - test_data.insert("118", "adfaf") - test_data.insert("124", "fggsg") + val testData = BSTree(121, "dgs") + testData.insert(110, "dafad") + testData.insert(118, "adfaf") + testData.insert(124, "fggsg") ``` +BSTree constructor gets two params: key and value. +insert method also gets same params and add node with key and value in tree. +Method setName allows you to set name of tree, as argument it takes the string. -find or remove element from tree: +Find or remove element from tree: ```kotlin - test_data.remove("124") - test_data.find("118") + testData.remove(124) + testData.find(118) ``` -Same operations support RBT and AVL trees. +As a parameter to the find and remove methods take a key-value node + +Same operations support red black tree and AVL tree. -BinarySearchTree supports save tree object to json file, and up tree from json. +AVL tree supports conservation tree object to json file, and receiving tree from json. For example: ```kotlin - test_data.saveTreeToJson() - readFromJson("tree_1.json") + val test = AVLTree(1231, "afea") + test.insert(2123, "adf") + test.insert(2123, "adf") + test.setName("test_1") + val control = JsonController() + control.saveTreeToJson(test) + println(control.readFromJson("test_1")?.treeName) ``` -In development saving nodes to databases: neo4j and sql. - -To save BST in SQL database we propose you to use models in directory main/db. -for example: -```kotlin - val treeObj = Tree.new { - name = "Tree_1" - } - val rootNode = Node.new { - key = "test" - value = "234" - tree = treeObj - } - treeObj.rootNode = rootNode -``` - -## Storing BSTs -`bst` provides \ No newline at end of file +Also you can save binary search tree to sql database: +``` + val test_data = BSTree(121, "dgs") + test_data.insert(110, "dafad") + test_data.insert(118, "adfaf") + test_data.insert(124, "fggsg") + test_data.setName("afefadsf") + val controller = SQLController() + controller.saveTreeToDB(test_data) + val remTree = controller.getTree("afefadsf") +``` +And you can save red black tree to neo4j database +``` +``` \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index 7252fc0..9a7cb1d 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -3,9 +3,17 @@ package bst import bst.nodes.AVLNode import kotlin.math.max -class AVLTree, V> : BalancingTree>() { +class AVLTree, V>(@Transient val key: K? = null, @Transient val value: V? = null) : BalancingTree>() { override fun initNode(key: K, value: V): AVLNode = AVLNode(key, value) + init { + if (key != null && value != null) { + rootNode = initNode(key, value) + } + } + fun setName(treeName: String){ + this.treeName = treeName + } override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { if (node == null) return initNode(key, value) if (key < node.key) { diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 4ea4010..55d8d56 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -1,8 +1,17 @@ package bst - import bst.nodes.RBTNode -class RedBlackTree, V> : BalancingTree>() { +class RedBlackTree, V>(@Transient val key: K? = null, @Transient val value: V? = null) : BalancingTree>() { + + init { + if (key != null && value != null) { + rootNode = initNode(key, value) + } + } + + fun setName(treeName: String){ + this.treeName = treeName + } private fun isRed(node: RBTNode?): Boolean { return node != null && node.color == RBTNode.Color.RED } diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index 057479c..a2251e3 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -110,3 +110,4 @@ class Neo4jController(config: Configuration) { fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()) .flatMap { it.values.map { value -> value.toString() } } } + From 05bc89f6fdbf6b7795bbdc4e78590610169f2b40 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 26 Apr 2023 17:38:35 +0300 Subject: [PATCH 097/118] refactor: better README.md Co-authored-by: Nikita Lukonenko <68463602+surfaceyellowduck@users.noreply.github.com> --- README.md | 76 +++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 6699035..5102d0c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ # Trees library. -> An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree.. +> An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) ![Junit](https://img.shields.io/badge/Tests-Junit-green) +![Neo4j](https://img.shields.io/badge/Neo4j-008CC1?style=for-the-badge&logo=neo4j&logoColor=white) ![Postgresql](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white) ![Docker](https://img.shields.io/badge/Docker-316192?style=for-the-badge&logo=Docker&logoColor=white) ## :package: Getting started To build the library run ```sh - ./gradlew build +./gradlew build ``` -To run postgresql with docker: +To run PostgreSQL with docker: ``` ./run-db.sh ``` @@ -20,54 +21,53 @@ or ./run-db.bat ``` -## Using Binary search tree -Any `Comparable` data can be stored in trees. -Example: +## Using binary search trees +Any data (provided with `Comparable` key) can be stored in trees. +For example: ```kotlin import bst.BSTree - val testData = BSTree(121, "dgs") - testData.insert(110, "dafad") - testData.insert(118, "adfaf") - testData.insert(124, "fggsg") + val tree = BSTree(1, "apple") + tree.insert(7, "orange") + tree.insert(28, "Alice") + tree.insert(4, "Bob") ``` -BSTree constructor gets two params: key and value. -insert method also gets same params and add node with key and value in tree. -Method setName allows you to set name of tree, as argument it takes the string. +Constructor takes two arguments: `key` and `value`, thus instantiating a root node (you can delete it, +but you cannot create an empty tree). +`insert` method also takes same arguments and adds a node with specified `key` and `value` properties to the tree. +Method `setName` allows you to set the name of a tree. Find or remove element from tree: -```kotlin - testData.remove(124) - testData.find(118) +```kotlin + tree.find(4) // returns "Bob" + tree.remove(1) ``` -As a parameter to the find and remove methods take a key-value node +`find` and `remove` methods take some `Comparable` key as an argument. -Same operations support red black tree and AVL tree. +AVL and red-black trees implement the same methods. - -AVL tree supports conservation tree object to json file, and receiving tree from json. +## Storing binary search trees +AVL tree can be saved to and loaded from JSON file. For example: ```kotlin - val test = AVLTree(1231, "afea") - test.insert(2123, "adf") - test.insert(2123, "adf") - test.setName("test_1") - val control = JsonController() - control.saveTreeToJson(test) - println(control.readFromJson("test_1")?.treeName) -``` - -Also you can save binary search tree to sql database: + val tree = AVLTree(1, "apple") + tree.setName("test") + val controller = JsonController() + controller.saveTreeToJson(test) + println(controller.readFromJson("test")?.treeName) ``` - val test_data = BSTree(121, "dgs") - test_data.insert(110, "dafad") - test_data.insert(118, "adfaf") - test_data.insert(124, "fggsg") - test_data.setName("afefadsf") +You can also save binary search tree to SQL database: +```kotlin + val tree = BSTree(1, "apple") + tree.setName("test") val controller = SQLController() controller.saveTreeToDB(test_data) - val remTree = controller.getTree("afefadsf") -``` -And you can save red black tree to neo4j database + val remTree = controller.getTree("test") ``` +And you can save red-black tree to Neo4j database +```kotlin + val tree = RedBlackTree(1, "apple") + val controller = Neoj4Conroller() + contoller.saveTree(tree) + val remTree = controller.loadTree("test") ``` \ No newline at end of file From fa8124ef2bb6c37ad23ef5de8a382210c225e85d Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 26 Apr 2023 17:39:59 +0300 Subject: [PATCH 098/118] feat: bring back markdown linter --- .github/workflows/linter.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index fc119eb..1115846 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -17,7 +17,6 @@ jobs: uses: github/super-linter/slim@v5 env: VALIDATE_JSCPD: false - VALIDATE_MARKDOWN: false VALIDATE_KOTLIN_ANDROID: false VALIDATE_ALL_CODEBASE: false DEFAULT_BRANCH: "main" From d764fe0d4c9cf6133e9ef9393508c80c3b82c3a8 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 26 Apr 2023 17:40:31 +0300 Subject: [PATCH 099/118] feat: add neo4j to docker-compose.yml --- docker-compose.yml | 14 +++++++++++++- neo4j.env | 1 + .../bst/db/controllers/Neo4jController.kt | 17 ++++++++++++----- 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 neo4j.env diff --git a/docker-compose.yml b/docker-compose.yml index 2cd846e..1bd5c58 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,24 @@ version: "3" services: local-database: - image: "postgres:15.2" + image: postgres:15.2 env_file: - postgres.env ports: - "5432:5432" volumes: - pg-volume:/var/lib/postgresql/data + neo4j: + image: neo4j:5.6.0 + container_name: neo4j + env_file: + - neo4j.env + ports: + - "7474:7474" + - "7687:7687" + volumes: + - neo4j-volume:/data + - neo4j-volume:/logs volumes: pg-volume: {} + neo4j-volume: {} diff --git a/neo4j.env b/neo4j.env new file mode 100644 index 0000000..c3e61fd --- /dev/null +++ b/neo4j.env @@ -0,0 +1 @@ +NEO4J_AUTH=neo4j/password \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index a2251e3..7d8bfef 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -10,11 +10,16 @@ import bst.nodes.RBTNode.Color import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory -class Neo4jController(config: Configuration) { +class Neo4jController { + private val config = Configuration.Builder() + .uri("bolt://localhost") + .credentials("neo4j", "password") + .build() private val sessionFactory = SessionFactory(config, "bst") private val session = sessionFactory.openSession() + private fun RBTNode<*, *>?.toSerializableNode(): SerializableNode? { if (this == null) { return null @@ -88,18 +93,21 @@ class Neo4jController(config: Configuration) { ) } - fun loadDeserialized(name: String): RedBlackTree { - val tree = getTree(name) + fun getTree(name: String): RedBlackTree { + val tree = loadTree(name) return deserializeTree(tree.toSerializableTree()) } // Could be useful for underlying logic of GUI + /* fun loadSerialized(name: String): SerializableTree { val tree = getTree(name) return tree.toSerializableTree() } - private fun getTree(name: String): TreeEntity { + */ + + private fun loadTree(name: String): TreeEntity { return session.queryForObject( TreeEntity::class.java, "MATCH (n)-[r *1..]-(m) " + "WHERE n.treeName = \$name RETURN n, r, m", @@ -110,4 +118,3 @@ class Neo4jController(config: Configuration) { fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()) .flatMap { it.values.map { value -> value.toString() } } } - From b44b5d729b2d2174c09a94114e4dee843c02ea29 Mon Sep 17 00:00:00 2001 From: ancavar Date: Wed, 26 Apr 2023 18:42:42 +0300 Subject: [PATCH 100/118] feat: add test coverage reports back --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 634a8fc..3a061f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,13 @@ jobs: - name: Build with Gradle run: ./gradlew build + - name: JaCoCo Coverage Report + if: runner.os != 'Windows' + env: + report_path: trees/build/jacoco/report.csv + run: | + awk -F"," '{ instructions += $4 + $5; covered += $5; branches += $6 + $7; branches_covered +=$7 } END { print "Instructions covered:", covered"/"instructions, "--", 100*covered/instructions"%"; print "Branches covered:", branches_covered"/"branches, "--", 100*branches_covered/branches"%" }' $report_path + - uses: actions/upload-artifact@v3 if: ${{ github.ref == 'refs/heads/main' }} with: From 6d0f73b160ea2dfd0c5af482dd74c80177e77947 Mon Sep 17 00:00:00 2001 From: ancavar Date: Thu, 27 Apr 2023 23:53:24 +0300 Subject: [PATCH 101/118] fix: typos in README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5102d0c..4105160 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Trees library. +# Binary search trees library. > An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) @@ -14,11 +14,11 @@ To build the library run ``` To run PostgreSQL with docker: ``` -./run-db.sh +./start-db.sh ``` or ``` -./run-db.bat +./start-db.bat ``` ## Using binary search trees @@ -67,6 +67,7 @@ You can also save binary search tree to SQL database: And you can save red-black tree to Neo4j database ```kotlin val tree = RedBlackTree(1, "apple") + tree.setName("test") val controller = Neoj4Conroller() contoller.saveTree(tree) val remTree = controller.loadTree("test") From 1438bcddb6f42f0a0d8895cfadc3e0a08897feb5 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 28 Apr 2023 22:18:40 +0300 Subject: [PATCH 102/118] fix: better names, remove unnecessary cast --- trees/src/main/kotlin/bst/RedBlackTree.kt | 59 ++++++++++++----------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index 55d8d56..b89ba9e 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -1,8 +1,11 @@ package bst + import bst.nodes.RBTNode class RedBlackTree, V>(@Transient val key: K? = null, @Transient val value: V? = null) : BalancingTree>() { + override fun initNode(key: K, value: V): RBTNode = RBTNode(key, value) + init { if (key != null && value != null) { rootNode = initNode(key, value) @@ -30,11 +33,11 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie val head = initNode(key, value) // False tree root var grandparent: RBTNode? = null - var t: RBTNode = head // Helper + var helper: RBTNode = head var parent: RBTNode? = null - t.right = rootNode - var iter: RBTNode? = t.right + helper.right = rootNode + var iter: RBTNode? = helper.right /* true is for right child false is for left child @@ -57,18 +60,18 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } // Fix red violation if (isRed(iter) && isRed(parent) && grandparent != null) { - val dir2 = t.child(true) == grandparent + val dir2 = helper.child(true) == grandparent if (dir2) { if (iter == parent?.child(last)) { - t.right = rotate(grandparent, !last) + helper.right = rotate(grandparent, !last) } else { - t.right = doubleRotate(grandparent, !last) + helper.right = doubleRotate(grandparent, !last) } } else { if (iter == parent?.child(last)) { - t.left = rotate(grandparent, !last) + helper.left = rotate(grandparent, !last) } else { - t.left = doubleRotate(grandparent, !last) + helper.left = doubleRotate(grandparent, !last) } } } @@ -83,7 +86,7 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie direction = iter.key < key // Update helpers - t = grandparent ?: t + helper = grandparent ?: helper grandparent = parent parent = iter iter = iter.child(direction) @@ -104,10 +107,10 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie if (rootNode != null) { /* False tree root. - If someone knows how to do it - without ugly cast make a pull request please + */ - val head = initNode(key, "" as V) + val head = initNode(key, rootNode?.value + ?: throw IllegalStateException("Root of the tree cannot be null")) var iter: RBTNode = head var parent: RBTNode? = null var grandparent: RBTNode? @@ -150,26 +153,26 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } parent = parent.child(last) } else if (!isRed(iter.child(!direction))) { - val s = parent.child(!last) + val sibling = parent.child(!last) - if (s != null) { - if (!isRed(s.child(!last)) && !isRed(s.child(last))) { + if (sibling != null) { + if (!isRed(sibling.child(!last)) && !isRed(sibling.child(last))) { /* Color flip */ parent.color = RBTNode.Color.BLACK - s.color = RBTNode.Color.RED + sibling.color = RBTNode.Color.RED iter.color = RBTNode.Color.RED } else { - val dir2 = (grandparent?.right + val direction2 = (grandparent?.right ?: throw IllegalStateException("Grandparent node cannot be null")) == parent - if (isRed(s.child(last))) { - if (dir2) { + if (isRed(sibling.child(last))) { + if (direction2) { grandparent.right = doubleRotate(parent, last) } else { grandparent.left = doubleRotate(parent, last) } - } else if (isRed(s.child(!last))) { - if (dir2) { + } else if (isRed(sibling.child(!last))) { + if (direction2) { grandparent.right = rotate(parent, last) } else { grandparent.left = rotate(parent, last) @@ -177,9 +180,9 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } /* Ensure correct coloring */ iter.color = RBTNode.Color.RED - grandparent.child(dir2)?.color = RBTNode.Color.RED - grandparent.child(dir2)?.left?.color = RBTNode.Color.BLACK - grandparent.child(dir2)?.right?.color = RBTNode.Color.BLACK + grandparent.child(direction2)?.color = RBTNode.Color.RED + grandparent.child(direction2)?.left?.color = RBTNode.Color.BLACK + grandparent.child(direction2)?.right?.color = RBTNode.Color.BLACK } } } @@ -207,14 +210,14 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } private fun rotate(node: RBTNode, dir: Boolean): RBTNode { - val save: RBTNode = if (dir) { + val childNode: RBTNode = if (dir) { rotateRight(node) } else { rotateLeft(node) } node.color = RBTNode.Color.RED - save.color = RBTNode.Color.BLACK - return save + childNode.color = RBTNode.Color.BLACK + return childNode } private fun doubleRotate(node: RBTNode, dir: Boolean): RBTNode { @@ -226,8 +229,6 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie return rotate(node, dir) } - override fun initNode(key: K, value: V): RBTNode = RBTNode(key, value) - override fun rotateLeft(node: RBTNode): RBTNode { val right = node.right ?: throw IllegalStateException("Node's right child cannot be null") node.right = right.left From cb8506bbd1261b60685b069d14161f8a29421428 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 28 Apr 2023 23:13:38 +0300 Subject: [PATCH 103/118] feat: implement Controller interface, add removal to JSON --- trees/src/main/kotlin/bst/AVLTree.kt | 4 +- trees/src/main/kotlin/bst/AbstractBST.kt | 1 - trees/src/main/kotlin/bst/BSTree.kt | 4 -- trees/src/main/kotlin/bst/RedBlackTree.kt | 3 -- .../kotlin/bst/db/controllers/Controller.kt | 10 +++++ .../bst/db/controllers/JsonController.kt | 14 +++++-- .../bst/db/controllers/Neo4jController.kt | 39 +++++++------------ .../bst/db/controllers/SQLController.kt | 20 +++++----- 8 files changed, 46 insertions(+), 49 deletions(-) create mode 100644 trees/src/main/kotlin/bst/db/controllers/Controller.kt diff --git a/trees/src/main/kotlin/bst/AVLTree.kt b/trees/src/main/kotlin/bst/AVLTree.kt index 9a7cb1d..fa4b91a 100644 --- a/trees/src/main/kotlin/bst/AVLTree.kt +++ b/trees/src/main/kotlin/bst/AVLTree.kt @@ -11,9 +11,7 @@ class AVLTree, V>(@Transient val key: K? = null, @Transient va rootNode = initNode(key, value) } } - fun setName(treeName: String){ - this.treeName = treeName - } + override fun insertNode(node: AVLNode?, key: K, value: V): AVLNode { if (node == null) return initNode(key, value) if (key < node.key) { diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index c82e270..908341d 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -3,7 +3,6 @@ package bst import bst.nodes.BinaryNode abstract class AbstractBST, V, Self : BinaryNode> : Tree { - var treeName: String = "" internal var rootNode: Self? = null diff --git a/trees/src/main/kotlin/bst/BSTree.kt b/trees/src/main/kotlin/bst/BSTree.kt index 6337ec3..149b4db 100644 --- a/trees/src/main/kotlin/bst/BSTree.kt +++ b/trees/src/main/kotlin/bst/BSTree.kt @@ -11,8 +11,4 @@ class BSTree, V>(@Transient val key: K? = null, @Transient val rootNode = initNode(key, value) } } - - fun setName(treeName: String) { - this.treeName = treeName - } } diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index b89ba9e..e1bf50a 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -12,9 +12,6 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } } - fun setName(treeName: String){ - this.treeName = treeName - } private fun isRed(node: RBTNode?): Boolean { return node != null && node.color == RBTNode.Color.RED } diff --git a/trees/src/main/kotlin/bst/db/controllers/Controller.kt b/trees/src/main/kotlin/bst/db/controllers/Controller.kt new file mode 100644 index 0000000..4178684 --- /dev/null +++ b/trees/src/main/kotlin/bst/db/controllers/Controller.kt @@ -0,0 +1,10 @@ +package bst.db.controllers + +import bst.AbstractBST +import bst.nodes.BinaryNode + +interface Controller, T: AbstractBST> { + fun saveTree(tree: T, treeName: String) + fun getTree(treeName: String): T? + fun removeTree(treeName: String) +} \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt index a25257d..e49ab05 100644 --- a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -1,15 +1,17 @@ package bst.db.controllers import bst.AVLTree +import bst.nodes.AVLNode import com.google.gson.Gson +import java.io.File import java.io.FileReader import java.io.FileWriter -class JsonController { - fun saveTreeToJson(tree: AVLTree) { +class JsonController: Controller, AVLTree> { + override fun saveTree(tree: AVLTree, treeName: String) { val gson = Gson() try { - val writer = FileWriter("${tree.treeName}.json") + val writer = FileWriter("$treeName.json") gson.toJson(tree, writer) writer.close() } catch (e: Exception) { @@ -17,7 +19,7 @@ class JsonController { } } - fun readFromJson(treeName: String): AVLTree? { + override fun getTree(treeName: String): AVLTree? { val gson = Gson() return try { val reader = FileReader("$treeName.json") @@ -29,4 +31,8 @@ class JsonController { null } } + + override fun removeTree(treeName: String) { + File("$treeName.json").delete() + } } diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index 7d8bfef..3df056f 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -10,7 +10,7 @@ import bst.nodes.RBTNode.Color import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory -class Neo4jController { +class Neo4jController: Controller, RedBlackTree> { private val config = Configuration.Builder() .uri("bolt://localhost") .credentials("neo4j", "password") @@ -35,7 +35,7 @@ class Neo4jController { ) } - private fun RedBlackTree<*, *>.toSerializableTree(): SerializableTree { + private fun RedBlackTree<*, *>.toSerializableTree(treeName: String): SerializableTree { return SerializableTree(treeName, rootNode?.toSerializableNode()) } @@ -68,7 +68,6 @@ class Neo4jController { private fun deserializeTree(tree: SerializableTree): RedBlackTree { val rbTree = RedBlackTree() rbTree.rootNode = deserializeNode(tree.rootNode) - rbTree.treeName = tree.treeName return rbTree } @@ -80,39 +79,31 @@ class Neo4jController { } } - fun saveTree(tree: RedBlackTree<*, *>) { - removeTree(tree.treeName) - val entityTree = tree.toSerializableTree().toTreeEntity() + override fun saveTree(tree: RedBlackTree, treeName: String) { + removeTree(treeName) + val entityTree = tree.toSerializableTree(treeName).toTreeEntity() + entityTree.treeName = treeName session.save(entityTree) session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", mapOf()) } - fun removeTree(name: String) { + override fun removeTree(treeName: String) { session.query( - "MATCH (n)-[r *0..]->(m) " + "WHERE n.treeName = \$name DETACH DELETE m", mapOf("name" to name) + "MATCH (n)-[r *0..]->(m) " + "WHERE n.treeName = \$treeName DETACH DELETE m", mapOf("treeName" to treeName) ) } - fun getTree(name: String): RedBlackTree { - val tree = loadTree(name) - return deserializeTree(tree.toSerializableTree()) + override fun getTree(treeName: String): RedBlackTree? { + val tree = loadTree(treeName) + return tree?.let { deserializeTree(it.toSerializableTree()) } } - // Could be useful for underlying logic of GUI - /* - fun loadSerialized(name: String): SerializableTree { - val tree = getTree(name) - return tree.toSerializableTree() - } - - */ - - private fun loadTree(name: String): TreeEntity { + private fun loadTree(treeName: String): TreeEntity? { return session.queryForObject( TreeEntity::class.java, - "MATCH (n)-[r *1..]-(m) " + "WHERE n.treeName = \$name RETURN n, r, m", - mapOf("name" to name) - ) ?: throw NoSuchElementException("No tree with that name has been found") + "MATCH (n)-[r *1..]-(m) " + "WHERE n.treeName = \$treeName RETURN n, r, m", + mapOf("treeName" to treeName) + ) ?: null } fun getNames() = session.query("MATCH (n: TreeEntity) RETURN n.treeName", mapOf()) diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index e212ab8..7fd89fc 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -12,27 +12,27 @@ import bst.db.models.sql.Tree import org.jetbrains.exposed.sql.transactions.transaction -class SQLController { +class SQLController: Controller, BSTree>{ private fun connectDB() { Database.connect( "jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", user = "test", password = "test" ) } - private fun deleteTree(treeName: String) { + override fun removeTree(treeName: String) { transaction { val treeEntity = Tree.find { (Trees.name eq treeName) }.firstOrNull() treeEntity?.delete() } } - private fun serializeNode(node: BSTNode<*, *>?): SerializableNode? { + private fun serializeNode(node: BSTNode?): SerializableNode? { return if (node == null) { null } else { val serializableNode = SerializableNode( key = node.key.toString(), - value = node.value.toString(), + value = node.value, leftNode = null, rightNode = null ) @@ -42,9 +42,9 @@ class SQLController { } } - private fun serializeTree(tree: BSTree<*, *>): SerializableTree? { + private fun serializeTree(tree: BSTree, treeName: String): SerializableTree? { return tree.rootNode?.let { serializeNode(it) } - ?.let { SerializableTree(treeName = tree.treeName, rootNode = it) } + ?.let { SerializableTree(treeName = treeName, rootNode = it) } } private fun createTables() { @@ -65,10 +65,10 @@ class SQLController { } } - fun saveTreeToDB(tree: BSTree<*, *>) { + override fun saveTree(tree: BSTree, treeName: String) { connectDB() - deleteTree(tree.treeName) - val serializedTree = serializeTree(tree) + removeTree(treeName) + val serializedTree = serializeTree(tree, treeName) transaction { addLogger(StdOutSqlLogger) createTables() @@ -136,7 +136,7 @@ class SQLController { return null } - fun getTree(treeName: String): BSTree? { + override fun getTree(treeName: String): BSTree? { var deserializedTree: SerializableTree? = null transaction { deserializedTree = findTree(treeName) From f8c9dfd297cc6e2b45efd9c9b489ef569af1821e Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 28 Apr 2023 23:25:07 +0300 Subject: [PATCH 104/118] fix: improve linter --- .github/workflows/linter.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 1115846..3dc14b4 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,12 +1,22 @@ -name: Lint Codebase +name: Lint Code Base on: push: + branches: main pull_request: + branches: main jobs: - run-lint: + build: + name: Lint Code Base + runs-on: ubuntu-latest + + permissions: + contents: read + packages: read + statuses: write + steps: - name: Checkout code uses: actions/checkout@v3 From 50f9f7cfc1736daa67b09f9317026d62eed88631 Mon Sep 17 00:00:00 2001 From: ancavar Date: Fri, 28 Apr 2023 23:28:54 +0300 Subject: [PATCH 105/118] fix: remove setName usage --- trees/src/main/kotlin/bst/db/controllers/SQLController.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index 7fd89fc..aa76109 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -129,7 +129,6 @@ class SQLController: Controller, BSTree>{ val rootNode = deserializeNodeDoubleKey(tree.rootNode) val deserializedTree: BSTree = BSTree(rootNode?.key, rootNode?.value) deserializedTree.rootNode = rootNode - deserializedTree.setName(tree.treeName) return deserializedTree } } From e27033649753800d5c4247499963203010304c71 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sun, 30 Apr 2023 18:46:43 +0300 Subject: [PATCH 106/118] feat: add close session method --- .../src/main/kotlin/bst/db/controllers/Neo4jController.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index 3df056f..84545ce 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -18,8 +18,6 @@ class Neo4jController: Controller, RedBlackTree?.toSerializableNode(): SerializableNode? { if (this == null) { return null @@ -106,6 +104,11 @@ class Neo4jController: Controller, RedBlackTree()) .flatMap { it.values.map { value -> value.toString() } } } From 2d4ec8b27737ef19495be754a52c63833317d4f7 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sun, 30 Apr 2023 20:20:11 +0300 Subject: [PATCH 107/118] refactor --- .github/workflows/test.yml | 2 +- README.md | 10 ++++----- trees/build.gradle.kts | 6 ------ trees/src/main/kotlin/bst/AbstractBST.kt | 2 -- trees/src/main/kotlin/bst/RedBlackTree.kt | 12 ++++++----- .../kotlin/bst/db/controllers/Controller.kt | 4 ++-- .../bst/db/controllers/JsonController.kt | 2 +- .../bst/db/controllers/Neo4jController.kt | 12 +++++++---- .../bst/db/controllers/SQLController.kt | 21 +++++++++++-------- .../kotlin/bst/db/models/neo4j/TreeEntity.kt | 2 +- .../src/main/kotlin/bst/db/models/sql/Node.kt | 4 ++-- .../main/kotlin/bst/db/models/sql/Nodes.kt | 1 - .../src/main/kotlin/bst/db/models/sql/Tree.kt | 1 - .../main/kotlin/bst/db/models/sql/Trees.kt | 1 - .../db/serializeClasses/SerializableNode.kt | 2 +- .../db/serializeClasses/SerializableTree.kt | 2 +- trees/src/main/kotlin/bst/nodes/BinaryNode.kt | 2 +- 17 files changed, 42 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3a061f4..16eb552 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: env: report_path: trees/build/jacoco/report.csv run: | - awk -F"," '{ instructions += $4 + $5; covered += $5; branches += $6 + $7; branches_covered +=$7 } END { print "Instructions covered:", covered"/"instructions, "--", 100*covered/instructions"%"; print "Branches covered:", branches_covered"/"branches, "--", 100*branches_covered/branches"%" }' $report_path + awk -F"," '{ instructions += $4 + $5; covered += $5; branches += $6 + $7; branches_covered +=$7 } END { print "Instructions covered:", covered"/"instructions, "--", 100*covered/instructions"%"; print "Branches covered:", branches_covered"/"branches, "--", 100*branches_covered/branches"%" }' "$report_path" - uses: actions/upload-artifact@v3 if: ${{ github.ref == 'refs/heads/main' }} diff --git a/README.md b/README.md index 4105160..29615f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Binary search trees library. +# Binary search trees library > An open source library written in Kotlin to work with data structures such as AVL tree, red-black tree, and binary search tree. ## 🖍 Used technology ![Kotlin](https://img.shields.io/badge/-Kotlin-61DAFB?logo=kotlin) @@ -13,11 +13,11 @@ To build the library run ./gradlew build ``` To run PostgreSQL with docker: -``` +```sh ./start-db.sh ``` or -``` +```sh ./start-db.bat ``` @@ -32,7 +32,7 @@ For example: tree.insert(28, "Alice") tree.insert(4, "Bob") ``` -Constructor takes two arguments: `key` and `value`, thus instantiating a root node (you can delete it, +Constructor takes two arguments: `key` and `value`, thus instantiating a root node (you can delete it, but you cannot create an empty tree). `insert` method also takes same arguments and adds a node with specified `key` and `value` properties to the tree. Method `setName` allows you to set the name of a tree. @@ -47,7 +47,7 @@ Find or remove element from tree: AVL and red-black trees implement the same methods. ## Storing binary search trees -AVL tree can be saved to and loaded from JSON file. +AVL tree can be saved to and loaded from JSON file. For example: ```kotlin val tree = AVLTree(1, "apple") diff --git a/trees/build.gradle.kts b/trees/build.gradle.kts index b061174..a1cbaf8 100644 --- a/trees/build.gradle.kts +++ b/trees/build.gradle.kts @@ -4,7 +4,6 @@ plugins { id("jacoco") id("org.jetbrains.kotlin.plugin.noarg") version "1.8.20" id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" - } repositories { @@ -21,14 +20,11 @@ dependencies { implementation("io.github.microutils:kotlin-logging-jvm:2.0.11") - implementation("org.slf4j", "slf4j-simple", "2.0.0") - implementation("org.neo4j:neo4j-ogm-core:4.0.5") runtimeOnly("org.neo4j:neo4j-ogm-bolt-driver:4.0.5") - implementation("com.google.code.gson:gson:2.10.1") // Use JetBrains Exposed @@ -40,8 +36,6 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.postgresql:postgresql:42.5.4") - - } tasks.test { diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index 908341d..dbba956 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -25,7 +25,6 @@ abstract class AbstractBST, V, Self : BinaryNode> return node } - override fun remove(key: K) { rootNode = removeNode(rootNode, key) } @@ -72,4 +71,3 @@ abstract class AbstractBST, V, Self : BinaryNode> else -> findMax(node.right) } } - diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index e1bf50a..b80a0f7 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -99,14 +99,14 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie removeNode(key) } - private fun removeNode(key: K): Int { if (rootNode != null) { /* False tree root. - */ - val head = initNode(key, rootNode?.value + val head = initNode( + key, + rootNode?.value ?: throw IllegalStateException("Root of the tree cannot be null")) var iter: RBTNode = head var parent: RBTNode? = null @@ -159,8 +159,10 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie sibling.color = RBTNode.Color.RED iter.color = RBTNode.Color.RED } else { - val direction2 = (grandparent?.right - ?: throw IllegalStateException("Grandparent node cannot be null")) == parent + val direction2 = ( + grandparent?.right + ?: throw IllegalStateException("Grandparent node cannot be null") + ) == parent if (isRed(sibling.child(last))) { if (direction2) { diff --git a/trees/src/main/kotlin/bst/db/controllers/Controller.kt b/trees/src/main/kotlin/bst/db/controllers/Controller.kt index 4178684..96f3ccc 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Controller.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Controller.kt @@ -3,8 +3,8 @@ package bst.db.controllers import bst.AbstractBST import bst.nodes.BinaryNode -interface Controller, T: AbstractBST> { +interface Controller, T : AbstractBST> { fun saveTree(tree: T, treeName: String) fun getTree(treeName: String): T? fun removeTree(treeName: String) -} \ No newline at end of file +} diff --git a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt index e49ab05..f684807 100644 --- a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -7,7 +7,7 @@ import java.io.File import java.io.FileReader import java.io.FileWriter -class JsonController: Controller, AVLTree> { +class JsonController : Controller, AVLTree> { override fun saveTree(tree: AVLTree, treeName: String) { val gson = Gson() try { diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index 84545ce..07eb0bd 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -10,7 +10,7 @@ import bst.nodes.RBTNode.Color import org.neo4j.ogm.config.Configuration import org.neo4j.ogm.session.SessionFactory -class Neo4jController: Controller, RedBlackTree> { +class Neo4jController : Controller, RedBlackTree> { private val config = Configuration.Builder() .uri("bolt://localhost") .credentials("neo4j", "password") @@ -56,7 +56,9 @@ class Neo4jController: Controller, RedBlackTree? { node ?: return null val rbtNode = RBTNode( - key = node.key.toInt(), value = node.value, color = deserializeMetadata(node.metadata) + key = node.key.toInt(), + value = node.value, + color = deserializeMetadata(node.metadata) ) rbtNode.left = deserializeNode(node.leftNode) rbtNode.right = deserializeNode(node.rightNode) @@ -82,12 +84,14 @@ class Neo4jController: Controller, RedBlackTree()) + session.query("MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", + mapOf()) } override fun removeTree(treeName: String) { session.query( - "MATCH (n)-[r *0..]->(m) " + "WHERE n.treeName = \$treeName DETACH DELETE m", mapOf("treeName" to treeName) + "MATCH (n)-[r *0..]->(m) " + "WHERE n.treeName = \$treeName DETACH DELETE m", + mapOf("treeName" to treeName) ) } diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index aa76109..f9bd416 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -5,17 +5,22 @@ import bst.db.serializeClasses.SerializableNode import bst.db.serializeClasses.SerializableTree import bst.nodes.BSTNode import bst.db.models.sql.Node -import org.jetbrains.exposed.sql.* import bst.db.models.sql.Trees import bst.db.models.sql.Nodes import bst.db.models.sql.Tree +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.transaction - -class SQLController: Controller, BSTree>{ +class SQLController : Controller, BSTree> { private fun connectDB() { Database.connect( - "jdbc:postgresql://localhost:5432/test", driver = "org.postgresql.Driver", user = "test", password = "test" + "jdbc:postgresql://localhost:5432/test", + driver = "org.postgresql.Driver", + user = "test", + password = "test-test" ) } @@ -52,7 +57,6 @@ class SQLController: Controller, BSTree>{ SchemaUtils.create(Nodes) } - private fun SerializableNode.toNodeDao(treeDao: Tree): Node { return Node.new { key = this@toNodeDao.key @@ -87,9 +91,8 @@ class SQLController: Controller, BSTree>{ this@getSerializedNode.y, null, this@getSerializedNode.left?.getSerializedNode(treeDao), - this@getSerializedNode.right?.getSerializedNode(treeDao), + this@getSerializedNode.right?.getSerializedNode(treeDao) ) - } private fun findTree(treeName: String): SerializableTree? { @@ -97,7 +100,8 @@ class SQLController: Controller, BSTree>{ val treeDAO = Tree.find { Trees.name eq treeName }.firstOrNull() ?: return null return treeDAO.rootNode?.getSerializedNode(treeDAO)?.let { SerializableTree( - treeName, it + treeName, + it ) } } @@ -111,7 +115,6 @@ class SQLController: Controller, BSTree>{ } } - private fun deserializeNodeDoubleKey(node: SerializableNode?): BSTNode? { return if (node == null) { null diff --git a/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt b/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt index 4bff1e3..fa39040 100644 --- a/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt +++ b/trees/src/main/kotlin/bst/db/models/neo4j/TreeEntity.kt @@ -7,7 +7,7 @@ import org.neo4j.ogm.annotation.NodeEntity @NodeEntity class TreeEntity( var treeName: String = "", - var rootNode: TreeNodeEntity? = null, + var rootNode: TreeNodeEntity? = null ) { @Id @GeneratedValue diff --git a/trees/src/main/kotlin/bst/db/models/sql/Node.kt b/trees/src/main/kotlin/bst/db/models/sql/Node.kt index fa1a764..3987446 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Node.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Node.kt @@ -1,6 +1,7 @@ package bst.db.models.sql -import org.jetbrains.exposed.dao.* +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID class Node(id: EntityID) : IntEntity(id) { @@ -14,5 +15,4 @@ class Node(id: EntityID) : IntEntity(id) { var right by Node optionalReferencedOn Nodes.right var tree by Tree referencedOn Nodes.tree override fun toString(): String = "Node(key = $key, value=$value, x=$x, y=$y, left=$left, right=$right, tree=$tree)" - } diff --git a/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt b/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt index aeb1e42..1c11751 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Nodes.kt @@ -12,4 +12,3 @@ object Nodes : IntIdTable("nodes") { val right = reference("right", Nodes).nullable() val tree = reference("tree", Trees, onDelete = ReferenceOption.CASCADE) } - diff --git a/trees/src/main/kotlin/bst/db/models/sql/Tree.kt b/trees/src/main/kotlin/bst/db/models/sql/Tree.kt index 7352500..82d0a38 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Tree.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Tree.kt @@ -11,4 +11,3 @@ class Tree(id: EntityID) : IntEntity(id) { var rootNode by Node optionalReferencedOn Trees.rootNode override fun toString(): String = "Node(key = $name, rootNode=$rootNode)" } - diff --git a/trees/src/main/kotlin/bst/db/models/sql/Trees.kt b/trees/src/main/kotlin/bst/db/models/sql/Trees.kt index f7a3fae..ca31743 100644 --- a/trees/src/main/kotlin/bst/db/models/sql/Trees.kt +++ b/trees/src/main/kotlin/bst/db/models/sql/Trees.kt @@ -6,4 +6,3 @@ object Trees : IntIdTable("trees") { val name = varchar("name", length = 128).uniqueIndex() val rootNode = reference("rootNode", Nodes.id).nullable() } - diff --git a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt index c92ced6..32c6718 100644 --- a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableNode.kt @@ -10,5 +10,5 @@ class SerializableNode( var y: Double = 0.0, var metadata: String? = null, var leftNode: SerializableNode? = null, - var rightNode: SerializableNode? = null, + var rightNode: SerializableNode? = null ) diff --git a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt index 1cbd32c..6abd06d 100644 --- a/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt +++ b/trees/src/main/kotlin/bst/db/serializeClasses/SerializableTree.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable class SerializableTree( var treeName: String, - var rootNode: SerializableNode?, + var rootNode: SerializableNode? ) diff --git a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt index facf88a..a9e8137 100644 --- a/trees/src/main/kotlin/bst/nodes/BinaryNode.kt +++ b/trees/src/main/kotlin/bst/nodes/BinaryNode.kt @@ -6,4 +6,4 @@ abstract class BinaryNode, V, Self : BinaryNode>( ) { var left: Self? = null var right: Self? = null -} \ No newline at end of file +} From dcc1a304b811f383e3f21c04594e39d28f630121 Mon Sep 17 00:00:00 2001 From: ancavar Date: Sun, 30 Apr 2023 21:02:39 +0300 Subject: [PATCH 108/118] refactor: more --- trees/src/main/kotlin/bst/RedBlackTree.kt | 5 +++-- .../src/main/kotlin/bst/db/controllers/Neo4jController.kt | 6 ++++-- trees/src/main/kotlin/bst/db/controllers/SQLController.kt | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/trees/src/main/kotlin/bst/RedBlackTree.kt b/trees/src/main/kotlin/bst/RedBlackTree.kt index b80a0f7..827999d 100644 --- a/trees/src/main/kotlin/bst/RedBlackTree.kt +++ b/trees/src/main/kotlin/bst/RedBlackTree.kt @@ -107,7 +107,8 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie val head = initNode( key, rootNode?.value - ?: throw IllegalStateException("Root of the tree cannot be null")) + ?: throw IllegalStateException("Root of the tree cannot be null") + ) var iter: RBTNode = head var parent: RBTNode? = null var grandparent: RBTNode? @@ -161,7 +162,7 @@ class RedBlackTree, V>(@Transient val key: K? = null, @Transie } else { val direction2 = ( grandparent?.right - ?: throw IllegalStateException("Grandparent node cannot be null") + ?: throw IllegalStateException("Grandparent node cannot be null") ) == parent if (isRed(sibling.child(last))) { diff --git a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt index 07eb0bd..2524b23 100644 --- a/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/Neo4jController.kt @@ -84,8 +84,10 @@ class Neo4jController : Controller, RedBlackTree()) + session.query( + "MATCH (n: BinaryNode) WHERE NOT (n)--() DELETE (n)", + mapOf() + ) } override fun removeTree(treeName: String) { diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index f9bd416..ce0b714 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -1,13 +1,13 @@ package bst.db.controllers import bst.BSTree -import bst.db.serializeClasses.SerializableNode -import bst.db.serializeClasses.SerializableTree -import bst.nodes.BSTNode import bst.db.models.sql.Node -import bst.db.models.sql.Trees import bst.db.models.sql.Nodes import bst.db.models.sql.Tree +import bst.db.models.sql.Trees +import bst.db.serializeClasses.SerializableNode +import bst.db.serializeClasses.SerializableTree +import bst.nodes.BSTNode import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.StdOutSqlLogger From 7ebf862abaf3d88a3aa84d482e69c897042a781e Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 1 May 2023 11:15:30 +0300 Subject: [PATCH 109/118] feat: add ability to delete node --- .../kotlin/app/controller/BSTController.kt | 13 +++++---- .../kotlin/app/view/SetTreeNameFragment.kt | 23 ---------------- .../app/view/treeView/BinarySearchTreeView.kt | 24 ++++++++++++++--- trees/src/main/kotlin/bst/AbstractBST.kt | 6 ++--- .../bst/db/controllers/SQLController.kt | 27 ++++++++++++++----- 5 files changed, 53 insertions(+), 40 deletions(-) delete mode 100644 app/src/main/kotlin/app/view/SetTreeNameFragment.kt diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index 4f96741..41361ab 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -12,6 +12,7 @@ import javafx.scene.control.Label import bst.db.controllers.SQLController import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList +import kotlin.reflect.jvm.internal.impl.resolve.constants.KClassValue.Value class BSTController : Controller() { fun isNumeric(s: String): Boolean { @@ -90,13 +91,15 @@ class BSTController : Controller() { fun deleteTreeFromDB(name: String) { SQLController().run { - deleteTree(name) + removeTree(name) } } - fun saveTree(tree: BSTree) { + fun saveTree(tree: BSTree, treeName: String) { val controller = SQLController() - controller.saveTreeToDB(tree) + controller.saveTree(tree, treeName) + } + fun deleteNode(value: Int, tree: BSTree, treePane: Pane){ + tree.remove(value) + drawTree(tree, treePane) } } - - diff --git a/app/src/main/kotlin/app/view/SetTreeNameFragment.kt b/app/src/main/kotlin/app/view/SetTreeNameFragment.kt deleted file mode 100644 index ed2ea21..0000000 --- a/app/src/main/kotlin/app/view/SetTreeNameFragment.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.view - -import bst.BSTree -import javafx.beans.property.SimpleStringProperty -import tornadofx.* -import app.controller.BSTController - -class SetTreeNameFragment : Fragment() { - private val name = SimpleStringProperty() - private val controller = BSTController() - override val root = vbox { - field("Input tree name") { - textfield(name) - } - button("save") { - close() -// tree.setName(name.value) -// if (tree.getRoot() != null) { -// controller.saveTree(tree) -// } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index 514d2b7..e2904a8 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -17,6 +17,7 @@ class BinarySearchTreeView : View() { private var trees = controller.getTreesList() private var selectedItem: String? = "" private val treeName = SimpleStringProperty() + private val valueFotDeletion = SimpleStringProperty() override val root = vbox { hbox { val availableTrees = combobox { @@ -72,20 +73,37 @@ class BinarySearchTreeView : View() { key.value = "" value.value = "" } - } + field("Value input"){ + textfield(valueFotDeletion) + } + button("Delete node"){ + action { + if (controller.isNumeric(valueFotDeletion.value)){ + controller.deleteNode(valueFotDeletion.value.toInt(), tree, treePane) + } + else{ + alert(type = Alert.AlertType.ERROR, header = "Deletion Error") + } + } + } + field("Input tree name") { textfield(treeName) } button("Save tree") { action { if (tree.getRoot() != null) { - tree.treeName = treeName.value - controller.saveTree(tree) +// tree.treeName = treeName.value + controller.saveTree(tree, treeName.value) if (!availableTrees.items.contains(treeName.value)) { availableTrees.items.add(treeName.value) } } + else{ + alert(type = Alert.AlertType.ERROR, header = "Can not save tree with empty root") + + } } } } diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index c1997d7..c33253f 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -7,9 +7,9 @@ abstract class AbstractBST, V, Self : BinaryNode> internal var rootNode: Self? = null - fun setName(treeName: String){ - this.treeName = treeName - } +// fun setName(treeName: String){ +// this.treeName = treeName +// } fun getRoot(): Self? = this.rootNode diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index ce0b714..eef946a 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -8,10 +8,8 @@ import bst.db.models.sql.Trees import bst.db.serializeClasses.SerializableNode import bst.db.serializeClasses.SerializableTree import bst.nodes.BSTNode -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger +import org.jetbrains.exposed.exceptions.ExposedSQLException +import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction class SQLController : Controller, BSTree> { @@ -26,8 +24,13 @@ class SQLController : Controller, BSTree> { override fun removeTree(treeName: String) { transaction { - val treeEntity = Tree.find { (Trees.name eq treeName) }.firstOrNull() - treeEntity?.delete() + try{ + Tree.find { (Trees.name eq treeName) } + .firstOrNull()?.delete() + } + catch (e: ExposedSQLException){ + println("Tree does not exists") + } } } @@ -145,4 +148,16 @@ class SQLController : Controller, BSTree> { } return deserializeTree(deserializedTree) } + fun getAllTrees(): List { + val notes = mutableListOf() + connectDB() + transaction { + Trees.selectAll().forEach { + val name = it[Trees.name] + notes.add(name) + } + } + + return notes + } } From 360080d6d2364878a1ceb925c458a27f2d4a3f94 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 1 May 2023 12:37:19 +0300 Subject: [PATCH 110/118] fix: fix a bug related to the fact that tables were not created when starting the application --- trees/src/main/kotlin/bst/db/controllers/SQLController.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt index eef946a..733c9e9 100644 --- a/trees/src/main/kotlin/bst/db/controllers/SQLController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/SQLController.kt @@ -20,6 +20,7 @@ class SQLController : Controller, BSTree> { user = "test", password = "test-test" ) + createTables() } override fun removeTree(treeName: String) { @@ -56,8 +57,10 @@ class SQLController : Controller, BSTree> { } private fun createTables() { - SchemaUtils.create(Trees) - SchemaUtils.create(Nodes) + transaction { + SchemaUtils.create(Trees) + SchemaUtils.create(Nodes) + } } private fun SerializableNode.toNodeDao(treeDao: Tree): Node { From 1fa30f70817333b87e8cbebf0ebabaee0e6c7714 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 1 May 2023 14:27:17 +0300 Subject: [PATCH 111/118] feat: Add the ability to draw an AVl tree and work with it through the graphical interface --- .../kotlin/app/controller/AVLController.kt | 101 ++++++++++++++++ .../kotlin/app/controller/BSTController.kt | 2 - .../kotlin/app/controller/RBTController.kt | 5 + .../kotlin/app/view/treeView/AVLTreeView.kt | 111 ++++++++++++++++++ trees/src/main/kotlin/bst/AbstractBST.kt | 7 +- .../bst/db/controllers/JsonController.kt | 19 ++- 6 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 app/src/main/kotlin/app/controller/AVLController.kt create mode 100644 app/src/main/kotlin/app/controller/RBTController.kt diff --git a/app/src/main/kotlin/app/controller/AVLController.kt b/app/src/main/kotlin/app/controller/AVLController.kt new file mode 100644 index 0000000..9467dac --- /dev/null +++ b/app/src/main/kotlin/app/controller/AVLController.kt @@ -0,0 +1,101 @@ +package app.controller + +import bst.AVLTree +import bst.db.controllers.JsonController +import bst.nodes.AVLNode +import javafx.collections.FXCollections +import javafx.collections.ObservableList +import javafx.scene.control.Label +import javafx.scene.layout.Pane +import javafx.scene.paint.Color +import javafx.scene.shape.Circle +import javafx.scene.shape.Line +import tornadofx.Controller + +class AVLController: Controller() { + fun isNumeric(s: String): Boolean { + return try { + s.toInt() + true + } catch (e: NumberFormatException) { + false + } + } + + fun insertNode(tree: AVLTree, treePane: Pane, key: Int, value: String) { + tree.insert(key, value) + drawTree(tree, treePane) + } + + fun clearTree(tree: AVLTree, treePane: Pane) { + tree.clear() + treePane.children.clear() + } + + //make here not null check + fun drawTree(tree: AVLTree, treePane: Pane) { + treePane.children.clear() + val root = tree.getRoot() + if (root != null) { + drawNode(root, treePane, treePane.width / 2.0, 50.0, treePane.width / 4.0) + } + } + + private fun drawNode(node: AVLNode, treePane: Pane, x: Double, y: Double, offsetX: Double) { + val circleRadius = 20.0 + val circle = Circle(circleRadius) + circle.centerX = x + circle.centerY = y + circle.fill = Color.WHITE + circle.stroke = Color.BLACK + val nodeLabel = Label(node.key.toString()) + nodeLabel.layoutX = circle.centerX - (circle.radius / 3) + nodeLabel.layoutY = circle.centerY - (circle.radius / 3) + treePane.children.addAll(circle, nodeLabel) + + if (node.left != null) { + val leftX = x - offsetX + val leftY = y + 50 + val leftLine = Line(x, y + circleRadius, leftX, leftY - circleRadius) + treePane.children.add(leftLine) + drawNode(node.left!!, treePane, leftX, leftY, offsetX / 2.0) + } + + if (node.right != null) { + val rightX = x + offsetX + val rightY = y + 50 + val rightLine = Line(x, y + circleRadius, rightX, rightY - circleRadius) + treePane.children.add(rightLine) + drawNode(node.right!!, treePane, rightX, rightY, offsetX / 2.0) + } + } + + fun getTreesList(): ObservableList? { + val controller = JsonController() + val treeNames = controller.getAllTrees() + val values = FXCollections.observableArrayList() + treeNames.forEach { + values.add(it) + } + return values + } + fun getTreeFromJson(name: String): AVLTree? { + val controller = JsonController() + val tree = controller.getTree(name) + return tree + } + + fun deleteTreeFromDB(name: String) { + JsonController().run { + removeTree(name) + } + } + fun saveTree(tree: AVLTree, treeName: String) { + val controller = JsonController() + controller.saveTree(tree, treeName) + } + fun deleteNode(value: Int, tree: AVLTree, treePane: Pane){ + tree.remove(value) + drawTree(tree, treePane) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index 41361ab..b5b75db 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -12,7 +12,6 @@ import javafx.scene.control.Label import bst.db.controllers.SQLController import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList -import kotlin.reflect.jvm.internal.impl.resolve.constants.KClassValue.Value class BSTController : Controller() { fun isNumeric(s: String): Boolean { @@ -30,7 +29,6 @@ class BSTController : Controller() { } fun clearTree(tree: BSTree, treePane: Pane) { - val controller = SQLController() tree.clear() treePane.children.clear() } diff --git a/app/src/main/kotlin/app/controller/RBTController.kt b/app/src/main/kotlin/app/controller/RBTController.kt new file mode 100644 index 0000000..36efee9 --- /dev/null +++ b/app/src/main/kotlin/app/controller/RBTController.kt @@ -0,0 +1,5 @@ +package app.controller + +class RBTController { + +} diff --git a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt index e1175b3..592f25e 100644 --- a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt @@ -1,7 +1,112 @@ package app.view.treeView +import app.controller.AVLController +import bst.AVLTree +import bst.BSTree +import javafx.beans.property.SimpleStringProperty +import javafx.scene.control.Alert +import javafx.scene.layout.Pane import tornadofx.* class AVLTreeView: View() { + private val controller: AVLController by inject() + private var tree = AVLTree() + private val treePane = Pane() + private val key = SimpleStringProperty() + private val value = SimpleStringProperty() + private var trees = controller.getTreesList() + private var selectedItem: String? = "" + private val treeName = SimpleStringProperty() + private val valueFotDeletion = SimpleStringProperty() override val root = vbox { + hbox { + val availableTrees = combobox { + this@AVLTreeView.trees?.let { items.addAll(it) } + selectionModel.selectedItemProperty().addListener { _, _, newValue -> + this@AVLTreeView.selectedItem = newValue + } + } + + button("Select") { + action { + println("Selected item: $selectedItem") + val loadedTree = selectedItem?.let { controller.getTreeFromJson(it) } + if (loadedTree != null) { + tree = loadedTree + controller.drawTree(tree, treePane) + } + } + } + button("Delete") { + action { + selectedItem?.let { + controller.clearTree(tree, treePane) + controller.deleteTreeFromDB(it) + } + availableTrees.items.remove(selectedItem) + + println("Item deleted: $selectedItem") + } + } + + button("Clear") { + action { + controller.clearTree(tree, treePane) + } + } + form { + fieldset { + field("Key input") { + textfield(key) + } + field("Value input") { + textfield(value) + } + + button("Add Node") { + action { + if (key.value != null && value.value != null && controller.isNumeric(key.value)) { + controller.insertNode(tree, treePane, key.value.toInt(), value.value) + } else { + alert(type = Alert.AlertType.ERROR, header = "Insertion Error") + } + key.value = "" + value.value = "" + } + } + field("Value input"){ + textfield(valueFotDeletion) + } + button("Delete node"){ + action { + if (controller.isNumeric(valueFotDeletion.value)){ + controller.deleteNode(valueFotDeletion.value.toInt(), tree, treePane) + } + else{ + alert(type = Alert.AlertType.ERROR, header = "Deletion Error") + } + } + } + + field("Input tree name") { + textfield(treeName) + } + button("Save tree") { + action { + if (tree.getRoot() != null) { +// tree.treeName = treeName.value + controller.saveTree(tree, treeName.value) + if (!availableTrees.items.contains(treeName.value)) { + availableTrees.items.add(treeName.value) + } + } + else{ + alert(type = Alert.AlertType.ERROR, header = "Can not save tree with empty root") + + } + } + } + } + } + } button("Binary Search Tree") { action { replaceWith(BinarySearchTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) @@ -12,5 +117,11 @@ class AVLTreeView: View() { replaceWith(RedBlackTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) } } + this += treePane + treePane.apply { + minWidth = 600.0 + minHeight = 400.0 + style = "-fx-border-color: black;" + } } } \ No newline at end of file diff --git a/trees/src/main/kotlin/bst/AbstractBST.kt b/trees/src/main/kotlin/bst/AbstractBST.kt index c33253f..40833c2 100644 --- a/trees/src/main/kotlin/bst/AbstractBST.kt +++ b/trees/src/main/kotlin/bst/AbstractBST.kt @@ -5,11 +5,12 @@ import bst.nodes.BinaryNode abstract class AbstractBST, V, Self : BinaryNode> : Tree { + var treeName: String = "" internal var rootNode: Self? = null -// fun setName(treeName: String){ -// this.treeName = treeName -// } + fun setName(treeName: String){ + this.treeName = treeName + } fun getRoot(): Self? = this.rootNode diff --git a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt index 99411d0..a9b2a74 100644 --- a/trees/src/main/kotlin/bst/db/controllers/JsonController.kt +++ b/trees/src/main/kotlin/bst/db/controllers/JsonController.kt @@ -8,10 +8,11 @@ import java.io.FileReader import java.io.FileWriter class JsonController : Controller, AVLTree> { + val folderPath = "json" override fun saveTree(tree: AVLTree, treeName: String) { val gson = Gson() try { - val writer = FileWriter("$treeName.json") + val writer = FileWriter("${this.folderPath}/$treeName.json") gson.toJson(tree, writer) writer.close() } catch (e: Exception) { @@ -22,7 +23,7 @@ class JsonController : Controller, AVLTree> { override fun getTree(treeName: String): AVLTree? { val gson = Gson() return try { - val reader = FileReader("$treeName.json") + val reader = FileReader("${this.folderPath}/$treeName.json") val tree = gson.fromJson(reader, AVLTree()::class.java) reader.close() tree @@ -33,6 +34,16 @@ class JsonController : Controller, AVLTree> { } override fun removeTree(treeName: String) { - File("$treeName.json").delete() + File("${this.folderPath}/$treeName.json").delete() } -} + + fun getAllTrees(): List{ + val jsonFiles = File(folderPath).listFiles{ file -> file.extension == "json" } + val jsonNames = mutableListOf() + jsonFiles?.forEach { file -> + jsonNames.add(file.name.removeSuffix(".json")) +// jsonNames.add(file.name) + } + return jsonNames + } +} \ No newline at end of file From 2163189cef354b6581d5b9d5ad33eb0e8987012b Mon Sep 17 00:00:00 2001 From: Nikita Nemakin Date: Mon, 1 May 2023 16:57:14 +0300 Subject: [PATCH 112/118] feat: Add RBTree visualization with ability to interact with Neo4j DB --- .../kotlin/app/controller/RBTController.kt | 98 +++++++++++++- .../kotlin/app/view/treeView/AVLTreeView.kt | 3 +- .../app/view/treeView/BinarySearchTreeView.kt | 3 +- .../app/view/treeView/RedBlackTreeView.kt | 121 +++++++++++++++++- 4 files changed, 216 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/app/controller/RBTController.kt b/app/src/main/kotlin/app/controller/RBTController.kt index 36efee9..1b3590d 100644 --- a/app/src/main/kotlin/app/controller/RBTController.kt +++ b/app/src/main/kotlin/app/controller/RBTController.kt @@ -1,5 +1,101 @@ package app.controller -class RBTController { +import bst.RedBlackTree +import bst.nodes.RBTNode +import bst.db.controllers.JsonController +import bst.db.controllers.Neo4jController +import javafx.collections.FXCollections +import javafx.collections.ObservableList +import javafx.scene.control.Label +import javafx.scene.layout.Pane +import javafx.scene.paint.Color +import javafx.scene.shape.Circle +import javafx.scene.shape.Line +import tornadofx.Controller +class RBTController: Controller() { + fun isNumeric(s: String): Boolean { + return try { + s.toInt() + true + } catch (e: NumberFormatException) { + false + } + } + + fun insertNode(tree: RedBlackTree, treePane: Pane, key: Int, value: String) { + tree.insert(key, value) + drawTree(tree, treePane) + } + + fun clearTree(tree: RedBlackTree, treePane: Pane) { + tree.clear() + treePane.children.clear() + } + + //make here not null check + fun drawTree(tree: RedBlackTree, treePane: Pane) { + treePane.children.clear() + val root = tree.getRoot() + if (root != null) { + drawNode(root, treePane, treePane.width / 2.0, 50.0, treePane.width / 4.0) + } + } + + private fun drawNode(node: RBTNode, treePane: Pane, x: Double, y: Double, offsetX: Double) { + val circleRadius = 20.0 + val circle = Circle(circleRadius) + circle.centerX = x + circle.centerY = y + circle.fill = if (node.color == RBTNode.Color.RED) Color.RED else Color.BLACK + circle.stroke = Color.BLACK + val nodeLabel = Label(node.key.toString()) + nodeLabel.layoutX = circle.centerX - (circle.radius / 3) + nodeLabel.layoutY = circle.centerY - (circle.radius / 3) + treePane.children.addAll(circle, nodeLabel) + + if (node.left != null) { + val leftX = x - offsetX + val leftY = y + 50 + val leftLine = Line(x, y + circleRadius, leftX, leftY - circleRadius) + treePane.children.add(leftLine) + drawNode(node.left!!, treePane, leftX, leftY, offsetX / 2.0) + } + + if (node.right != null) { + val rightX = x + offsetX + val rightY = y + 50 + val rightLine = Line(x, y + circleRadius, rightX, rightY - circleRadius) + treePane.children.add(rightLine) + drawNode(node.right!!, treePane, rightX, rightY, offsetX / 2.0) + } + } + + fun getTreesList(): ObservableList? { + val controller = Neo4jController() + val treeNames = controller.getNames() + val values = FXCollections.observableArrayList() + treeNames.forEach { + values.add(it) + } + return values + } + fun getTreeFromNeo4j(name: String): RedBlackTree? { + val controller = Neo4jController() + return controller.getTree(name) + } + + fun deleteTreeFromDB(name: String) { + Neo4jController().run { + removeTree(name) + } + } + fun saveTree(tree: RedBlackTree, treeName: String) { + val controller = Neo4jController() + controller.saveTree(tree, treeName) + } + fun deleteNode(value: Int, tree: RedBlackTree, treePane: Pane){ + tree.remove(value) + drawTree(tree, treePane) + } } diff --git a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt index 592f25e..06e332f 100644 --- a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt @@ -16,6 +16,7 @@ class AVLTreeView: View() { private var selectedItem: String? = "" private val treeName = SimpleStringProperty() private val valueFotDeletion = SimpleStringProperty() + override val root = vbox { hbox { val availableTrees = combobox { @@ -72,7 +73,7 @@ class AVLTreeView: View() { value.value = "" } } - field("Value input"){ + field("Key input"){ textfield(valueFotDeletion) } button("Delete node"){ diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index e2904a8..a88f699 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -18,6 +18,7 @@ class BinarySearchTreeView : View() { private var selectedItem: String? = "" private val treeName = SimpleStringProperty() private val valueFotDeletion = SimpleStringProperty() + override val root = vbox { hbox { val availableTrees = combobox { @@ -74,7 +75,7 @@ class BinarySearchTreeView : View() { value.value = "" } } - field("Value input"){ + field("Key input"){ textfield(valueFotDeletion) } button("Delete node"){ diff --git a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt index 82c754c..30bd3c4 100644 --- a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt @@ -1,22 +1,131 @@ package app.view.treeView -import app.view.treeView.AVLTreeView -import app.view.treeView.BinarySearchTreeView + +import app.controller.RBTController import tornadofx.* +import bst.RedBlackTree +import bst.db.controllers.Neo4jController +import javafx.beans.property.SimpleStringProperty +import javafx.scene.layout.Pane +import javafx.scene.control.Alert + + +class RedBlackTreeView : View() { + private val controller: RBTController by inject() + private var tree = RedBlackTree() + private val treePane = Pane() + private val key = SimpleStringProperty() + private val value = SimpleStringProperty() + private var trees = controller.getTreesList() + private var selectedItem: String? = "" + private val treeName = SimpleStringProperty() + private val valueFotDeletion = SimpleStringProperty() -class RedBlackTreeView: View() { override val root = vbox { + hbox { + val availableTrees = combobox { + this@RedBlackTreeView.trees?.let { items.addAll(it) } + selectionModel.selectedItemProperty().addListener { _, _, newValue -> + this@RedBlackTreeView.selectedItem = newValue + } + } + + button("Select") { + action { + println("Selected item: $selectedItem") + val loadedTree = selectedItem?.let { controller.getTreeFromNeo4j(it) } + if (loadedTree != null) { + tree = loadedTree + controller.drawTree(tree, treePane) + } + } + } + button("Delete") { + action { + selectedItem?.let { + controller.clearTree(tree, treePane) + controller.deleteTreeFromDB(it) + } + availableTrees.items.remove(selectedItem) + + println("Item deleted: $selectedItem") + } + } + + button("Clear") { + action { + controller.clearTree(tree, treePane) + } + } + form { + fieldset { + field("Key input") { + textfield(key) + } + field("Value input") { + textfield(value) + } + + button("Add Node") { + action { + if (key.value != null && value.value != null && controller.isNumeric(key.value)) { + controller.insertNode(tree, treePane, key.value.toInt(), value.value) + } else { + alert(type = Alert.AlertType.ERROR, header = "Insertion Error") + } + key.value = "" + value.value = "" + } + } + field("Value input"){ + textfield(valueFotDeletion) + } + button("Delete node"){ + action { + if (controller.isNumeric(valueFotDeletion.value)){ + controller.deleteNode(valueFotDeletion.value.toInt(), tree, treePane) + } + else{ + alert(type = Alert.AlertType.ERROR, header = "Deletion Error") + } + } + } + + field("Input tree name") { + textfield(treeName) + } + button("Save tree") { + action { + if (tree.getRoot() != null) { +// tree.treeName = treeName.value + controller.saveTree(tree, treeName.value) + if (!availableTrees.items.contains(treeName.value)) { + availableTrees.items.add(treeName.value) + } + } + else{ + alert(type = Alert.AlertType.ERROR, header = "Can not save tree with empty root") + } + } + } + } + } + } button("AVL Tree") { action { replaceWith(AVLTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) - } } button("Binary Search Tree") { action { replaceWith(BinarySearchTreeView::class, ViewTransition.Slide(0.3.seconds, ViewTransition.Direction.LEFT)) - } } + this += treePane + treePane.apply { + minWidth = 600.0 + minHeight = 400.0 + style = "-fx-border-color: black;" + } } -} \ No newline at end of file +} From c34cc9c01faee71383a851f293a448559c7e83cc Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 1 May 2023 19:09:18 +0300 Subject: [PATCH 113/118] feat: better text positioning --- .../kotlin/app/controller/AVLController.kt | 27 +++++++++++++++--- .../kotlin/app/controller/BSTController.kt | 27 +++++++++++++++--- .../kotlin/app/controller/RBTController.kt | 28 +++++++++++++++---- 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/app/controller/AVLController.kt b/app/src/main/kotlin/app/controller/AVLController.kt index 9467dac..847d28e 100644 --- a/app/src/main/kotlin/app/controller/AVLController.kt +++ b/app/src/main/kotlin/app/controller/AVLController.kt @@ -6,11 +6,15 @@ import bst.nodes.AVLNode import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.scene.control.Label +import javafx.scene.control.Tooltip import javafx.scene.layout.Pane import javafx.scene.paint.Color import javafx.scene.shape.Circle import javafx.scene.shape.Line +import javafx.scene.text.Text +import javafx.scene.text.TextBoundsType import tornadofx.Controller +import kotlin.math.min class AVLController: Controller() { fun isNumeric(s: String): Boolean { @@ -48,10 +52,25 @@ class AVLController: Controller() { circle.centerY = y circle.fill = Color.WHITE circle.stroke = Color.BLACK - val nodeLabel = Label(node.key.toString()) - nodeLabel.layoutX = circle.centerX - (circle.radius / 3) - nodeLabel.layoutY = circle.centerY - (circle.radius / 3) - treePane.children.addAll(circle, nodeLabel) + + val nodeText = Text(node.key.toString()) + nodeText.x = circle.centerX + nodeText.y = circle.centerY + nodeText.boundsType = TextBoundsType.VISUAL + + val scale = min(circleRadius * 1.3 / nodeText.boundsInLocal.width, circleRadius * 1.3 / nodeText.boundsInLocal.height) + nodeText.scaleX = scale + nodeText.scaleY = scale + + nodeText.x -= nodeText.boundsInLocal.width / 2 + nodeText.y += nodeText.boundsInLocal.height / 3 + + val tooltip = Tooltip(node.value) + Tooltip.install(circle, tooltip) + Tooltip.install(nodeText, tooltip) + + treePane.children.addAll(circle, nodeText) + if (node.left != null) { val leftX = x - offsetX diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index b5b75db..f14723f 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -12,6 +12,10 @@ import javafx.scene.control.Label import bst.db.controllers.SQLController import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList +import javafx.scene.control.Tooltip +import javafx.scene.text.Text +import javafx.scene.text.TextBoundsType +import kotlin.math.min class BSTController : Controller() { fun isNumeric(s: String): Boolean { @@ -49,10 +53,25 @@ class BSTController : Controller() { circle.centerY = y circle.fill = Color.WHITE circle.stroke = Color.BLACK - val nodeLabel = Label(node.key.toString()) - nodeLabel.layoutX = circle.centerX - (circle.radius / 3) - nodeLabel.layoutY = circle.centerY - (circle.radius / 3) - treePane.children.addAll(circle, nodeLabel) + + val nodeText = Text(node.key.toString()) + nodeText.x = circle.centerX + nodeText.y = circle.centerY + nodeText.boundsType = TextBoundsType.VISUAL + + val scale = min(circleRadius * 1.3 / nodeText.boundsInLocal.width, circleRadius * 1.3 / nodeText.boundsInLocal.height) + nodeText.scaleX = scale + nodeText.scaleY = scale + + nodeText.x -= nodeText.boundsInLocal.width / 2 + nodeText.y += nodeText.boundsInLocal.height / 3 + + val tooltip = Tooltip(node.value) + Tooltip.install(circle, tooltip) + Tooltip.install(nodeText, tooltip) + + treePane.children.addAll(circle, nodeText) + if (node.left != null) { val leftX = x - offsetX diff --git a/app/src/main/kotlin/app/controller/RBTController.kt b/app/src/main/kotlin/app/controller/RBTController.kt index 1b3590d..83609c8 100644 --- a/app/src/main/kotlin/app/controller/RBTController.kt +++ b/app/src/main/kotlin/app/controller/RBTController.kt @@ -7,11 +7,15 @@ import bst.db.controllers.Neo4jController import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.scene.control.Label +import javafx.scene.control.Tooltip import javafx.scene.layout.Pane import javafx.scene.paint.Color import javafx.scene.shape.Circle import javafx.scene.shape.Line +import javafx.scene.text.Text +import javafx.scene.text.TextBoundsType import tornadofx.Controller +import kotlin.math.min class RBTController: Controller() { fun isNumeric(s: String): Boolean { @@ -47,12 +51,26 @@ class RBTController: Controller() { val circle = Circle(circleRadius) circle.centerX = x circle.centerY = y - circle.fill = if (node.color == RBTNode.Color.RED) Color.RED else Color.BLACK + circle.fill = Color.WHITE circle.stroke = Color.BLACK - val nodeLabel = Label(node.key.toString()) - nodeLabel.layoutX = circle.centerX - (circle.radius / 3) - nodeLabel.layoutY = circle.centerY - (circle.radius / 3) - treePane.children.addAll(circle, nodeLabel) + + val nodeText = Text(node.key.toString()) + nodeText.x = circle.centerX + nodeText.y = circle.centerY + nodeText.boundsType = TextBoundsType.VISUAL + + val scale = min(circleRadius * 1.3 / nodeText.boundsInLocal.width, circleRadius * 1.3 / nodeText.boundsInLocal.height) + nodeText.scaleX = scale + nodeText.scaleY = scale + + nodeText.x -= nodeText.boundsInLocal.width / 2 + nodeText.y += nodeText.boundsInLocal.height / 3 + + val tooltip = Tooltip(node.value) + Tooltip.install(circle, tooltip) + Tooltip.install(nodeText, tooltip) + + treePane.children.addAll(circle, nodeText) if (node.left != null) { val leftX = x - offsetX From e4549c92ce72bcc6c4fedd3e53de5403dd518e81 Mon Sep 17 00:00:00 2001 From: SurfaceYellowDuck Date: Mon, 1 May 2023 19:25:29 +0300 Subject: [PATCH 114/118] fix: fix build on mac --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 16eb552..2c24337 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - os: [ macos-latest, ubuntu-latest, windows-latest ] + os: [ ubuntu-latest, windows-latest ] steps: - uses: actions/checkout@v3 - name: Setup Gradle From 5caacb677623cceb6ca28fb4559da8b843c01ace Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 1 May 2023 19:47:15 +0300 Subject: [PATCH 115/118] feat: add tooltip to show value --- .../kotlin/app/controller/AVLController.kt | 17 ++++++----------- .../kotlin/app/controller/BSTController.kt | 18 ++++++------------ .../kotlin/app/controller/RBTController.kt | 18 ++++++------------ 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/app/src/main/kotlin/app/controller/AVLController.kt b/app/src/main/kotlin/app/controller/AVLController.kt index 847d28e..1260dc7 100644 --- a/app/src/main/kotlin/app/controller/AVLController.kt +++ b/app/src/main/kotlin/app/controller/AVLController.kt @@ -5,9 +5,9 @@ import bst.db.controllers.JsonController import bst.nodes.AVLNode import javafx.collections.FXCollections import javafx.collections.ObservableList -import javafx.scene.control.Label import javafx.scene.control.Tooltip import javafx.scene.layout.Pane +import javafx.scene.layout.StackPane import javafx.scene.paint.Color import javafx.scene.shape.Circle import javafx.scene.shape.Line @@ -48,28 +48,23 @@ class AVLController: Controller() { private fun drawNode(node: AVLNode, treePane: Pane, x: Double, y: Double, offsetX: Double) { val circleRadius = 20.0 val circle = Circle(circleRadius) - circle.centerX = x - circle.centerY = y circle.fill = Color.WHITE circle.stroke = Color.BLACK val nodeText = Text(node.key.toString()) - nodeText.x = circle.centerX - nodeText.y = circle.centerY nodeText.boundsType = TextBoundsType.VISUAL val scale = min(circleRadius * 1.3 / nodeText.boundsInLocal.width, circleRadius * 1.3 / nodeText.boundsInLocal.height) nodeText.scaleX = scale nodeText.scaleY = scale - nodeText.x -= nodeText.boundsInLocal.width / 2 - nodeText.y += nodeText.boundsInLocal.height / 3 + val nodeStackPane = StackPane(circle, nodeText) + nodeStackPane.relocate(x - circleRadius, y - circleRadius) - val tooltip = Tooltip(node.value) - Tooltip.install(circle, tooltip) - Tooltip.install(nodeText, tooltip) + val tooltip = Tooltip("value: ${node.value}") + Tooltip.install(nodeStackPane, tooltip) - treePane.children.addAll(circle, nodeText) + treePane.children.add(nodeStackPane) if (node.left != null) { diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index f14723f..8b1b746 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -8,11 +8,11 @@ import javafx.scene.shape.Circle import javafx.scene.shape.Line import tornadofx.Controller -import javafx.scene.control.Label import bst.db.controllers.SQLController import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList import javafx.scene.control.Tooltip +import javafx.scene.layout.StackPane import javafx.scene.text.Text import javafx.scene.text.TextBoundsType import kotlin.math.min @@ -49,29 +49,23 @@ class BSTController : Controller() { private fun drawNode(node: BSTNode, treePane: Pane, x: Double, y: Double, offsetX: Double) { val circleRadius = 20.0 val circle = Circle(circleRadius) - circle.centerX = x - circle.centerY = y circle.fill = Color.WHITE circle.stroke = Color.BLACK val nodeText = Text(node.key.toString()) - nodeText.x = circle.centerX - nodeText.y = circle.centerY nodeText.boundsType = TextBoundsType.VISUAL val scale = min(circleRadius * 1.3 / nodeText.boundsInLocal.width, circleRadius * 1.3 / nodeText.boundsInLocal.height) nodeText.scaleX = scale nodeText.scaleY = scale - nodeText.x -= nodeText.boundsInLocal.width / 2 - nodeText.y += nodeText.boundsInLocal.height / 3 + val nodeStackPane = StackPane(circle, nodeText) + nodeStackPane.relocate(x - circleRadius, y - circleRadius) - val tooltip = Tooltip(node.value) - Tooltip.install(circle, tooltip) - Tooltip.install(nodeText, tooltip) - - treePane.children.addAll(circle, nodeText) + val tooltip = Tooltip("value: ${node.value}") + Tooltip.install(nodeStackPane, tooltip) + treePane.children.add(nodeStackPane) if (node.left != null) { val leftX = x - offsetX diff --git a/app/src/main/kotlin/app/controller/RBTController.kt b/app/src/main/kotlin/app/controller/RBTController.kt index 83609c8..1b75a58 100644 --- a/app/src/main/kotlin/app/controller/RBTController.kt +++ b/app/src/main/kotlin/app/controller/RBTController.kt @@ -2,13 +2,12 @@ package app.controller import bst.RedBlackTree import bst.nodes.RBTNode -import bst.db.controllers.JsonController import bst.db.controllers.Neo4jController import javafx.collections.FXCollections import javafx.collections.ObservableList -import javafx.scene.control.Label import javafx.scene.control.Tooltip import javafx.scene.layout.Pane +import javafx.scene.layout.StackPane import javafx.scene.paint.Color import javafx.scene.shape.Circle import javafx.scene.shape.Line @@ -49,28 +48,23 @@ class RBTController: Controller() { private fun drawNode(node: RBTNode, treePane: Pane, x: Double, y: Double, offsetX: Double) { val circleRadius = 20.0 val circle = Circle(circleRadius) - circle.centerX = x - circle.centerY = y circle.fill = Color.WHITE circle.stroke = Color.BLACK val nodeText = Text(node.key.toString()) - nodeText.x = circle.centerX - nodeText.y = circle.centerY nodeText.boundsType = TextBoundsType.VISUAL val scale = min(circleRadius * 1.3 / nodeText.boundsInLocal.width, circleRadius * 1.3 / nodeText.boundsInLocal.height) nodeText.scaleX = scale nodeText.scaleY = scale - nodeText.x -= nodeText.boundsInLocal.width / 2 - nodeText.y += nodeText.boundsInLocal.height / 3 + val nodeStackPane = StackPane(circle, nodeText) + nodeStackPane.relocate(x - circleRadius, y - circleRadius) - val tooltip = Tooltip(node.value) - Tooltip.install(circle, tooltip) - Tooltip.install(nodeText, tooltip) + val tooltip = Tooltip("value: ${node.value}") + Tooltip.install(nodeStackPane, tooltip) - treePane.children.addAll(circle, nodeText) + treePane.children.add(nodeStackPane) if (node.left != null) { val leftX = x - offsetX From bd88cf0c6d3f85b004aa0796c2bae97f5e73b985 Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 1 May 2023 20:24:56 +0300 Subject: [PATCH 116/118] feat: add tree resizing when resizing app's window --- .../main/kotlin/app/view/treeView/AVLTreeView.kt | 16 ++++++++++++++++ .../app/view/treeView/BinarySearchTreeView.kt | 16 ++++++++++++++++ .../kotlin/app/view/treeView/RedBlackTreeView.kt | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt index 06e332f..1a547fa 100644 --- a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt @@ -17,6 +17,22 @@ class AVLTreeView: View() { private val treeName = SimpleStringProperty() private val valueFotDeletion = SimpleStringProperty() + init { + // Add listeners to the width and height properties of the scene + val sizeChangeListener = ChangeListener { _, _, _ -> + resizeTreePane() + } + + primaryStage.widthProperty().addListener(sizeChangeListener) + primaryStage.heightProperty().addListener(sizeChangeListener) + } + + private fun resizeTreePane() { + treePane.prefWidthProperty().bind(root.widthProperty()) + treePane.prefHeightProperty().bind(root.heightProperty()) + controller.drawTree(tree, treePane) + } + override val root = vbox { hbox { val availableTrees = combobox { diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index a88f699..943b23a 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -19,6 +19,22 @@ class BinarySearchTreeView : View() { private val treeName = SimpleStringProperty() private val valueFotDeletion = SimpleStringProperty() + init { + // Add listeners to the width and height properties of the scene + val sizeChangeListener = ChangeListener { _, _, _ -> + resizeTreePane() + } + + primaryStage.widthProperty().addListener(sizeChangeListener) + primaryStage.heightProperty().addListener(sizeChangeListener) + } + + private fun resizeTreePane() { + treePane.prefWidthProperty().bind(root.widthProperty()) + treePane.prefHeightProperty().bind(root.heightProperty()) + controller.drawTree(tree, treePane) + } + override val root = vbox { hbox { val availableTrees = combobox { diff --git a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt index 30bd3c4..c1e425b 100644 --- a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt @@ -20,6 +20,22 @@ class RedBlackTreeView : View() { private val treeName = SimpleStringProperty() private val valueFotDeletion = SimpleStringProperty() + init { + // Add listeners to the width and height properties of the scene + val sizeChangeListener = ChangeListener { _, _, _ -> + resizeTreePane() + } + + primaryStage.widthProperty().addListener(sizeChangeListener) + primaryStage.heightProperty().addListener(sizeChangeListener) + } + + private fun resizeTreePane() { + treePane.prefWidthProperty().bind(root.widthProperty()) + treePane.prefHeightProperty().bind(root.heightProperty()) + controller.drawTree(tree, treePane) + } + override val root = vbox { hbox { val availableTrees = combobox { From da4cfdf6a705e6a2bc11d56408a34d86a10964f2 Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 1 May 2023 22:33:39 +0300 Subject: [PATCH 117/118] refactor --- app/src/main/kotlin/app/App.kt | 5 ++- .../kotlin/app/controller/AVLController.kt | 12 +++---- .../kotlin/app/controller/BSTController.kt | 17 +++++----- .../kotlin/app/controller/RBTController.kt | 8 ++--- app/src/main/kotlin/app/view/MainView.kt | 4 +-- .../kotlin/app/view/treeView/AVLTreeView.kt | 31 ++++++++---------- .../app/view/treeView/BinarySearchTreeView.kt | 32 ++++++++----------- .../app/view/treeView/RedBlackTreeView.kt | 30 ++++++++--------- 8 files changed, 62 insertions(+), 77 deletions(-) diff --git a/app/src/main/kotlin/app/App.kt b/app/src/main/kotlin/app/App.kt index a717fbe..15ca4c5 100644 --- a/app/src/main/kotlin/app/App.kt +++ b/app/src/main/kotlin/app/App.kt @@ -2,9 +2,8 @@ package app import app.view.MainView import tornadofx.* -class MyApp : App(MainView::class){ -} +class MyApp : App(MainView::class) fun main() { launch() -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/app/controller/AVLController.kt b/app/src/main/kotlin/app/controller/AVLController.kt index 1260dc7..c1e43d7 100644 --- a/app/src/main/kotlin/app/controller/AVLController.kt +++ b/app/src/main/kotlin/app/controller/AVLController.kt @@ -16,7 +16,7 @@ import javafx.scene.text.TextBoundsType import tornadofx.Controller import kotlin.math.min -class AVLController: Controller() { +class AVLController : Controller() { fun isNumeric(s: String): Boolean { return try { s.toInt() @@ -36,7 +36,7 @@ class AVLController: Controller() { treePane.children.clear() } - //make here not null check + // make here not null check fun drawTree(tree: AVLTree, treePane: Pane) { treePane.children.clear() val root = tree.getRoot() @@ -66,7 +66,6 @@ class AVLController: Controller() { treePane.children.add(nodeStackPane) - if (node.left != null) { val leftX = x - offsetX val leftY = y + 50 @@ -95,8 +94,7 @@ class AVLController: Controller() { } fun getTreeFromJson(name: String): AVLTree? { val controller = JsonController() - val tree = controller.getTree(name) - return tree + return controller.getTree(name) } fun deleteTreeFromDB(name: String) { @@ -108,8 +106,8 @@ class AVLController: Controller() { val controller = JsonController() controller.saveTree(tree, treeName) } - fun deleteNode(value: Int, tree: AVLTree, treePane: Pane){ + fun deleteNode(value: Int, tree: AVLTree, treePane: Pane) { tree.remove(value) drawTree(tree, treePane) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/app/controller/BSTController.kt b/app/src/main/kotlin/app/controller/BSTController.kt index 8b1b746..9b494d9 100644 --- a/app/src/main/kotlin/app/controller/BSTController.kt +++ b/app/src/main/kotlin/app/controller/BSTController.kt @@ -1,20 +1,19 @@ package app.controller import bst.BSTree -import bst.nodes.BSTNode -import javafx.scene.layout.Pane -import javafx.scene.paint.Color -import javafx.scene.shape.Circle -import javafx.scene.shape.Line - -import tornadofx.Controller import bst.db.controllers.SQLController +import bst.nodes.BSTNode import javafx.collections.FXCollections.observableArrayList import javafx.collections.ObservableList import javafx.scene.control.Tooltip +import javafx.scene.layout.Pane import javafx.scene.layout.StackPane +import javafx.scene.paint.Color +import javafx.scene.shape.Circle +import javafx.scene.shape.Line import javafx.scene.text.Text import javafx.scene.text.TextBoundsType +import tornadofx.Controller import kotlin.math.min class BSTController : Controller() { @@ -37,7 +36,7 @@ class BSTController : Controller() { treePane.children.clear() } - //make here not null check + // make here not null check fun drawTree(tree: BSTree, treePane: Pane) { treePane.children.clear() val root = tree.getRoot() @@ -109,7 +108,7 @@ class BSTController : Controller() { val controller = SQLController() controller.saveTree(tree, treeName) } - fun deleteNode(value: Int, tree: BSTree, treePane: Pane){ + fun deleteNode(value: Int, tree: BSTree, treePane: Pane) { tree.remove(value) drawTree(tree, treePane) } diff --git a/app/src/main/kotlin/app/controller/RBTController.kt b/app/src/main/kotlin/app/controller/RBTController.kt index 1b75a58..9fc2d04 100644 --- a/app/src/main/kotlin/app/controller/RBTController.kt +++ b/app/src/main/kotlin/app/controller/RBTController.kt @@ -1,8 +1,8 @@ package app.controller import bst.RedBlackTree -import bst.nodes.RBTNode import bst.db.controllers.Neo4jController +import bst.nodes.RBTNode import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.scene.control.Tooltip @@ -16,7 +16,7 @@ import javafx.scene.text.TextBoundsType import tornadofx.Controller import kotlin.math.min -class RBTController: Controller() { +class RBTController : Controller() { fun isNumeric(s: String): Boolean { return try { s.toInt() @@ -36,7 +36,7 @@ class RBTController: Controller() { treePane.children.clear() } - //make here not null check + // make here not null check fun drawTree(tree: RedBlackTree, treePane: Pane) { treePane.children.clear() val root = tree.getRoot() @@ -106,7 +106,7 @@ class RBTController: Controller() { val controller = Neo4jController() controller.saveTree(tree, treeName) } - fun deleteNode(value: Int, tree: RedBlackTree, treePane: Pane){ + fun deleteNode(value: Int, tree: RedBlackTree, treePane: Pane) { tree.remove(value) drawTree(tree, treePane) } diff --git a/app/src/main/kotlin/app/view/MainView.kt b/app/src/main/kotlin/app/view/MainView.kt index 0bd58cb..980ce04 100644 --- a/app/src/main/kotlin/app/view/MainView.kt +++ b/app/src/main/kotlin/app/view/MainView.kt @@ -2,8 +2,8 @@ package app.view import app.view.treeView.BinarySearchTreeView import tornadofx.* -class MainView: View(){ - val tree: BinarySearchTreeView by inject() +class MainView : View() { + private val tree: BinarySearchTreeView by inject() override val root = vbox { add(tree) } diff --git a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt index 1a547fa..baa9e3d 100644 --- a/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/AVLTreeView.kt @@ -1,12 +1,12 @@ package app.view.treeView import app.controller.AVLController import bst.AVLTree -import bst.BSTree import javafx.beans.property.SimpleStringProperty import javafx.scene.control.Alert import javafx.scene.layout.Pane import tornadofx.* -class AVLTreeView: View() { + +class AVLTreeView : View() { private val controller: AVLController by inject() private var tree = AVLTree() private val treePane = Pane() @@ -15,7 +15,7 @@ class AVLTreeView: View() { private var trees = controller.getTreesList() private var selectedItem: String? = "" private val treeName = SimpleStringProperty() - private val valueFotDeletion = SimpleStringProperty() + private val valueForDeletion = SimpleStringProperty() init { // Add listeners to the width and height properties of the scene @@ -71,14 +71,14 @@ class AVLTreeView: View() { } form { fieldset { - field("Key input") { + field("Key") { textfield(key) } - field("Value input") { + field("Value") { textfield(value) } - button("Add Node") { + button("Add") { action { if (key.value != null && value.value != null && controller.isNumeric(key.value)) { controller.insertNode(tree, treePane, key.value.toInt(), value.value) @@ -89,15 +89,14 @@ class AVLTreeView: View() { value.value = "" } } - field("Key input"){ - textfield(valueFotDeletion) + field("Key") { + textfield(valueForDeletion) } - button("Delete node"){ + button("Delete") { action { - if (controller.isNumeric(valueFotDeletion.value)){ - controller.deleteNode(valueFotDeletion.value.toInt(), tree, treePane) - } - else{ + if (controller.isNumeric(valueForDeletion.value)) { + controller.deleteNode(valueForDeletion.value.toInt(), tree, treePane) + } else { alert(type = Alert.AlertType.ERROR, header = "Deletion Error") } } @@ -114,10 +113,8 @@ class AVLTreeView: View() { if (!availableTrees.items.contains(treeName.value)) { availableTrees.items.add(treeName.value) } - } - else{ + } else { alert(type = Alert.AlertType.ERROR, header = "Can not save tree with empty root") - } } } @@ -141,4 +138,4 @@ class AVLTreeView: View() { style = "-fx-border-color: black;" } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt index 943b23a..0d76929 100644 --- a/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/BinarySearchTreeView.kt @@ -1,12 +1,11 @@ package app.view.treeView -import tornadofx.* import app.controller.BSTController import bst.BSTree import javafx.beans.property.SimpleStringProperty -import javafx.scene.layout.Pane import javafx.scene.control.Alert - +import javafx.scene.layout.Pane +import tornadofx.* class BinarySearchTreeView : View() { private val controller: BSTController by inject() @@ -17,7 +16,7 @@ class BinarySearchTreeView : View() { private var trees = controller.getTreesList() private var selectedItem: String? = "" private val treeName = SimpleStringProperty() - private val valueFotDeletion = SimpleStringProperty() + private val valueForDeletion = SimpleStringProperty() init { // Add listeners to the width and height properties of the scene @@ -73,14 +72,14 @@ class BinarySearchTreeView : View() { } form { fieldset { - field("Key input") { + field("Key") { textfield(key) } - field("Value input") { + field("Value") { textfield(value) } - button("Add Node") { + button("Add") { action { if (key.value != null && value.value != null && controller.isNumeric(key.value)) { controller.insertNode(tree, treePane, key.value.toInt(), value.value) @@ -91,15 +90,14 @@ class BinarySearchTreeView : View() { value.value = "" } } - field("Key input"){ - textfield(valueFotDeletion) + field("Key") { + textfield(valueForDeletion) } - button("Delete node"){ + button("Delete") { action { - if (controller.isNumeric(valueFotDeletion.value)){ - controller.deleteNode(valueFotDeletion.value.toInt(), tree, treePane) - } - else{ + if (controller.isNumeric(valueForDeletion.value)) { + controller.deleteNode(valueForDeletion.value.toInt(), tree, treePane) + } else { alert(type = Alert.AlertType.ERROR, header = "Deletion Error") } } @@ -111,15 +109,13 @@ class BinarySearchTreeView : View() { button("Save tree") { action { if (tree.getRoot() != null) { -// tree.treeName = treeName.value + // tree.treeName = treeName.value controller.saveTree(tree, treeName.value) if (!availableTrees.items.contains(treeName.value)) { availableTrees.items.add(treeName.value) } - } - else{ + } else { alert(type = Alert.AlertType.ERROR, header = "Can not save tree with empty root") - } } } diff --git a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt index c1e425b..215ada4 100644 --- a/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt +++ b/app/src/main/kotlin/app/view/treeView/RedBlackTreeView.kt @@ -1,13 +1,11 @@ package app.view.treeView import app.controller.RBTController -import tornadofx.* import bst.RedBlackTree -import bst.db.controllers.Neo4jController import javafx.beans.property.SimpleStringProperty -import javafx.scene.layout.Pane import javafx.scene.control.Alert - +import javafx.scene.layout.Pane +import tornadofx.* class RedBlackTreeView : View() { private val controller: RBTController by inject() @@ -18,7 +16,7 @@ class RedBlackTreeView : View() { private var trees = controller.getTreesList() private var selectedItem: String? = "" private val treeName = SimpleStringProperty() - private val valueFotDeletion = SimpleStringProperty() + private val valueForDeletion = SimpleStringProperty() init { // Add listeners to the width and height properties of the scene @@ -74,14 +72,14 @@ class RedBlackTreeView : View() { } form { fieldset { - field("Key input") { + field("Key") { textfield(key) } - field("Value input") { + field("Value") { textfield(value) } - button("Add Node") { + button("Add") { action { if (key.value != null && value.value != null && controller.isNumeric(key.value)) { controller.insertNode(tree, treePane, key.value.toInt(), value.value) @@ -92,15 +90,14 @@ class RedBlackTreeView : View() { value.value = "" } } - field("Value input"){ - textfield(valueFotDeletion) + field("Value") { + textfield(valueForDeletion) } - button("Delete node"){ + button("Delete") { action { - if (controller.isNumeric(valueFotDeletion.value)){ - controller.deleteNode(valueFotDeletion.value.toInt(), tree, treePane) - } - else{ + if (controller.isNumeric(valueForDeletion.value)) { + controller.deleteNode(valueForDeletion.value.toInt(), tree, treePane) + } else { alert(type = Alert.AlertType.ERROR, header = "Deletion Error") } } @@ -117,8 +114,7 @@ class RedBlackTreeView : View() { if (!availableTrees.items.contains(treeName.value)) { availableTrees.items.add(treeName.value) } - } - else{ + } else { alert(type = Alert.AlertType.ERROR, header = "Can not save tree with empty root") } } From 61b23ddf228d3698504ef83cafb84f5e993fe7cc Mon Sep 17 00:00:00 2001 From: ancavar Date: Mon, 1 May 2023 22:46:49 +0300 Subject: [PATCH 118/118] fix: setup java 11 before building --- .github/workflows/test.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c24337..5edc278 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,9 +9,15 @@ jobs: strategy: matrix: - os: [ ubuntu-latest, windows-latest ] + os: [ ubuntu-latest, windows-latest, macos-latest ] steps: - uses: actions/checkout@v3 + + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + - name: Setup Gradle uses: gradle/gradle-build-action@v2