From ae772c6b0aad02370d86cbf0e72158a539c996df Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:02:49 +0000 Subject: [PATCH 001/164] Setting up GitHub Classroom Feedback From c4ca40f67cc89514f9350cafd2abcd254a72be40 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:12:24 +0300 Subject: [PATCH 002/164] feat(gitignore): was made --- .gitignore | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e7d0d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,205 @@ +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij,windows,linux,kotlin +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,intellij,windows,linux,kotlin + +### 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 + +### Kotlin ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij,windows,linux,kotlin +/.idea/ +/.gradle/ From 4fd72d3b6b83658c762a26008b5187977841e985 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:18:31 +0300 Subject: [PATCH 003/164] init(class): abstract BinTree feat: class Node feat: constructors feat: fun: getParent, getNode, getNode, sortInsert --- src/main/kotlin/abstractBinTree.kt | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/main/kotlin/abstractBinTree.kt diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt new file mode 100644 index 0000000..4139498 --- /dev/null +++ b/src/main/kotlin/abstractBinTree.kt @@ -0,0 +1,78 @@ +import kotlin.math.abs +abstract class BinTree, Value> { + protected open class Node, Value>( + val key: Key, + var value: Value, + var parent: Node? = null, + var left: Node? = null, + var right: Node? = null + + ) : Comparable { + override fun compareTo(other: Key): Int { + return key.compareTo(other) + } + + fun equalKey(other: Key): Boolean { + return this.compareTo(other) == 0 + } + } + protected open var rootNode: Node? = null + + constructor() + constructor(key: Key, value: Value) { + insert(key, value) + } + private fun sortInsert(array: Array>) { + val serArray = array.sortedBy { it.first }.toTypedArray() + var indices = serArray.indices.toList() + indices = indices.sortedBy{ abs(serArray.size/2 - it) } + for (i in indices) { + insert(serArray[i].first, serArray[i].second) + } + } + constructor(array: Array>) { + sortInsert(array) + } + + abstract fun insert(key: Key, value: Value) + abstract fun remove(key: Key) + + + + protected fun getParent(key: Key) : Node? { + tailrec fun recFind(curNode: Node?): Node? { + return if (curNode == null) + null + else if (curNode > key) { + if (curNode.left?.equalKey(key) != false) + curNode + else + recFind(curNode.left) + } else { + if (curNode.right?.equalKey(key) != false) + curNode + else + recFind(curNode.right) + } + } + + return recFind(rootNode) + } + + protected fun getNode(key: Key): Node? { + if (rootNode?.equalKey(key) == true) + return rootNode + val parent = getParent(key) + return if (parent == null) + null + else if (parent.left?.equalKey(key) == true) + parent.left + else + parent.right + } + + open fun get(key: Key): Value? { + return getNode(key)?.value + } +} + From 3468a0948458b6a4f942da5a0c4ec048cd1057f7 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:27:59 +0300 Subject: [PATCH 004/164] init(class): abstract BalanceTree --- src/main/kotlin/abstractBalanceTree.kt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/kotlin/abstractBalanceTree.kt diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt new file mode 100644 index 0000000..0ac1447 --- /dev/null +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -0,0 +1,5 @@ +abstract class BalanceTree, Value>: BinTree() { + protected abstract fun rebalancing() + protected abstract fun leftRotation() + protected abstract fun rightRotation() +} \ No newline at end of file From 4ea364985570bb3b5cd4f2f64e8af0215830b089 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:29:27 +0300 Subject: [PATCH 005/164] init(class): BSTree feat: constructors feat: fun insert init: fun remove --- src/main/kotlin/BSTree.kt | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/kotlin/BSTree.kt diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt new file mode 100644 index 0000000..f5c1b8f --- /dev/null +++ b/src/main/kotlin/BSTree.kt @@ -0,0 +1,53 @@ +open class BSTree, Value>: BinTree { + constructor(): super() + constructor(key: Key, value: Value): super(key, value) + constructor(vararg pairs: Pair): super(pairs) + open override fun insert(key: Key, value: Value) { + if (rootNode == null) + rootNode = Node(key, value) + else { + val parent = getParent(key) + if (parent != null) { + if (parent < key) + if (parent.right == null) + parent.right = Node(key, value) + else parent.right?.value = value ?: error("unexpected null") + else + if (parent.left == null) + parent.left = Node(key, value) + else (parent.left)?.value = value ?: error("unexpected null") + } + } + } + + open override fun remove(key: Key) { + removeNode(getNode(key)) + } + protected open fun removeNode(node: Node?) { + if ((node!!.left == null) && (node.right == null)) { + if (node.parent == null) + rootNode == null + else if (node == (node.parent)!!.left) + (node.parent)!!.left == null + else + (node.parent)!!.right == null + } + else if (node.left == null) + transplant(node, (node.right)!!) + else if (node.right == null) + transplant(node, (node.left)!!) + else{ + TODO("Юля когда-нибудь доделает") + } + } + protected open fun transplant(node1: Node, node2: Node) { + if (node1.parent == null) + rootNode = node2 + else if (node1 == (node1.parent)!!.left) { + (node1.parent)!!.left = node2 + } else { + (node1.parent)!!.right = node2 + } + node2.parent = node1.parent + } +} From f2332abf4e995a3ebed454bbdc0c990b9cbce9ac Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:31:12 +0300 Subject: [PATCH 006/164] init(class): AVLTree feat: class AVLNode init: fun insert --- src/main/kotlin/AVLTree.kt | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/kotlin/AVLTree.kt diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt new file mode 100644 index 0000000..dd071ae --- /dev/null +++ b/src/main/kotlin/AVLTree.kt @@ -0,0 +1,41 @@ +class AVLTree, Value> : BalanceTree() { + protected class AVLNode, Value>( + key: Key, + value: Value, + parent: AVLNode? = null, + left: AVLNode? = null, + right: AVLNode? = null, + var height: UByte = 0U + ) : Node(key, value, parent, left, right) + + override fun insert(key: Key, value: Value) { + if (rootNode != null) { + if ((rootNode as AVLNode).height != 255.toUByte()) { + val parent = getParent(key) + if (parent != null) { + if (parent < key) + parent.right = AVLNode(key, value) + else + parent.left = AVLNode(key, value) + } + } + } else + rootNode = AVLNode(key, value) + } + + override fun remove(key: Key) { + TODO("jfaj;l") + } + + override fun leftRotation() { + TODO("Not yet implemented") + } + + override fun rightRotation() { + TODO("Not yet implemented") + } + + override fun rebalancing() { + TODO("Not yet implemented") + } +} \ No newline at end of file From 22fdf94d5f80c996f72ed292e45c157c20e049c3 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:31:49 +0300 Subject: [PATCH 007/164] init(class): RBTree feat: class RBNode --- src/main/kotlin/RBTree.kt | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/kotlin/RBTree.kt diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt new file mode 100644 index 0000000..6d16803 --- /dev/null +++ b/src/main/kotlin/RBTree.kt @@ -0,0 +1,35 @@ +class RBTree, Value> : BalanceTree() { + protected class RBNode, Value>( + key: Key, + value: Value, + parent: RBNode?= null, + left: RBNode? = null, + right: RBNode? = null, + var Black: Boolean = false + ) : Node(key, value, parent, left, right) { + fun swapColor() { + Black = !Black + } + } + + override fun insert(key: Key, value: Value) { + if (rootNode == null) + rootNode = RBNode(key, value) + } + + override fun rebalancing() { + TODO("Not yet implemented") + } + + override fun leftRotation() { + TODO("Not yet implemented") + } + + override fun rightRotation() { + TODO("Not yet implemented") + } + + override fun remove(key: Key) { + TODO("Not yet implemented") + } +} \ No newline at end of file From 9a45fe5545b7e2eb2383b5f30ff5a773970a1d14 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:32:42 +0300 Subject: [PATCH 008/164] init(test): BSTreeTest feat: addFirstNodeCorrectTest test --- src/test/kotlin/BSTreeTest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/test/kotlin/BSTreeTest.kt diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt new file mode 100644 index 0000000..ffdcc27 --- /dev/null +++ b/src/test/kotlin/BSTreeTest.kt @@ -0,0 +1,13 @@ +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class BSTreeTest { + @Test + fun addFirstNodeCorrectTest() { + val tree = BSTree() + val expected = "4k" + tree.insert(4, "4k") + Assertions.assertEquals(expected, tree.get(4)) + } +} \ No newline at end of file From 706d772934e702e84d87352ab8451d03b3c38d4e Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:34:53 +0300 Subject: [PATCH 009/164] feat: gradle and CI --- .github/workflows/actions.yml | 22 +++ build.gradle.kts | 27 +++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 240 +++++++++++++++++++++++ gradlew.bat | 91 +++++++++ settings.gradle.kts | 2 + 7 files changed, 388 insertions(+) create mode 100644 .github/workflows/actions.yml create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..ade9117 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,22 @@ +name: test + +on: + push: + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 18 + uses: actions/setup-java@v1 + with: + java-version: 18 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..766f636 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("jvm") version "1.8.0" + application +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(8) +} + +application { + mainClass.set("MainKt") +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ae04661 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..a69d9cb --- /dev/null +++ b/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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..f127cfd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% 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..c18280d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "trees" + From 790baf890336545dcf46a6818d597298f0b44feb Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 20:37:08 +0300 Subject: [PATCH 010/164] fix: added a forgotten file for gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 zcmb5WV{~QRw(p$^Dz@00IL3?^hro$gg*4VI_WAaTyVM5Foj~O|-84 z$;06hMwt*rV;^8iB z1~&0XWpYJmG?Ts^K9PC62H*`G}xom%S%yq|xvG~FIfP=9*f zZoDRJBm*Y0aId=qJ?7dyb)6)JGWGwe)MHeNSzhi)Ko6J<-m@v=a%NsP537lHe0R* z`If4$aaBA#S=w!2z&m>{lpTy^Lm^mg*3?M&7HFv}7K6x*cukLIGX;bQG|QWdn{%_6 zHnwBKr84#B7Z+AnBXa16a?or^R?+>$4`}{*a_>IhbjvyTtWkHw)|ay)ahWUd-qq$~ zMbh6roVsj;_qnC-R{G+Cy6bApVOinSU-;(DxUEl!i2)1EeQ9`hrfqj(nKI7?Z>Xur zoJz-a`PxkYit1HEbv|jy%~DO^13J-ut986EEG=66S}D3!L}Efp;Bez~7tNq{QsUMm zh9~(HYg1pA*=37C0}n4g&bFbQ+?-h-W}onYeE{q;cIy%eZK9wZjSwGvT+&Cgv z?~{9p(;bY_1+k|wkt_|N!@J~aoY@|U_RGoWX<;p{Nu*D*&_phw`8jYkMNpRTWx1H* z>J-Mi_!`M468#5Aix$$u1M@rJEIOc?k^QBc?T(#=n&*5eS#u*Y)?L8Ha$9wRWdH^3D4|Ps)Y?m0q~SiKiSfEkJ!=^`lJ(%W3o|CZ zSrZL-Xxc{OrmsQD&s~zPfNJOpSZUl%V8tdG%ei}lQkM+z@-4etFPR>GOH9+Y_F<3=~SXln9Kb-o~f>2a6Xz@AS3cn^;c_>lUwlK(n>z?A>NbC z`Ud8^aQy>wy=$)w;JZzA)_*Y$Z5hU=KAG&htLw1Uh00yE!|Nu{EZkch zY9O6x7Y??>!7pUNME*d!=R#s)ghr|R#41l!c?~=3CS8&zr6*aA7n9*)*PWBV2w+&I zpW1-9fr3j{VTcls1>ua}F*bbju_Xq%^v;-W~paSqlf zolj*dt`BBjHI)H9{zrkBo=B%>8}4jeBO~kWqO!~Thi!I1H(in=n^fS%nuL=X2+s!p}HfTU#NBGiwEBF^^tKU zbhhv+0dE-sbK$>J#t-J!B$TMgN@Wh5wTtK2BG}4BGfsZOoRUS#G8Cxv|6EI*n&Xxq zt{&OxCC+BNqz$9b0WM7_PyBJEVObHFh%%`~!@MNZlo*oXDCwDcFwT~Rls!aApL<)^ zbBftGKKBRhB!{?fX@l2_y~%ygNFfF(XJzHh#?`WlSL{1lKT*gJM zs>bd^H9NCxqxn(IOky5k-wALFowQr(gw%|`0991u#9jXQh?4l|l>pd6a&rx|v=fPJ z1mutj{YzpJ_gsClbWFk(G}bSlFi-6@mwoQh-XeD*j@~huW4(8ub%^I|azA)h2t#yG z7e_V_<4jlM3D(I+qX}yEtqj)cpzN*oCdYHa!nm%0t^wHm)EmFP*|FMw!tb@&`G-u~ zK)=Sf6z+BiTAI}}i{*_Ac$ffr*Wrv$F7_0gJkjx;@)XjYSh`RjAgrCck`x!zP>Ifu z&%he4P|S)H*(9oB4uvH67^0}I-_ye_!w)u3v2+EY>eD3#8QR24<;7?*hj8k~rS)~7 zSXs5ww)T(0eHSp$hEIBnW|Iun<_i`}VE0Nc$|-R}wlSIs5pV{g_Dar(Zz<4X3`W?K z6&CAIl4U(Qk-tTcK{|zYF6QG5ArrEB!;5s?tW7 zrE3hcFY&k)+)e{+YOJ0X2uDE_hd2{|m_dC}kgEKqiE9Q^A-+>2UonB+L@v3$9?AYw zVQv?X*pK;X4Ovc6Ev5Gbg{{Eu*7{N3#0@9oMI~}KnObQE#Y{&3mM4`w%wN+xrKYgD zB-ay0Q}m{QI;iY`s1Z^NqIkjrTlf`B)B#MajZ#9u41oRBC1oM1vq0i|F59> z#StM@bHt|#`2)cpl_rWB($DNJ3Lap}QM-+A$3pe}NyP(@+i1>o^fe-oxX#Bt`mcQc zb?pD4W%#ep|3%CHAYnr*^M6Czg>~L4?l16H1OozM{P*en298b+`i4$|w$|4AHbzqB zHpYUsHZET$Z0ztC;U+0*+amF!@PI%^oUIZy{`L{%O^i{Xk}X0&nl)n~tVEpcAJSJ} zverw15zP1P-O8h9nd!&hj$zuwjg?DoxYIw{jWM zW5_pj+wFy8Tsa9g<7Qa21WaV&;ejoYflRKcz?#fSH_)@*QVlN2l4(QNk| z4aPnv&mrS&0|6NHq05XQw$J^RR9T{3SOcMKCXIR1iSf+xJ0E_Wv?jEc*I#ZPzyJN2 zUG0UOXHl+PikM*&g$U@g+KbG-RY>uaIl&DEtw_Q=FYq?etc!;hEC_}UX{eyh%dw2V zTTSlap&5>PY{6I#(6`j-9`D&I#|YPP8a;(sOzgeKDWsLa!i-$frD>zr-oid!Hf&yS z!i^cr&7tN}OOGmX2)`8k?Tn!!4=tz~3hCTq_9CdiV!NIblUDxHh(FJ$zs)B2(t5@u z-`^RA1ShrLCkg0)OhfoM;4Z{&oZmAec$qV@ zGQ(7(!CBk<5;Ar%DLJ0p0!ResC#U<+3i<|vib1?{5gCebG7$F7URKZXuX-2WgF>YJ^i zMhHDBsh9PDU8dlZ$yJKtc6JA#y!y$57%sE>4Nt+wF1lfNIWyA`=hF=9Gj%sRwi@vd z%2eVV3y&dvAgyuJ=eNJR+*080dbO_t@BFJO<@&#yqTK&+xc|FRR;p;KVk@J3$S{p` zGaMj6isho#%m)?pOG^G0mzOAw0z?!AEMsv=0T>WWcE>??WS=fII$t$(^PDPMU(P>o z_*0s^W#|x)%tx8jIgZY~A2yG;US0m2ZOQt6yJqW@XNY_>_R7(Nxb8Ged6BdYW6{prd!|zuX$@Q2o6Ona8zzYC1u!+2!Y$Jc9a;wy+pXt}o6~Bu1oF1c zp7Y|SBTNi@=I(K%A60PMjM#sfH$y*c{xUgeSpi#HB`?|`!Tb&-qJ3;vxS!TIzuTZs-&%#bAkAyw9m4PJgvey zM5?up*b}eDEY+#@tKec)-c(#QF0P?MRlD1+7%Yk*jW;)`f;0a-ZJ6CQA?E%>i2Dt7T9?s|9ZF|KP4;CNWvaVKZ+Qeut;Jith_y{v*Ny6Co6!8MZx;Wgo z=qAi%&S;8J{iyD&>3CLCQdTX*$+Rx1AwA*D_J^0>suTgBMBb=*hefV+Ars#mmr+YsI3#!F@Xc1t4F-gB@6aoyT+5O(qMz*zG<9Qq*f0w^V!03rpr*-WLH}; zfM{xSPJeu6D(%8HU%0GEa%waFHE$G?FH^kMS-&I3)ycx|iv{T6Wx}9$$D&6{%1N_8 z_CLw)_9+O4&u94##vI9b-HHm_95m)fa??q07`DniVjAy`t7;)4NpeyAY(aAk(+T_O z1om+b5K2g_B&b2DCTK<>SE$Ode1DopAi)xaJjU>**AJK3hZrnhEQ9E`2=|HHe<^tv z63e(bn#fMWuz>4erc47}!J>U58%<&N<6AOAewyzNTqi7hJc|X{782&cM zHZYclNbBwU6673=!ClmxMfkC$(CykGR@10F!zN1Se83LR&a~$Ht&>~43OX22mt7tcZUpa;9@q}KDX3O&Ugp6< zLZLfIMO5;pTee1vNyVC$FGxzK2f>0Z-6hM82zKg44nWo|n}$Zk6&;5ry3`(JFEX$q zK&KivAe${e^5ZGc3a9hOt|!UOE&OocpVryE$Y4sPcs4rJ>>Kbi2_subQ9($2VN(3o zb~tEzMsHaBmBtaHAyES+d3A(qURgiskSSwUc9CfJ@99&MKp2sooSYZu+-0t0+L*!I zYagjOlPgx|lep9tiU%ts&McF6b0VE57%E0Ho%2oi?=Ks+5%aj#au^OBwNwhec zta6QAeQI^V!dF1C)>RHAmB`HnxyqWx?td@4sd15zPd*Fc9hpDXP23kbBenBxGeD$k z;%0VBQEJ-C)&dTAw_yW@k0u?IUk*NrkJ)(XEeI z9Y>6Vel>#s_v@=@0<{4A{pl=9cQ&Iah0iD0H`q)7NeCIRz8zx;! z^OO;1+IqoQNak&pV`qKW+K0^Hqp!~gSohcyS)?^P`JNZXw@gc6{A3OLZ?@1Uc^I2v z+X!^R*HCm3{7JPq{8*Tn>5;B|X7n4QQ0Bs79uTU%nbqOJh`nX(BVj!#f;#J+WZxx4 z_yM&1Y`2XzhfqkIMO7tB3raJKQS+H5F%o83bM+hxbQ zeeJm=Dvix$2j|b4?mDacb67v-1^lTp${z=jc1=j~QD>7c*@+1?py>%Kj%Ejp7Y-!? z8iYRUlGVrQPandAaxFfks53@2EC#0)%mrnmGRn&>=$H$S8q|kE_iWko4`^vCS2aWg z#!`RHUGyOt*k?bBYu3*j3u0gB#v(3tsije zgIuNNWNtrOkx@Pzs;A9un+2LX!zw+p3_NX^Sh09HZAf>m8l@O*rXy_82aWT$Q>iyy zqO7Of)D=wcSn!0+467&!Hl))eff=$aneB?R!YykdKW@k^_uR!+Q1tR)+IJb`-6=jj zymzA>Sv4>Z&g&WWu#|~GcP7qP&m*w-S$)7Xr;(duqCTe7p8H3k5>Y-n8438+%^9~K z3r^LIT_K{i7DgEJjIocw_6d0!<;wKT`X;&vv+&msmhAAnIe!OTdybPctzcEzBy88_ zWO{6i4YT%e4^WQZB)KHCvA(0tS zHu_Bg+6Ko%a9~$EjRB90`P(2~6uI@SFibxct{H#o&y40MdiXblu@VFXbhz>Nko;7R z70Ntmm-FePqhb%9gL+7U8@(ch|JfH5Fm)5${8|`Lef>LttM_iww6LW2X61ldBmG0z zax3y)njFe>j*T{i0s8D4=L>X^j0)({R5lMGVS#7(2C9@AxL&C-lZQx~czI7Iv+{%1 z2hEG>RzX4S8x3v#9sgGAnPzptM)g&LB}@%E>fy0vGSa(&q0ch|=ncKjNrK z`jA~jObJhrJ^ri|-)J^HUyeZXz~XkBp$VhcTEcTdc#a2EUOGVX?@mYx#Vy*!qO$Jv zQ4rgOJ~M*o-_Wptam=~krnmG*p^j!JAqoQ%+YsDFW7Cc9M%YPiBOrVcD^RY>m9Pd< zu}#9M?K{+;UIO!D9qOpq9yxUquQRmQNMo0pT`@$pVt=rMvyX)ph(-CCJLvUJy71DI zBk7oc7)-%ngdj~s@76Yse3L^gV0 z2==qfp&Q~L(+%RHP0n}+xH#k(hPRx(!AdBM$JCfJ5*C=K3ts>P?@@SZ_+{U2qFZb>4kZ{Go37{# zSQc+-dq*a-Vy4?taS&{Ht|MLRiS)Sn14JOONyXqPNnpq&2y~)6wEG0oNy>qvod$FF z`9o&?&6uZjhZ4_*5qWVrEfu(>_n2Xi2{@Gz9MZ8!YmjYvIMasE9yVQL10NBrTCczq zcTY1q^PF2l!Eraguf{+PtHV3=2A?Cu&NN&a8V(y;q(^_mFc6)%Yfn&X&~Pq zU1?qCj^LF(EQB1F`8NxNjyV%fde}dEa(Hx=r7$~ts2dzDwyi6ByBAIx$NllB4%K=O z$AHz1<2bTUb>(MCVPpK(E9wlLElo(aSd(Os)^Raum`d(g9Vd_+Bf&V;l=@mM=cC>) z)9b0enb)u_7V!!E_bl>u5nf&Rl|2r=2F3rHMdb7y9E}}F82^$Rf+P8%dKnOeKh1vs zhH^P*4Ydr^$)$h@4KVzxrHyy#cKmWEa9P5DJ|- zG;!Qi35Tp7XNj60=$!S6U#!(${6hyh7d4q=pF{`0t|N^|L^d8pD{O9@tF~W;#Je*P z&ah%W!KOIN;SyAEhAeTafJ4uEL`(RtnovM+cb(O#>xQnk?dzAjG^~4$dFn^<@-Na3 z395;wBnS{t*H;Jef2eE!2}u5Ns{AHj>WYZDgQJt8v%x?9{MXqJsGP|l%OiZqQ1aB! z%E=*Ig`(!tHh>}4_z5IMpg{49UvD*Pp9!pxt_gdAW%sIf3k6CTycOT1McPl=_#0?8 zVjz8Hj*Vy9c5-krd-{BQ{6Xy|P$6LJvMuX$* zA+@I_66_ET5l2&gk9n4$1M3LN8(yEViRx&mtd#LD}AqEs?RW=xKC(OCWH;~>(X6h!uDxXIPH06xh z*`F4cVlbDP`A)-fzf>MuScYsmq&1LUMGaQ3bRm6i7OsJ|%uhTDT zlvZA1M}nz*SalJWNT|`dBm1$xlaA>CCiQ zK`xD-RuEn>-`Z?M{1%@wewf#8?F|(@1e0+T4>nmlSRrNK5f)BJ2H*$q(H>zGD0>eL zQ!tl_Wk)k*e6v^m*{~A;@6+JGeWU-q9>?+L_#UNT%G?4&BnOgvm9@o7l?ov~XL+et zbGT)|G7)KAeqb=wHSPk+J1bdg7N3$vp(ekjI1D9V$G5Cj!=R2w=3*4!z*J-r-cyeb zd(i2KmX!|Lhey!snRw z?#$Gu%S^SQEKt&kep)up#j&9}e+3=JJBS(s>MH+|=R(`8xK{mmndWo_r`-w1#SeRD&YtAJ#GiVI*TkQZ}&aq<+bU2+coU3!jCI6E+Ad_xFW*ghnZ$q zAoF*i&3n1j#?B8x;kjSJD${1jdRB;)R*)Ao!9bd|C7{;iqDo|T&>KSh6*hCD!rwv= zyK#F@2+cv3=|S1Kef(E6Niv8kyLVLX&e=U;{0x{$tDfShqkjUME>f8d(5nzSkY6@! z^-0>DM)wa&%m#UF1F?zR`8Y3X#tA!*7Q$P3lZJ%*KNlrk_uaPkxw~ zxZ1qlE;Zo;nb@!SMazSjM>;34ROOoygo%SF);LL>rRonWwR>bmSd1XD^~sGSu$Gg# zFZ`|yKU0%!v07dz^v(tY%;So(e`o{ZYTX`hm;@b0%8|H>VW`*cr8R%3n|ehw2`(9B+V72`>SY}9^8oh$En80mZK9T4abVG*to;E z1_S6bgDOW?!Oy1LwYy=w3q~KKdbNtyH#d24PFjX)KYMY93{3-mPP-H>@M-_>N~DDu zENh~reh?JBAK=TFN-SfDfT^=+{w4ea2KNWXq2Y<;?(gf(FgVp8Zp-oEjKzB%2Iqj;48GmY3h=bcdYJ}~&4tS`Q1sb=^emaW$IC$|R+r-8V- zf0$gGE(CS_n4s>oicVk)MfvVg#I>iDvf~Ov8bk}sSxluG!6#^Z_zhB&U^`eIi1@j( z^CK$z^stBHtaDDHxn+R;3u+>Lil^}fj?7eaGB z&5nl^STqcaBxI@v>%zG|j))G(rVa4aY=B@^2{TFkW~YP!8!9TG#(-nOf^^X-%m9{Z zCC?iC`G-^RcBSCuk=Z`(FaUUe?hf3{0C>>$?Vs z`2Uud9M+T&KB6o4o9kvdi^Q=Bw!asPdxbe#W-Oaa#_NP(qpyF@bVxv5D5))srkU#m zj_KA+#7sqDn*Ipf!F5Byco4HOSd!Ui$l94|IbW%Ny(s1>f4|Mv^#NfB31N~kya9!k zWCGL-$0ZQztBate^fd>R!hXY_N9ZjYp3V~4_V z#eB)Kjr8yW=+oG)BuNdZG?jaZlw+l_ma8aET(s+-x+=F-t#Qoiuu1i`^x8Sj>b^U} zs^z<()YMFP7CmjUC@M=&lA5W7t&cxTlzJAts*%PBDAPuqcV5o7HEnqjif_7xGt)F% zGx2b4w{@!tE)$p=l3&?Bf#`+!-RLOleeRk3 z7#pF|w@6_sBmn1nECqdunmG^}pr5(ZJQVvAt$6p3H(16~;vO>?sTE`Y+mq5YP&PBo zvq!7#W$Gewy`;%6o^!Dtjz~x)T}Bdk*BS#=EY=ODD&B=V6TD2z^hj1m5^d6s)D*wk zu$z~D7QuZ2b?5`p)E8e2_L38v3WE{V`bVk;6fl#o2`) z99JsWhh?$oVRn@$S#)uK&8DL8>An0&S<%V8hnGD7Z^;Y(%6;^9!7kDQ5bjR_V+~wp zfx4m3z6CWmmZ<8gDGUyg3>t8wgJ5NkkiEm^(sedCicP^&3D%}6LtIUq>mXCAt{9eF zNXL$kGcoUTf_Lhm`t;hD-SE)m=iBnxRU(NyL}f6~1uH)`K!hmYZjLI%H}AmEF5RZt z06$wn63GHnApHXZZJ}s^s)j9(BM6e*7IBK6Bq(!)d~zR#rbxK9NVIlgquoMq z=eGZ9NR!SEqP6=9UQg#@!rtbbSBUM#ynF);zKX+|!Zm}*{H z+j=d?aZ2!?@EL7C~%B?6ouCKLnO$uWn;Y6Xz zX8dSwj732u(o*U3F$F=7xwxm>E-B+SVZH;O-4XPuPkLSt_?S0)lb7EEg)Mglk0#eS z9@jl(OnH4juMxY+*r03VDfPx_IM!Lmc(5hOI;`?d37f>jPP$?9jQQIQU@i4vuG6MagEoJrQ=RD7xt@8E;c zeGV*+Pt+t$@pt!|McETOE$9k=_C!70uhwRS9X#b%ZK z%q(TIUXSS^F0`4Cx?Rk07C6wI4!UVPeI~-fxY6`YH$kABdOuiRtl73MqG|~AzZ@iL&^s?24iS;RK_pdlWkhcF z@Wv-Om(Aealfg)D^adlXh9Nvf~Uf@y;g3Y)i(YP zEXDnb1V}1pJT5ZWyw=1i+0fni9yINurD=EqH^ciOwLUGi)C%Da)tyt=zq2P7pV5-G zR7!oq28-Fgn5pW|nlu^b!S1Z#r7!Wtr{5J5PQ>pd+2P7RSD?>(U7-|Y z7ZQ5lhYIl_IF<9?T9^IPK<(Hp;l5bl5tF9>X-zG14_7PfsA>6<$~A338iYRT{a@r_ zuXBaT=`T5x3=s&3=RYx6NgG>No4?5KFBVjE(swfcivcIpPQFx5l+O;fiGsOrl5teR z_Cm+;PW}O0Dwe_(4Z@XZ)O0W-v2X><&L*<~*q3dg;bQW3g7)a#3KiQP>+qj|qo*Hk z?57>f2?f@`=Fj^nkDKeRkN2d$Z@2eNKpHo}ksj-$`QKb6n?*$^*%Fb3_Kbf1(*W9K>{L$mud2WHJ=j0^=g30Xhg8$#g^?36`p1fm;;1@0Lrx+8t`?vN0ZorM zSW?rhjCE8$C|@p^sXdx z|NOHHg+fL;HIlqyLp~SSdIF`TnSHehNCU9t89yr@)FY<~hu+X`tjg(aSVae$wDG*C zq$nY(Y494R)hD!i1|IIyP*&PD_c2FPgeY)&mX1qujB1VHPG9`yFQpLFVQ0>EKS@Bp zAfP5`C(sWGLI?AC{XEjLKR4FVNw(4+9b?kba95ukgR1H?w<8F7)G+6&(zUhIE5Ef% z=fFkL3QKA~M@h{nzjRq!Y_t!%U66#L8!(2-GgFxkD1=JRRqk=n%G(yHKn%^&$dW>; zSjAcjETMz1%205se$iH_)ZCpfg_LwvnsZQAUCS#^FExp8O4CrJb6>JquNV@qPq~3A zZ<6dOU#6|8+fcgiA#~MDmcpIEaUO02L5#T$HV0$EMD94HT_eXLZ2Zi&(! z&5E>%&|FZ`)CN10tM%tLSPD*~r#--K(H-CZqIOb99_;m|D5wdgJ<1iOJz@h2Zkq?} z%8_KXb&hf=2Wza(Wgc;3v3TN*;HTU*q2?#z&tLn_U0Nt!y>Oo>+2T)He6%XuP;fgn z-G!#h$Y2`9>Jtf}hbVrm6D70|ERzLAU>3zoWhJmjWfgM^))T+2u$~5>HF9jQDkrXR z=IzX36)V75PrFjkQ%TO+iqKGCQ-DDXbaE;C#}!-CoWQx&v*vHfyI>$HNRbpvm<`O( zlx9NBWD6_e&J%Ous4yp~s6)Ghni!I6)0W;9(9$y1wWu`$gs<$9Mcf$L*piP zPR0Av*2%ul`W;?-1_-5Zy0~}?`e@Y5A&0H!^ApyVTT}BiOm4GeFo$_oPlDEyeGBbh z1h3q&Dx~GmUS|3@4V36&$2uO8!Yp&^pD7J5&TN{?xphf*-js1fP?B|`>p_K>lh{ij zP(?H%e}AIP?_i^f&Li=FDSQ`2_NWxL+BB=nQr=$ zHojMlXNGauvvwPU>ZLq!`bX-5F4jBJ&So{kE5+ms9UEYD{66!|k~3vsP+mE}x!>%P za98bAU0!h0&ka4EoiDvBM#CP#dRNdXJcb*(%=<(g+M@<)DZ!@v1V>;54En?igcHR2 zhubQMq}VSOK)onqHfczM7YA@s=9*ow;k;8)&?J3@0JiGcP! zP#00KZ1t)GyZeRJ=f0^gc+58lc4Qh*S7RqPIC6GugG1gXe$LIQMRCo8cHf^qXgAa2 z`}t>u2Cq1CbSEpLr~E=c7~=Qkc9-vLE%(v9N*&HF`(d~(0`iukl5aQ9u4rUvc8%m) zr2GwZN4!s;{SB87lJB;veebPmqE}tSpT>+`t?<457Q9iV$th%i__Z1kOMAswFldD6 ztbOvO337S5o#ZZgN2G99_AVqPv!?Gmt3pzgD+Hp3QPQ`9qJ(g=kjvD+fUSS3upJn! zqoG7acIKEFRX~S}3|{EWT$kdz#zrDlJU(rPkxjws_iyLKU8+v|*oS_W*-guAb&Pj1 z35Z`3z<&Jb@2Mwz=KXucNYdY#SNO$tcVFr9KdKm|%^e-TXzs6M`PBper%ajkrIyUe zp$vVxVs9*>Vp4_1NC~Zg)WOCPmOxI1V34QlG4!aSFOH{QqSVq1^1)- z0P!Z?tT&E-ll(pwf0?=F=yOzik=@nh1Clxr9}Vij89z)ePDSCYAqw?lVI?v?+&*zH z)p$CScFI8rrwId~`}9YWPFu0cW1Sf@vRELs&cbntRU6QfPK-SO*mqu|u~}8AJ!Q$z znzu}50O=YbjwKCuSVBs6&CZR#0FTu)3{}qJJYX(>QPr4$RqWiwX3NT~;>cLn*_&1H zaKpIW)JVJ>b{uo2oq>oQt3y=zJjb%fU@wLqM{SyaC6x2snMx-}ivfU<1- znu1Lh;i$3Tf$Kh5Uk))G!D1UhE8pvx&nO~w^fG)BC&L!_hQk%^p`Kp@F{cz>80W&T ziOK=Sq3fdRu*V0=S53rcIfWFazI}Twj63CG(jOB;$*b`*#B9uEnBM`hDk*EwSRdwP8?5T?xGUKs=5N83XsR*)a4|ijz|c{4tIU+4j^A5C<#5 z*$c_d=5ml~%pGxw#?*q9N7aRwPux5EyqHVkdJO=5J>84!X6P>DS8PTTz>7C#FO?k#edkntG+fJk8ZMn?pmJSO@`x-QHq;7^h6GEXLXo1TCNhH z8ZDH{*NLAjo3WM`xeb=X{((uv3H(8&r8fJJg_uSs_%hOH%JDD?hu*2NvWGYD+j)&` zz#_1%O1wF^o5ryt?O0n;`lHbzp0wQ?rcbW(F1+h7_EZZ9{>rePvLAPVZ_R|n@;b$;UchU=0j<6k8G9QuQf@76oiE*4 zXOLQ&n3$NR#p4<5NJMVC*S);5x2)eRbaAM%VxWu9ohlT;pGEk7;002enCbQ>2r-us z3#bpXP9g|mE`65VrN`+3mC)M(eMj~~eOf)do<@l+fMiTR)XO}422*1SL{wyY(%oMpBgJagtiDf zz>O6(m;};>Hi=t8o{DVC@YigqS(Qh+ix3Rwa9aliH}a}IlOCW1@?%h_bRbq-W{KHF z%Vo?-j@{Xi@=~Lz5uZP27==UGE15|g^0gzD|3x)SCEXrx`*MP^FDLl%pOi~~Il;dc z^hrwp9sYeT7iZ)-ajKy@{a`kr0-5*_!XfBpXwEcFGJ;%kV$0Nx;apKrur zJN2J~CAv{Zjj%FolyurtW8RaFmpn&zKJWL>(0;;+q(%(Hx!GMW4AcfP0YJ*Vz!F4g z!ZhMyj$BdXL@MlF%KeInmPCt~9&A!;cRw)W!Hi@0DY(GD_f?jeV{=s=cJ6e}JktJw zQORnxxj3mBxfrH=x{`_^Z1ddDh}L#V7i}$njUFRVwOX?qOTKjfPMBO4y(WiU<)epb zvB9L=%jW#*SL|Nd_G?E*_h1^M-$PG6Pc_&QqF0O-FIOpa4)PAEPsyvB)GKasmBoEt z?_Q2~QCYGH+hW31x-B=@5_AN870vY#KB~3a*&{I=f);3Kv7q4Q7s)0)gVYx2#Iz9g(F2;=+Iy4 z6KI^8GJ6D@%tpS^8boU}zpi=+(5GfIR)35PzrbuXeL1Y1N%JK7PG|^2k3qIqHfX;G zQ}~JZ-UWx|60P5?d1e;AHx!_;#PG%d=^X(AR%i`l0jSpYOpXoKFW~7ip7|xvN;2^? zsYC9fanpO7rO=V7+KXqVc;Q5z%Bj})xHVrgoR04sA2 zl~DAwv=!(()DvH*=lyhIlU^hBkA0$e*7&fJpB0|oB7)rqGK#5##2T`@_I^|O2x4GO z;xh6ROcV<9>?e0)MI(y++$-ksV;G;Xe`lh76T#Htuia+(UrIXrf9?

L(tZ$0BqX1>24?V$S+&kLZ`AodQ4_)P#Q3*4xg8}lMV-FLwC*cN$< zt65Rf%7z41u^i=P*qO8>JqXPrinQFapR7qHAtp~&RZ85$>ob|Js;GS^y;S{XnGiBc zGa4IGvDl?x%gY`vNhv8wgZnP#UYI-w*^4YCZnxkF85@ldepk$&$#3EAhrJY0U)lR{F6sM3SONV^+$;Zx8BD&Eku3K zKNLZyBni3)pGzU0;n(X@1fX8wYGKYMpLmCu{N5-}epPDxClPFK#A@02WM3!myN%bkF z|GJ4GZ}3sL{3{qXemy+#Uk{4>Kf8v11;f8I&c76+B&AQ8udd<8gU7+BeWC`akUU~U zgXoxie>MS@rBoyY8O8Tc&8id!w+_ooxcr!1?#rc$-|SBBtH6S?)1e#P#S?jFZ8u-Bs&k`yLqW|{j+%c#A4AQ>+tj$Y z^CZajspu$F%73E68Lw5q7IVREED9r1Ijsg#@DzH>wKseye>hjsk^{n0g?3+gs@7`i zHx+-!sjLx^fS;fY!ERBU+Q zVJ!e0hJH%P)z!y%1^ZyG0>PN@5W~SV%f>}c?$H8r;Sy-ui>aruVTY=bHe}$e zi&Q4&XK!qT7-XjCrDaufT@>ieQ&4G(SShUob0Q>Gznep9fR783jGuUynAqc6$pYX; z7*O@@JW>O6lKIk0G00xsm|=*UVTQBB`u1f=6wGAj%nHK_;Aqmfa!eAykDmi-@u%6~ z;*c!pS1@V8r@IX9j&rW&d*}wpNs96O2Ute>%yt{yv>k!6zfT6pru{F1M3P z2WN1JDYqoTB#(`kE{H676QOoX`cnqHl1Yaru)>8Ky~VU{)r#{&s86Vz5X)v15ULHA zAZDb{99+s~qI6;-dQ5DBjHJP@GYTwn;Dv&9kE<0R!d z8tf1oq$kO`_sV(NHOSbMwr=To4r^X$`sBW4$gWUov|WY?xccQJN}1DOL|GEaD_!@& z15p?Pj+>7d`@LvNIu9*^hPN)pwcv|akvYYq)ks%`G>!+!pW{-iXPZsRp8 z35LR;DhseQKWYSD`%gO&k$Dj6_6q#vjWA}rZcWtQr=Xn*)kJ9kacA=esi*I<)1>w^ zO_+E>QvjP)qiSZg9M|GNeLtO2D7xT6vsj`88sd!94j^AqxFLi}@w9!Y*?nwWARE0P znuI_7A-saQ+%?MFA$gttMV-NAR^#tjl_e{R$N8t2NbOlX373>e7Ox=l=;y#;M7asp zRCz*CLnrm$esvSb5{T<$6CjY zmZ(i{Rs_<#pWW>(HPaaYj`%YqBra=Ey3R21O7vUbzOkJJO?V`4-D*u4$Me0Bx$K(lYo`JO}gnC zx`V}a7m-hLU9Xvb@K2ymioF)vj12<*^oAqRuG_4u%(ah?+go%$kOpfb`T96P+L$4> zQ#S+sA%VbH&mD1k5Ak7^^dZoC>`1L%i>ZXmooA!%GI)b+$D&ziKrb)a=-ds9xk#~& z7)3iem6I|r5+ZrTRe_W861x8JpD`DDIYZNm{$baw+$)X^Jtjnl0xlBgdnNY}x%5za zkQ8E6T<^$sKBPtL4(1zi_Rd(tVth*3Xs!ulflX+70?gb&jRTnI8l+*Aj9{|d%qLZ+ z>~V9Z;)`8-lds*Zgs~z1?Fg?Po7|FDl(Ce<*c^2=lFQ~ahwh6rqSjtM5+$GT>3WZW zj;u~w9xwAhOc<kF}~`CJ68 z?(S5vNJa;kriPlim33{N5`C{9?NWhzsna_~^|K2k4xz1`xcui*LXL-1#Y}Hi9`Oo!zQ>x-kgAX4LrPz63uZ+?uG*84@PKq-KgQlMNRwz=6Yes) zY}>YN+qP}nwr$(CZQFjUOI=-6J$2^XGvC~EZ+vrqWaOXB$k?%Suf5k=4>AveC1aJ! ziaW4IS%F$_Babi)kA8Y&u4F7E%99OPtm=vzw$$ zEz#9rvn`Iot_z-r3MtV>k)YvErZ<^Oa${`2>MYYODSr6?QZu+be-~MBjwPGdMvGd!b!elsdi4% z`37W*8+OGulab8YM?`KjJ8e+jM(tqLKSS@=jimq3)Ea2EB%88L8CaM+aG7;27b?5` z4zuUWBr)f)k2o&xg{iZ$IQkJ+SK>lpq4GEacu~eOW4yNFLU!Kgc{w4&D$4ecm0f}~ zTTzquRW@`f0}|IILl`!1P+;69g^upiPA6F{)U8)muWHzexRenBU$E^9X-uIY2%&1w z_=#5*(nmxJ9zF%styBwivi)?#KMG96-H@hD-H_&EZiRNsfk7mjBq{L%!E;Sqn!mVX*}kXhwH6eh;b42eD!*~upVG@ z#smUqz$ICm!Y8wY53gJeS|Iuard0=;k5i5Z_hSIs6tr)R4n*r*rE`>38Pw&lkv{_r!jNN=;#?WbMj|l>cU(9trCq; z%nN~r^y7!kH^GPOf3R}?dDhO=v^3BeP5hF|%4GNQYBSwz;x({21i4OQY->1G=KFyu z&6d`f2tT9Yl_Z8YACZaJ#v#-(gcyeqXMhYGXb=t>)M@fFa8tHp2x;ODX=Ap@a5I=U z0G80^$N0G4=U(>W%mrrThl0DjyQ-_I>+1Tdd_AuB3qpYAqY54upwa3}owa|x5iQ^1 zEf|iTZxKNGRpI>34EwkIQ2zHDEZ=(J@lRaOH>F|2Z%V_t56Km$PUYu^xA5#5Uj4I4RGqHD56xT%H{+P8Ag>e_3pN$4m8n>i%OyJFPNWaEnJ4McUZPa1QmOh?t8~n& z&RulPCors8wUaqMHECG=IhB(-tU2XvHP6#NrLVyKG%Ee*mQ5Ps%wW?mcnriTVRc4J`2YVM>$ixSF2Xi+Wn(RUZnV?mJ?GRdw%lhZ+t&3s7g!~g{%m&i<6 z5{ib-<==DYG93I(yhyv4jp*y3#*WNuDUf6`vTM%c&hiayf(%=x@4$kJ!W4MtYcE#1 zHM?3xw63;L%x3drtd?jot!8u3qeqctceX3m;tWetK+>~q7Be$h>n6riK(5@ujLgRS zvOym)k+VAtyV^mF)$29Y`nw&ijdg~jYpkx%*^ z8dz`C*g=I?;clyi5|!27e2AuSa$&%UyR(J3W!A=ZgHF9OuKA34I-1U~pyD!KuRkjA zbkN!?MfQOeN>DUPBxoy5IX}@vw`EEB->q!)8fRl_mqUVuRu|C@KD-;yl=yKc=ZT0% zB$fMwcC|HE*0f8+PVlWHi>M`zfsA(NQFET?LrM^pPcw`cK+Mo0%8*x8@65=CS_^$cG{GZQ#xv($7J z??R$P)nPLodI;P!IC3eEYEHh7TV@opr#*)6A-;EU2XuogHvC;;k1aI8asq7ovoP!* z?x%UoPrZjj<&&aWpsbr>J$Er-7!E(BmOyEv!-mbGQGeJm-U2J>74>o5x`1l;)+P&~ z>}f^=Rx(ZQ2bm+YE0u=ZYrAV@apyt=v1wb?R@`i_g64YyAwcOUl=C!i>=Lzb$`tjv zOO-P#A+)t-JbbotGMT}arNhJmmGl-lyUpMn=2UacVZxmiG!s!6H39@~&uVokS zG=5qWhfW-WOI9g4!R$n7!|ViL!|v3G?GN6HR0Pt_L5*>D#FEj5wM1DScz4Jv@Sxnl zB@MPPmdI{(2D?;*wd>3#tjAirmUnQoZrVv`xM3hARuJksF(Q)wd4P$88fGYOT1p6U z`AHSN!`St}}UMBT9o7i|G`r$ zrB=s$qV3d6$W9@?L!pl0lf%)xs%1ko^=QY$ty-57=55PvP(^6E7cc zGJ*>m2=;fOj?F~yBf@K@9qwX0hA803Xw+b0m}+#a(>RyR8}*Y<4b+kpp|OS+!whP( zH`v{%s>jsQI9rd$*vm)EkwOm#W_-rLTHcZRek)>AtF+~<(did)*oR1|&~1|e36d-d zgtm5cv1O0oqgWC%Et@P4Vhm}Ndl(Y#C^MD03g#PH-TFy+7!Osv1z^UWS9@%JhswEq~6kSr2DITo59+; ze=ZC}i2Q?CJ~Iyu?vn|=9iKV>4j8KbxhE4&!@SQ^dVa-gK@YfS9xT(0kpW*EDjYUkoj! zE49{7H&E}k%5(>sM4uGY)Q*&3>{aitqdNnRJkbOmD5Mp5rv-hxzOn80QsG=HJ_atI-EaP69cacR)Uvh{G5dTpYG7d zbtmRMq@Sexey)||UpnZ?;g_KMZq4IDCy5}@u!5&B^-=6yyY{}e4Hh3ee!ZWtL*s?G zxG(A!<9o!CL+q?u_utltPMk+hn?N2@?}xU0KlYg?Jco{Yf@|mSGC<(Zj^yHCvhmyx z?OxOYoxbptDK()tsJ42VzXdINAMWL$0Gcw?G(g8TMB)Khw_|v9`_ql#pRd2i*?CZl z7k1b!jQB=9-V@h%;Cnl7EKi;Y^&NhU0mWEcj8B|3L30Ku#-9389Q+(Yet0r$F=+3p z6AKOMAIi|OHyzlHZtOm73}|ntKtFaXF2Fy|M!gOh^L4^62kGUoWS1i{9gsds_GWBc zLw|TaLP64z3z9?=R2|T6Xh2W4_F*$cq>MtXMOy&=IPIJ`;!Tw?PqvI2b*U1)25^<2 zU_ZPoxg_V0tngA0J+mm?3;OYw{i2Zb4x}NedZug!>EoN3DC{1i)Z{Z4m*(y{ov2%- zk(w>+scOO}MN!exSc`TN)!B=NUX`zThWO~M*ohqq;J2hx9h9}|s#?@eR!=F{QTrq~ zTcY|>azkCe$|Q0XFUdpFT=lTcyW##i;-e{}ORB4D?t@SfqGo_cS z->?^rh$<&n9DL!CF+h?LMZRi)qju!meugvxX*&jfD!^1XB3?E?HnwHP8$;uX{Rvp# zh|)hM>XDv$ZGg=$1{+_bA~u-vXqlw6NH=nkpyWE0u}LQjF-3NhATL@9rRxMnpO%f7 z)EhZf{PF|mKIMFxnC?*78(}{Y)}iztV12}_OXffJ;ta!fcFIVjdchyHxH=t%ci`Xd zX2AUB?%?poD6Zv*&BA!6c5S#|xn~DK01#XvjT!w!;&`lDXSJT4_j$}!qSPrb37vc{ z9^NfC%QvPu@vlxaZ;mIbn-VHA6miwi8qJ~V;pTZkKqqOii<1Cs}0i?uUIss;hM4dKq^1O35y?Yp=l4i zf{M!@QHH~rJ&X~8uATV><23zZUbs-J^3}$IvV_ANLS08>k`Td7aU_S1sLsfi*C-m1 z-e#S%UGs4E!;CeBT@9}aaI)qR-6NU@kvS#0r`g&UWg?fC7|b^_HyCE!8}nyh^~o@< zpm7PDFs9yxp+byMS(JWm$NeL?DNrMCNE!I^ko-*csB+dsf4GAq{=6sfyf4wb>?v1v zmb`F*bN1KUx-`ra1+TJ37bXNP%`-Fd`vVQFTwWpX@;s(%nDQa#oWhgk#mYlY*!d>( zE&!|ySF!mIyfING+#%RDY3IBH_fW$}6~1%!G`suHub1kP@&DoAd5~7J55;5_noPI6eLf{t;@9Kf<{aO0`1WNKd?<)C-|?C?)3s z>wEq@8=I$Wc~Mt$o;g++5qR+(6wt9GI~pyrDJ%c?gPZe)owvy^J2S=+M^ z&WhIE`g;;J^xQLVeCtf7b%Dg#Z2gq9hp_%g)-%_`y*zb; zn9`f`mUPN-Ts&fFo(aNTsXPA|J!TJ{0hZp0^;MYHLOcD=r_~~^ymS8KLCSeU3;^QzJNqS z5{5rEAv#l(X?bvwxpU;2%pQftF`YFgrD1jt2^~Mt^~G>T*}A$yZc@(k9orlCGv&|1 zWWvVgiJsCAtamuAYT~nzs?TQFt<1LSEx!@e0~@yd6$b5!Zm(FpBl;(Cn>2vF?k zOm#TTjFwd2D-CyA!mqR^?#Uwm{NBemP>(pHmM}9;;8`c&+_o3#E5m)JzfwN?(f-a4 zyd%xZc^oQx3XT?vcCqCX&Qrk~nu;fxs@JUoyVoi5fqpi&bUhQ2y!Ok2pzsFR(M(|U zw3E+kH_zmTRQ9dUMZWRE%Zakiwc+lgv7Z%|YO9YxAy`y28`Aw;WU6HXBgU7fl@dnt z-fFBV)}H-gqP!1;V@Je$WcbYre|dRdp{xt!7sL3Eoa%IA`5CAA%;Wq8PktwPdULo! z8!sB}Qt8#jH9Sh}QiUtEPZ6H0b*7qEKGJ%ITZ|vH)5Q^2m<7o3#Z>AKc%z7_u`rXA zqrCy{-{8;9>dfllLu$^M5L z-hXs))h*qz%~ActwkIA(qOVBZl2v4lwbM>9l70Y`+T*elINFqt#>OaVWoja8RMsep z6Or3f=oBnA3vDbn*+HNZP?8LsH2MY)x%c13@(XfuGR}R?Nu<|07{$+Lc3$Uv^I!MQ z>6qWgd-=aG2Y^24g4{Bw9ueOR)(9h`scImD=86dD+MnSN4$6 z^U*o_mE-6Rk~Dp!ANp#5RE9n*LG(Vg`1)g6!(XtDzsov$Dvz|Gv1WU68J$CkshQhS zCrc|cdkW~UK}5NeaWj^F4MSgFM+@fJd{|LLM)}_O<{rj z+?*Lm?owq?IzC%U%9EBga~h-cJbIu=#C}XuWN>OLrc%M@Gu~kFEYUi4EC6l#PR2JS zQUkGKrrS#6H7}2l0F@S11DP`@pih0WRkRJl#F;u{c&ZC{^$Z+_*lB)r)-bPgRFE;* zl)@hK4`tEP=P=il02x7-C7p%l=B`vkYjw?YhdJU9!P!jcmY$OtC^12w?vy3<<=tlY zUwHJ_0lgWN9vf>1%WACBD{UT)1qHQSE2%z|JHvP{#INr13jM}oYv_5#xsnv9`)UAO zuwgyV4YZ;O)eSc3(mka6=aRohi!HH@I#xq7kng?Acdg7S4vDJb6cI5fw?2z%3yR+| zU5v@Hm}vy;${cBp&@D=HQ9j7NcFaOYL zj-wV=eYF{|XTkFNM2uz&T8uH~;)^Zo!=KP)EVyH6s9l1~4m}N%XzPpduPg|h-&lL` zAXspR0YMOKd2yO)eMFFJ4?sQ&!`dF&!|niH*!^*Ml##o0M(0*uK9&yzekFi$+mP9s z>W9d%Jb)PtVi&-Ha!o~Iyh@KRuKpQ@)I~L*d`{O8!kRObjO7=n+Gp36fe!66neh+7 zW*l^0tTKjLLzr`x4`_8&on?mjW-PzheTNox8Hg7Nt@*SbE-%kP2hWYmHu#Fn@Q^J(SsPUz*|EgOoZ6byg3ew88UGdZ>9B2Tq=jF72ZaR=4u%1A6Vm{O#?@dD!(#tmR;eP(Fu z{$0O%=Vmua7=Gjr8nY%>ul?w=FJ76O2js&17W_iq2*tb!i{pt#`qZB#im9Rl>?t?0c zicIC}et_4d+CpVPx)i4~$u6N-QX3H77ez z?ZdvXifFk|*F8~L(W$OWM~r`pSk5}#F?j_5u$Obu9lDWIknO^AGu+Blk7!9Sb;NjS zncZA?qtASdNtzQ>z7N871IsPAk^CC?iIL}+{K|F@BuG2>qQ;_RUYV#>hHO(HUPpk@ z(bn~4|F_jiZi}Sad;_7`#4}EmD<1EiIxa48QjUuR?rC}^HRocq`OQPM@aHVKP9E#q zy%6bmHygCpIddPjE}q_DPC`VH_2m;Eey&ZH)E6xGeStOK7H)#+9y!%-Hm|QF6w#A( zIC0Yw%9j$s-#odxG~C*^MZ?M<+&WJ+@?B_QPUyTg9DJGtQN#NIC&-XddRsf3n^AL6 zT@P|H;PvN;ZpL0iv$bRb7|J{0o!Hq+S>_NrH4@coZtBJu#g8#CbR7|#?6uxi8d+$g z87apN>EciJZ`%Zv2**_uiET9Vk{pny&My;+WfGDw4EVL#B!Wiw&M|A8f1A@ z(yFQS6jfbH{b8Z-S7D2?Ixl`j0{+ZnpT=;KzVMLW{B$`N?Gw^Fl0H6lT61%T2AU**!sX0u?|I(yoy&Xveg7XBL&+>n6jd1##6d>TxE*Vj=8lWiG$4=u{1UbAa5QD>5_ z;Te^42v7K6Mmu4IWT6Rnm>oxrl~b<~^e3vbj-GCdHLIB_>59}Ya+~OF68NiH=?}2o zP(X7EN=quQn&)fK>M&kqF|<_*H`}c zk=+x)GU>{Af#vx&s?`UKUsz})g^Pc&?Ka@t5$n$bqf6{r1>#mWx6Ep>9|A}VmWRnowVo`OyCr^fHsf# zQjQ3Ttp7y#iQY8l`zEUW)(@gGQdt(~rkxlkefskT(t%@i8=|p1Y9Dc5bc+z#n$s13 zGJk|V0+&Ekh(F};PJzQKKo+FG@KV8a<$gmNSD;7rd_nRdc%?9)p!|B-@P~kxQG}~B zi|{0}@}zKC(rlFUYp*dO1RuvPC^DQOkX4<+EwvBAC{IZQdYxoq1Za!MW7%p7gGr=j zzWnAq%)^O2$eItftC#TTSArUyL$U54-O7e|)4_7%Q^2tZ^0-d&3J1}qCzR4dWX!)4 zzIEKjgnYgMus^>6uw4Jm8ga6>GBtMjpNRJ6CP~W=37~||gMo_p@GA@#-3)+cVYnU> zE5=Y4kzl+EbEh%dhQokB{gqNDqx%5*qBusWV%!iprn$S!;oN_6E3?0+umADVs4ako z?P+t?m?};gev9JXQ#Q&KBpzkHPde_CGu-y z<{}RRAx=xlv#mVi+Ibrgx~ujW$h{?zPfhz)Kp7kmYS&_|97b&H&1;J-mzrBWAvY} zh8-I8hl_RK2+nnf&}!W0P+>5?#?7>npshe<1~&l_xqKd0_>dl_^RMRq@-Myz&|TKZBj1=Q()) zF{dBjv5)h=&Z)Aevx}+i|7=R9rG^Di!sa)sZCl&ctX4&LScQ-kMncgO(9o6W6)yd< z@Rk!vkja*X_N3H=BavGoR0@u0<}m-7|2v!0+2h~S2Q&a=lTH91OJsvms2MT~ zY=c@LO5i`mLpBd(vh|)I&^A3TQLtr>w=zoyzTd=^f@TPu&+*2MtqE$Avf>l>}V|3-8Fp2hzo3y<)hr_|NO(&oSD z!vEjTWBxbKTiShVl-U{n*B3#)3a8$`{~Pk}J@elZ=>Pqp|MQ}jrGv7KrNcjW%TN_< zZz8kG{#}XoeWf7qY?D)L)8?Q-b@Na&>i=)(@uNo zr;cH98T3$Iau8Hn*@vXi{A@YehxDE2zX~o+RY`)6-X{8~hMpc#C`|8y> zU8Mnv5A0dNCf{Ims*|l-^ z(MRp{qoGohB34|ggDI*p!Aw|MFyJ|v+<+E3brfrI)|+l3W~CQLPbnF@G0)P~Ly!1TJLp}xh8uW`Q+RB-v`MRYZ9Gam3cM%{ zb4Cb*f)0deR~wtNb*8w-LlIF>kc7DAv>T0D(a3@l`k4TFnrO+g9XH7;nYOHxjc4lq zMmaW6qpgAgy)MckYMhl?>sq;-1E)-1llUneeA!ya9KM$)DaNGu57Z5aE>=VST$#vb zFo=uRHr$0M{-ha>h(D_boS4zId;3B|Tpqo|?B?Z@I?G(?&Iei+-{9L_A9=h=Qfn-U z1wIUnQe9!z%_j$F_{rf&`ZFSott09gY~qrf@g3O=Y>vzAnXCyL!@(BqWa)Zqt!#_k zfZHuwS52|&&)aK;CHq9V-t9qt0au{$#6c*R#e5n3rje0hic7c7m{kW$p(_`wB=Gw7 z4k`1Hi;Mc@yA7dp@r~?@rfw)TkjAW++|pkfOG}0N|2guek}j8Zen(!+@7?qt_7ndX zB=BG6WJ31#F3#Vk3=aQr8T)3`{=p9nBHlKzE0I@v`{vJ}h8pd6vby&VgFhzH|q;=aonunAXL6G2y(X^CtAhWr*jI zGjpY@raZDQkg*aMq}Ni6cRF z{oWv}5`nhSAv>usX}m^GHt`f(t8@zHc?K|y5Zi=4G*UG1Sza{$Dpj%X8 zzEXaKT5N6F5j4J|w#qlZP!zS7BT)9b+!ZSJdToqJts1c!)fwih4d31vfb{}W)EgcA zH2pZ^8_k$9+WD2n`6q5XbOy8>3pcYH9 z07eUB+p}YD@AH!}p!iKv><2QF-Y^&xx^PAc1F13A{nUeCDg&{hnix#FiO!fe(^&%Qcux!h znu*S!s$&nnkeotYsDthh1dq(iQrE|#f_=xVgfiiL&-5eAcC-> z5L0l|DVEM$#ulf{bj+Y~7iD)j<~O8CYM8GW)dQGq)!mck)FqoL^X zwNdZb3->hFrbHFm?hLvut-*uK?zXn3q1z|UX{RZ;-WiLoOjnle!xs+W0-8D)kjU#R z+S|A^HkRg$Ij%N4v~k`jyHffKaC~=wg=9)V5h=|kLQ@;^W!o2^K+xG&2n`XCd>OY5Ydi= zgHH=lgy++erK8&+YeTl7VNyVm9-GfONlSlVb3)V9NW5tT!cJ8d7X)!b-$fb!s76{t z@d=Vg-5K_sqHA@Zx-L_}wVnc@L@GL9_K~Zl(h5@AR#FAiKad8~KeWCo@mgXIQ#~u{ zgYFwNz}2b6Vu@CP0XoqJ+dm8px(5W5-Jpis97F`+KM)TuP*X8H@zwiVKDKGVp59pI zifNHZr|B+PG|7|Y<*tqap0CvG7tbR1R>jn70t1X`XJixiMVcHf%Ez*=xm1(CrTSDt z0cle!+{8*Ja&EOZ4@$qhBuKQ$U95Q%rc7tg$VRhk?3=pE&n+T3upZg^ZJc9~c2es% zh7>+|mrmA-p&v}|OtxqmHIBgUxL~^0+cpfkSK2mhh+4b=^F1Xgd2)}U*Yp+H?ls#z zrLxWg_hm}AfK2XYWr!rzW4g;+^^&bW%LmbtRai9f3PjU${r@n`JThy-cphbcwn)rq9{A$Ht`lmYKxOacy z6v2R(?gHhD5@&kB-Eg?4!hAoD7~(h>(R!s1c1Hx#s9vGPePUR|of32bS`J5U5w{F) z>0<^ktO2UHg<0{oxkdOQ;}coZDQph8p6ruj*_?uqURCMTac;>T#v+l1Tc~%^k-Vd@ zkc5y35jVNc49vZpZx;gG$h{%yslDI%Lqga1&&;mN{Ush1c7p>7e-(zp}6E7f-XmJb4nhk zb8zS+{IVbL$QVF8pf8}~kQ|dHJAEATmmnrb_wLG}-yHe>W|A&Y|;muy-d^t^<&)g5SJfaTH@P1%euONny=mxo+C z4N&w#biWY41r8k~468tvuYVh&XN&d#%QtIf9;iVXfWY)#j=l`&B~lqDT@28+Y!0E+MkfC}}H*#(WKKdJJq=O$vNYCb(ZG@p{fJgu;h z21oHQ(14?LeT>n5)s;uD@5&ohU!@wX8w*lB6i@GEH0pM>YTG+RAIWZD;4#F1&F%Jp zXZUml2sH0!lYJT?&sA!qwez6cXzJEd(1ZC~kT5kZSp7(@=H2$Azb_*W&6aA|9iwCL zdX7Q=42;@dspHDwYE?miGX#L^3xD&%BI&fN9^;`v4OjQXPBaBmOF1;#C)8XA(WFlH zycro;DS2?(G&6wkr6rqC>rqDv3nfGw3hmN_9Al>TgvmGsL8_hXx09};l9Ow@)F5@y z#VH5WigLDwZE4nh^7&@g{1FV^UZ%_LJ-s<{HN*2R$OPg@R~Z`c-ET*2}XB@9xvAjrK&hS=f|R8Gr9 zr|0TGOsI7RD+4+2{ZiwdVD@2zmg~g@^D--YL;6UYGSM8i$NbQr4!c7T9rg!8;TM0E zT#@?&S=t>GQm)*ua|?TLT2ktj#`|R<_*FAkOu2Pz$wEc%-=Y9V*$&dg+wIei3b*O8 z2|m$!jJG!J!ZGbbIa!(Af~oSyZV+~M1qGvelMzPNE_%5?c2>;MeeG2^N?JDKjFYCy z7SbPWH-$cWF9~fX%9~v99L!G(wi!PFp>rB!9xj7=Cv|F+7CsGNwY0Q_J%FID%C^CBZQfJ9K(HK%k31j~e#&?hQ zNuD6gRkVckU)v+53-fc} z7ZCzYN-5RG4H7;>>Hg?LU9&5_aua?A0)0dpew1#MMlu)LHe(M;OHjHIUl7|%%)YPo z0cBk;AOY00%Fe6heoN*$(b<)Cd#^8Iu;-2v@>cE-OB$icUF9EEoaC&q8z9}jMTT2I z8`9;jT%z0;dy4!8U;GW{i`)3!c6&oWY`J3669C!tM<5nQFFrFRglU8f)5Op$GtR-3 zn!+SPCw|04sv?%YZ(a7#L?vsdr7ss@WKAw&A*}-1S|9~cL%uA+E~>N6QklFE>8W|% zyX-qAUGTY1hQ-+um`2|&ji0cY*(qN!zp{YpDO-r>jPk*yuVSay<)cUt`t@&FPF_&$ zcHwu1(SQ`I-l8~vYyUxm@D1UEdFJ$f5Sw^HPH7b!9 zzYT3gKMF((N(v0#4f_jPfVZ=ApN^jQJe-X$`A?X+vWjLn_%31KXE*}5_}d8 zw_B1+a#6T1?>M{ronLbHIlEsMf93muJ7AH5h%;i99<~JX^;EAgEB1uHralD*!aJ@F zV2ruuFe9i2Q1C?^^kmVy921eb=tLDD43@-AgL^rQ3IO9%+vi_&R2^dpr}x{bCVPej z7G0-0o64uyWNtr*loIvslyo0%)KSDDKjfThe0hcqs)(C-MH1>bNGBDRTW~scy_{w} zp^aq8Qb!h9Lwielq%C1b8=?Z=&U)ST&PHbS)8Xzjh2DF?d{iAv)Eh)wsUnf>UtXN( zL7=$%YrZ#|^c{MYmhn!zV#t*(jdmYdCpwqpZ{v&L8KIuKn`@IIZfp!uo}c;7J57N` zAxyZ-uA4=Gzl~Ovycz%MW9ZL7N+nRo&1cfNn9(1H5eM;V_4Z_qVann7F>5f>%{rf= zPBZFaV@_Sobl?Fy&KXyzFDV*FIdhS5`Uc~S^Gjo)aiTHgn#<0C=9o-a-}@}xDor;D zZyZ|fvf;+=3MZd>SR1F^F`RJEZo+|MdyJYQAEauKu%WDol~ayrGU3zzbHKsnHKZ*z zFiwUkL@DZ>!*x05ql&EBq@_Vqv83&?@~q5?lVmffQZ+V-=qL+!u4Xs2Z2zdCQ3U7B&QR9_Iggy} z(om{Y9eU;IPe`+p1ifLx-XWh?wI)xU9ik+m#g&pGdB5Bi<`PR*?92lE0+TkRuXI)z z5LP!N2+tTc%cB6B1F-!fj#}>S!vnpgVU~3!*U1ej^)vjUH4s-bd^%B=ItQqDCGbrEzNQi(dJ`J}-U=2{7-d zK8k^Rlq2N#0G?9&1?HSle2vlkj^KWSBYTwx`2?9TU_DX#J+f+qLiZCqY1TXHFxXZqYMuD@RU$TgcnCC{_(vwZ-*uX)~go#%PK z@}2Km_5aQ~(<3cXeJN6|F8X_1@L%@xTzs}$_*E|a^_URF_qcF;Pfhoe?FTFwvjm1o z8onf@OY@jC2tVcMaZS;|T!Ks(wOgPpRzRnFS-^RZ4E!9dsnj9sFt609a|jJbb1Dt@ z<=Gal2jDEupxUSwWu6zp<<&RnAA;d&4gKVG0iu6g(DsST(4)z6R)zDpfaQ}v{5ARt zyhwvMtF%b-YazR5XLz+oh=mn;y-Mf2a8>7?2v8qX;19y?b>Z5laGHvzH;Nu9S`B8} zI)qN$GbXIQ1VL3lnof^6TS~rvPVg4V?Dl2Bb*K2z4E{5vy<(@@K_cN@U>R!>aUIRnb zL*)=787*cs#zb31zBC49x$`=fkQbMAef)L2$dR{)6BAz!t5U_B#1zZG`^neKSS22oJ#5B=gl%U=WeqL9REF2g zZnfCb0?quf?Ztj$VXvDSWoK`0L=Zxem2q}!XWLoT-kYMOx)!7fcgT35uC~0pySEme z`{wGWTkGr7>+Kb^n;W?BZH6ZP(9tQX%-7zF>vc2}LuWDI(9kh1G#7B99r4x6;_-V+k&c{nPUrR zAXJGRiMe~aup{0qzmLNjS_BC4cB#sXjckx{%_c&^xy{M61xEb>KW_AG5VFXUOjAG4 z^>Qlm9A#1N{4snY=(AmWzatb!ngqiqPbBZ7>Uhb3)dTkSGcL#&SH>iMO-IJBPua`u zo)LWZ>=NZLr758j{%(|uQuZ)pXq_4c!!>s|aDM9#`~1bzK3J1^^D#<2bNCccH7~-X}Ggi!pIIF>uFx%aPARGQsnC8ZQc8lrQ5o~smqOg>Ti^GNme94*w z)JZy{_{#$jxGQ&`M z!OMvZMHR>8*^>eS%o*6hJwn!l8VOOjZQJvh)@tnHVW&*GYPuxqXw}%M!(f-SQf`=L z5;=5w2;%82VMH6Xi&-K3W)o&K^+vJCepWZ-rW%+Dc6X3(){z$@4zjYxQ|}8UIojeC zYZpQ1dU{fy=oTr<4VX?$q)LP}IUmpiez^O&N3E_qPpchGTi5ZM6-2ScWlQq%V&R2Euz zO|Q0Hx>lY1Q1cW5xHv5!0OGU~PVEqSuy#fD72d#O`N!C;o=m+YioGu-wH2k6!t<~K zSr`E=W9)!g==~x9VV~-8{4ZN9{~-A9zJpRe%NGg$+MDuI-dH|b@BD)~>pPCGUNNzY zMDg||0@XGQgw`YCt5C&A{_+J}mvV9Wg{6V%2n#YSRN{AP#PY?1FF1#|vO_%e+#`|2*~wGAJaeRX6=IzFNeWhz6gJc8+(03Ph4y6ELAm=AkN7TOgMUEw*N{= z_)EIDQx5q22oUR+_b*tazu9+pX|n1c*IB-}{DqIj z-?E|ks{o3AGRNb;+iKcHkZvYJvFsW&83RAPs1Oh@IWy%l#5x2oUP6ZCtv+b|q>jsf zZ_9XO;V!>n`UxH1LvH8)L4?8raIvasEhkpQoJ`%!5rBs!0Tu(s_D{`4opB;57)pkX z4$A^8CsD3U5*!|bHIEqsn~{q+Ddj$ME@Gq4JXtgVz&7l{Ok!@?EA{B3P~NAqb9)4? zkQo30A^EbHfQ@87G5&EQTd`frrwL)&Yw?%-W@uy^Gn23%j?Y!Iea2xw<-f;esq zf%w5WN@E1}zyXtYv}}`U^B>W`>XPmdLj%4{P298|SisrE;7HvXX;A}Ffi8B#3Lr;1 zHt6zVb`8{#+e$*k?w8|O{Uh|&AG}|DG1PFo1i?Y*cQm$ZwtGcVgMwtBUDa{~L1KT-{jET4w60>{KZ27vXrHJ;fW{6| z=|Y4!&UX020wU1>1iRgB@Q#m~1^Z^9CG1LqDhYBrnx%IEdIty z!46iOoKlKs)c}newDG)rWUikD%j`)p z_w9Ph&e40=(2eBy;T!}*1p1f1SAUDP9iWy^u^Ubdj21Kn{46;GR+hwLO=4D11@c~V zI8x&(D({K~Df2E)Nx_yQvYfh4;MbMJ@Z}=Dt3_>iim~QZ*hZIlEs0mEb z_54+&*?wMD`2#vsQRN3KvoT>hWofI_Vf(^C1ff-Ike@h@saEf7g}<9T`W;HAne-Nd z>RR+&SP35w)xKn8^U$7))PsM!jKwYZ*RzEcG-OlTrX3}9a{q%#Un5E5W{{hp>w~;` zGky+3(vJvQyGwBo`tCpmo0mo((?nM8vf9aXrrY1Ve}~TuVkB(zeds^jEfI}xGBCM2 zL1|#tycSaWCurP+0MiActG3LCas@_@tao@(R1ANlwB$4K53egNE_;!&(%@Qo$>h`^1S_!hN6 z)vZtG$8fN!|BXBJ=SI>e(LAU(y(i*PHvgQ2llulxS8>qsimv7yL}0q_E5WiAz7)(f zC(ahFvG8&HN9+6^jGyLHM~$)7auppeWh_^zKk&C_MQ~8;N??OlyH~azgz5fe^>~7F zl3HnPN3z-kN)I$4@`CLCMQx3sG~V8hPS^}XDXZrQA>}mQPw%7&!sd(Pp^P=tgp-s^ zjl}1-KRPNWXgV_K^HkP__SR`S-|OF0bR-N5>I%ODj&1JUeAQ3$9i;B~$S6}*^tK?= z**%aCiH7y?xdY?{LgVP}S0HOh%0%LI$wRx;$T|~Y8R)Vdwa}kGWv8?SJVm^>r6+%I z#lj1aR94{@MP;t-scEYQWc#xFA30^}?|BeX*W#9OL;Q9#WqaaM546j5j29((^_8Nu z4uq}ESLr~r*O7E7$D{!k9W>`!SLoyA53i9QwRB{!pHe8um|aDE`Cg0O*{jmor)^t)3`>V>SWN-2VJcFmj^1?~tT=JrP`fVh*t zXHarp=8HEcR#vFe+1a%XXuK+)oFs`GDD}#Z+TJ}Ri`FvKO@ek2ayn}yaOi%(8p%2$ zpEu)v0Jym@f}U|-;}CbR=9{#<^z28PzkkTNvyKvJDZe+^VS2bES3N@Jq!-*}{oQlz z@8bgC_KnDnT4}d#&Cpr!%Yb?E!brx0!eVOw~;lLwUoz#Np%d$o%9scc3&zPm`%G((Le|6o1 zM(VhOw)!f84zG^)tZ1?Egv)d8cdNi+T${=5kV+j;Wf%2{3g@FHp^Gf*qO0q!u$=m9 zCaY`4mRqJ;FTH5`a$affE5dJrk~k`HTP_7nGTY@B9o9vvnbytaID;^b=Tzp7Q#DmD zC(XEN)Ktn39z5|G!wsVNnHi) z%^q94!lL|hF`IijA^9NR0F$@h7k5R^ljOW(;Td9grRN0Mb)l_l7##{2nPQ@?;VjXv zaLZG}yuf$r$<79rVPpXg?6iiieX|r#&`p#Con2i%S8*8F}(E) zI5E6c3tG*<;m~6>!&H!GJ6zEuhH7mkAzovdhLy;)q z{H2*8I^Pb}xC4s^6Y}6bJvMu=8>g&I)7!N!5QG$xseeU#CC?ZM-TbjsHwHgDGrsD= z{%f;@Sod+Ch66Ko2WF~;Ty)v>&x^aovCbCbD7>qF*!?BXmOV3(s|nxsb*Lx_2lpB7 zokUnzrk;P=T-&kUHO}td+Zdj!3n&NR?K~cRU zAXU!DCp?51{J4w^`cV#ye}(`SQhGQkkMu}O3M*BWt4UsC^jCFUy;wTINYmhD$AT;4 z?Xd{HaJjP`raZ39qAm;%beDbrLpbRf(mkKbANan7XsL>_pE2oo^$TgdidjRP!5-`% zv0d!|iKN$c0(T|L0C~XD0aS8t{*&#LnhE;1Kb<9&=c2B+9JeLvJr*AyyRh%@jHej=AetOMSlz^=!kxX>>B{2B1uIrQyfd8KjJ+DBy!h)~*(!|&L4^Q_07SQ~E zcemVP`{9CwFvPFu7pyVGCLhH?LhEVb2{7U+Z_>o25#+3<|8%1T^5dh}*4(kfJGry} zm%r#hU+__Z;;*4fMrX=Bkc@7|v^*B;HAl0((IBPPii%X9+u3DDF6%bI&6?Eu$8&aWVqHIM7mK6?Uvq$1|(-T|)IV<>e?!(rY zqkmO1MRaLeTR=)io(0GVtQT@s6rN%C6;nS3@eu;P#ry4q;^O@1ZKCJyp_Jo)Ty^QW z+vweTx_DLm{P-XSBj~Sl<%_b^$=}odJ!S2wAcxenmzFGX1t&Qp8Vxz2VT`uQsQYtdn&_0xVivIcxZ_hnrRtwq4cZSj1c-SG9 z7vHBCA=fd0O1<4*=lu$6pn~_pVKyL@ztw1swbZi0B?spLo56ZKu5;7ZeUml1Ws1?u zqMf1p{5myAzeX$lAi{jIUqo1g4!zWLMm9cfWcnw`k6*BR^?$2(&yW?>w;G$EmTA@a z6?y#K$C~ZT8+v{87n5Dm&H6Pb_EQ@V0IWmG9cG=O;(;5aMWWrIPzz4Q`mhK;qQp~a z+BbQrEQ+w{SeiuG-~Po5f=^EvlouB@_|4xQXH@A~KgpFHrwu%dwuCR)=B&C(y6J4J zvoGk9;lLs9%iA-IJGU#RgnZZR+@{5lYl8(e1h6&>Vc_mvg0d@);X zji4T|n#lB!>pfL|8tQYkw?U2bD`W{na&;*|znjmalA&f;*U++_aBYerq;&C8Kw7mI z7tsG*?7*5j&dU)Lje;^{D_h`%(dK|pB*A*1(Jj)w^mZ9HB|vGLkF1GEFhu&rH=r=8 zMxO42e{Si6$m+Zj`_mXb&w5Q(i|Yxyg?juUrY}78uo@~3v84|8dfgbPd0iQJRdMj< zncCNGdMEcsxu#o#B5+XD{tsg*;j-eF8`mp~K8O1J!Z0+>0=7O=4M}E?)H)ENE;P*F z$Ox?ril_^p0g7xhDUf(q652l|562VFlC8^r8?lQv;TMvn+*8I}&+hIQYh2 z1}uQQaag&!-+DZ@|C+C$bN6W;S-Z@)d1|en+XGvjbOxCa-qAF*LA=6s(Jg+g;82f$ z(Vb)8I)AH@cdjGFAR5Rqd0wiNCu!xtqWbcTx&5kslzTb^7A78~Xzw1($UV6S^VWiP zFd{Rimd-0CZC_Bu(WxBFW7+k{cOW7DxBBkJdJ;VsJ4Z@lERQr%3eVv&$%)b%<~ zCl^Y4NgO}js@u{|o~KTgH}>!* z_iDNqX2(As7T0xivMH|3SC1ivm8Q}6Ffcd7owUKN5lHAtzMM4<0v+ykUT!QiowO;`@%JGv+K$bBx@*S7C8GJVqQ_K>12}M`f_Ys=S zKFh}HM9#6Izb$Y{wYzItTy+l5U2oL%boCJn?R3?jP@n$zSIwlmyGq30Cw4QBO|14` zW5c);AN*J3&eMFAk$SR~2k|&+&Bc$e>s%c{`?d~85S-UWjA>DS5+;UKZ}5oVa5O(N zqqc@>)nee)+4MUjH?FGv%hm2{IlIF-QX}ym-7ok4Z9{V+ZHVZQl$A*x!(q%<2~iVv znUa+BX35&lCb#9VE-~Y^W_f;Xhl%vgjwdjzMy$FsSIj&ok}L+X`4>J=9BkN&nu^E*gbhj3(+D>C4E z@Fwq_=N)^bKFSHTzZk?-gNU$@l}r}dwGyh_fNi=9b|n}J>&;G!lzilbWF4B}BBq4f zYIOl?b)PSh#XTPp4IS5ZR_2C!E)Z`zH0OW%4;&~z7UAyA-X|sh9@~>cQW^COA9hV4 zXcA6qUo9P{bW1_2`eo6%hgbN%(G-F1xTvq!sc?4wN6Q4`e9Hku zFwvlAcRY?6h^Fj$R8zCNEDq8`=uZB8D-xn)tA<^bFFy}4$vA}Xq0jAsv1&5!h!yRA zU()KLJya5MQ`q&LKdH#fwq&(bNFS{sKlEh_{N%{XCGO+po#(+WCLmKW6&5iOHny>g z3*VFN?mx!16V5{zyuMWDVP8U*|BGT$(%IO|)?EF|OI*sq&RovH!N%=>i_c?K*A>>k zyg1+~++zY4Q)J;VWN0axhoIKx;l&G$gvj(#go^pZskEVj8^}is3Jw26LzYYVos0HX zRPvmK$dVxM8(Tc?pHFe0Z3uq){{#OK3i-ra#@+;*=ui8)y6hsRv z4Fxx1c1+fr!VI{L3DFMwXKrfl#Q8hfP@ajgEau&QMCxd{g#!T^;ATXW)nUg&$-n25 zruy3V!!;{?OTobo|0GAxe`Acn3GV@W=&n;~&9 zQM>NWW~R@OYORkJAo+eq1!4vzmf9K%plR4(tB@TR&FSbDoRgJ8qVcH#;7lQub*nq&?Z>7WM=oeEVjkaG zT#f)=o!M2DO5hLR+op>t0CixJCIeXH*+z{-XS|%jx)y(j&}Wo|3!l7{o)HU3m7LYyhv*xF&tq z%IN7N;D4raue&&hm0xM=`qv`+TK@;_xAcGKuK(2|75~ar2Yw)geNLSmVxV@x89bQu zpViVKKnlkwjS&&c|-X6`~xdnh}Ps)Hs z4VbUL^{XNLf7_|Oi>tA%?SG5zax}esF*FH3d(JH^Gvr7Rp*n=t7frH!U;!y1gJB^i zY_M$KL_}mW&XKaDEi9K-wZR|q*L32&m+2n_8lq$xRznJ7p8}V>w+d@?uB!eS3#u<} zIaqi!b!w}a2;_BfUUhGMy#4dPx>)_>yZ`ai?Rk`}d0>~ce-PfY-b?Csd(28yX22L% zI7XI>OjIHYTk_@Xk;Gu^F52^Gn6E1&+?4MxDS2G_#PQ&yXPXP^<-p|2nLTb@AAQEY zI*UQ9Pmm{Kat}wuazpjSyXCdnrD&|C1c5DIb1TnzF}f4KIV6D)CJ!?&l&{T)e4U%3HTSYqsQ zo@zWB1o}ceQSV)<4G<)jM|@@YpL+XHuWsr5AYh^Q{K=wSV99D~4RRU52FufmMBMmd z_H}L#qe(}|I9ZyPRD6kT>Ivj&2Y?qVZq<4bG_co_DP`sE*_Xw8D;+7QR$Uq(rr+u> z8bHUWbV19i#)@@G4bCco@Xb<8u~wVDz9S`#k@ciJtlu@uP1U0X?yov8v9U3VOig2t zL9?n$P3=1U_Emi$#slR>N5wH-=J&T=EdUHA}_Z zZIl3nvMP*AZS9{cDqFanrA~S5BqxtNm9tlu;^`)3X&V4tMAkJ4gEIPl= zoV!Gyx0N{3DpD@)pv^iS*dl2FwANu;1;%EDl}JQ7MbxLMAp>)UwNwe{=V}O-5C*>F zu?Ny+F64jZn<+fKjF01}8h5H_3pey|;%bI;SFg$w8;IC<8l|3#Lz2;mNNik6sVTG3 z+Su^rIE#40C4a-587$U~%KedEEw1%r6wdvoMwpmlXH$xPnNQN#f%Z7|p)nC>WsuO= z4zyqapLS<8(UJ~Qi9d|dQijb_xhA2)v>la)<1md5s^R1N&PiuA$^k|A<+2C?OiHbj z>Bn$~t)>Y(Zb`8hW7q9xQ=s>Rv81V+UiuZJc<23HplI88isqRCId89fb`Kt|CxVIg znWcwprwXnotO>3s&Oypkte^9yJjlUVVxSe%_xlzmje|mYOVPH^vjA=?6xd0vaj0Oz zwJ4OJNiFdnHJX3rw&inskjryukl`*fRQ#SMod5J|KroJRsVXa5_$q7whSQ{gOi*s0 z1LeCy|JBWRsDPn7jCb4s(p|JZiZ8+*ExC@Vj)MF|*Vp{B(ziccSn`G1Br9bV(v!C2 z6#?eqpJBc9o@lJ#^p-`-=`4i&wFe>2)nlPK1p9yPFzJCzBQbpkcR>={YtamIw)3nt z(QEF;+)4`>8^_LU)_Q3 zC5_7lgi_6y>U%m)m@}Ku4C}=l^J=<<7c;99ec3p{aR+v=diuJR7uZi%aQv$oP?dn?@6Yu_+*^>T0ptf(oobdL;6)N-I!TO`zg^Xbv3#L0I~sn@WGk-^SmPh5>W+LB<+1PU}AKa?FCWF|qMNELOgdxR{ zbqE7@jVe+FklzdcD$!(A$&}}H*HQFTJ+AOrJYnhh}Yvta(B zQ_bW4Rr;R~&6PAKwgLWXS{Bnln(vUI+~g#kl{r+_zbngT`Y3`^Qf=!PxN4IYX#iW4 zucW7@LLJA9Zh3(rj~&SyN_pjO8H&)|(v%!BnMWySBJV=eSkB3YSTCyIeJ{i;(oc%_hk{$_l;v>nWSB)oVeg+blh=HB5JSlG_r7@P z3q;aFoZjD_qS@zygYqCn=;Zxjo!?NK!%J$ z52lOP`8G3feEj+HTp@Tnn9X~nG=;tS+z}u{mQX_J0kxtr)O30YD%oo)L@wy`jpQYM z@M>Me=95k1p*FW~rHiV1CIfVc{K8r|#Kt(ApkXKsDG$_>76UGNhHExFCw#Ky9*B-z zNq2ga*xax!HMf_|Vp-86r{;~YgQKqu7%szk8$hpvi_2I`OVbG1doP(`gn}=W<8%Gn z%81#&WjkH4GV;4u43EtSW>K_Ta3Zj!XF?;SO3V#q=<=>Tc^@?A`i;&`-cYj|;^ zEo#Jl5zSr~_V-4}y8pnufXLa80vZY4z2ko7fj>DR)#z=wWuS1$$W!L?(y}YC+yQ|G z@L&`2upy3f>~*IquAjkVNU>}c10(fq#HdbK$~Q3l6|=@-eBbo>B9(6xV`*)sae58*f zym~RRVx;xoCG3`JV`xo z!lFw)=t2Hy)e!IFs?0~7osWk(d%^wxq&>_XD4+U#y&-VF%4z?XH^i4w`TxpF{`XhZ z%G}iEzf!T(l>g;W9<~K+)$g!{UvhW{E0Lis(S^%I8OF&%kr!gJ&fMOpM=&=Aj@wuL zBX?*6i51Qb$uhkwkFYkaD_UDE+)rh1c;(&Y=B$3)J&iJfQSx!1NGgPtK!$c9OtJuu zX(pV$bfuJpRR|K(dp@^j}i&HeJOh@|7lWo8^$*o~Xqo z5Sb+!EtJ&e@6F+h&+_1ETbg7LfP5GZjvIUIN3ibCOldAv z)>YdO|NH$x7AC8dr=<2ekiY1%fN*r~e5h6Yaw<{XIErujKV~tiyrvV_DV0AzEknC- zR^xKM3i<1UkvqBj3C{wDvytOd+YtDSGu!gEMg+!&|8BQrT*|p)(dwQLEy+ zMtMzij3zo40)CA!BKZF~yWg?#lWhqD3@qR)gh~D{uZaJO;{OWV8XZ_)J@r3=)T|kt zUS1pXr6-`!Z}w2QR7nP%d?ecf90;K_7C3d!UZ`N(TZoWNN^Q~RjVhQG{Y<%E1PpV^4 z-m-K+$A~-+VDABs^Q@U*)YvhY4Znn2^w>732H?NRK(5QSS$V@D7yz2BVX4)f5A04~$WbxGOam22>t&uD)JB8-~yiQW6ik;FGblY_I>SvB_z2?PS z*Qm&qbKI{H1V@YGWzpx`!v)WeLT02};JJo*#f$a*FH?IIad-^(;9XC#YTWN6;Z6+S zm4O1KH=#V@FJw7Pha0!9Vb%ZIM$)a`VRMoiN&C|$YA3~ZC*8ayZRY^fyuP6$n%2IU z$#XceYZeqLTXw(m$_z|33I$B4k~NZO>pP6)H_}R{E$i%USGy{l{-jOE;%CloYPEU+ zRFxOn4;7lIOh!7abb23YKD+_-?O z0FP9otcAh+oSj;=f#$&*ExUHpd&e#bSF%#8*&ItcL2H$Sa)?pt0Xtf+t)z$_u^wZi z44oE}r4kIZGy3!Mc8q$B&6JqtnHZ>Znn!Zh@6rgIu|yU+zG8q`q9%B18|T|oN3zMq z`l&D;U!OL~%>vo&q0>Y==~zLiCZk4v%s_7!9DxQ~id1LLE93gf*gg&2$|hB#j8;?3 z5v4S;oM6rT{Y;I+#FdmNw z){d%tNM<<#GN%n9ox7B=3#;u7unZ~tLB_vRZ52a&2=IM)2VkXm=L+Iqq~uk#Dug|x z>S84e+A7EiOY5lj*!q?6HDkNh~0g;0Jy(al!ZHHDtur9T$y-~)94HelX1NHjXWIM7UAe}$?jiz z9?P4`I0JM=G5K{3_%2jPLC^_Mlw?-kYYgb7`qGa3@dn|^1fRMwiyM@Ch z;CB&o7&&?c5e>h`IM;Wnha0QKnEp=$hA8TJgR-07N~U5(>9vJzeoFsSRBkDq=x(YgEMpb=l4TDD`2 zwVJpWGTA_u7}?ecW7s6%rUs&NXD3+n;jB86`X?8(l3MBo6)PdakI6V6a}22{)8ilT zM~T*mU}__xSy|6XSrJ^%lDAR3Lft%+yxC|ZUvSO_nqMX!_ul3;R#*{~4DA=h$bP)%8Yv9X zyp><|e8=_ttI}ZAwOd#dlnSjck#6%273{E$kJuCGu=I@O)&6ID{nWF5@gLb16sj|&Sb~+du4e4O_%_o`Ix4NRrAsyr1_}MuP94s>de8cH-OUkVPk3+K z&jW)It9QiU-ti~AuJkL`XMca8Oh4$SyJ=`-5WU<{cIh+XVH#e4d&zive_UHC!pN>W z3TB;Mn5i)9Qn)#6@lo4QpI3jFYc0~+jS)4AFz8fVC;lD^+idw^S~Qhq>Tg(!3$yLD zzktzoFrU@6s4wwCMz}edpF5i5Q1IMmEJQHzp(LAt)pgN3&O!&d?3W@6U4)I^2V{;- z6A(?zd93hS*uQmnh4T)nHnE{wVhh(=MMD(h(P4+^p83Om6t<*cUW>l(qJzr%5vp@K zN27ka(L{JX=1~e2^)F^i=TYj&;<7jyUUR2Bek^A8+3Up*&Xwc{)1nRR5CT8vG>ExV zHnF3UqXJOAno_?bnhCX-&kwI~Ti8t4`n0%Up>!U`ZvK^w2+0Cs-b9%w%4`$+To|k= zKtgc&l}P`*8IS>8DOe?EB84^kx4BQp3<7P{Pq}&p%xF_81pg!l2|u=&I{AuUgmF5n zJQCTLv}%}xbFGYtKfbba{CBo)lWW%Z>i(_NvLhoQZ*5-@2l&x>e+I~0Nld3UI9tdL zRzu8}i;X!h8LHVvN?C+|M81e>Jr38%&*9LYQec9Ax>?NN+9(_>XSRv&6hlCYB`>Qm z1&ygi{Y()OU4@D_jd_-7vDILR{>o|7-k)Sjdxkjgvi{@S>6GqiF|o`*Otr;P)kLHN zZkpts;0zw_6;?f(@4S1FN=m!4^mv~W+lJA`&7RH%2$)49z0A+8@0BCHtj|yH--AEL z0tW6G%X-+J+5a{5*WKaM0QDznf;V?L5&uQw+yegDNDP`hA;0XPYc6e0;Xv6|i|^F2WB)Z$LR|HR4 zTQsRAby9(^Z@yATyOgcfQw7cKyr^3Tz7lc7+JEwwzA7)|2x+PtEb>nD(tpxJQm)Kn zW9K_*r!L%~N*vS8<5T=iv|o!zTe9k_2jC_j*7ik^M_ zaf%k{WX{-;0*`t`G!&`eW;gChVXnJ-Rn)To8vW-?>>a%QU1v`ZC=U)f8iA@%JG0mZ zDqH;~mgBnrCP~1II<=V9;EBL)J+xzCoiRBaeH&J6rL!{4zIY8tZka?_FBeQeNO3q6 zyG_alW54Ba&wQf{&F1v-r1R6ID)PTsqjIBc+5MHkcW5Fnvi~{-FjKe)t1bl}Y;z@< z=!%zvpRua>>t_x}^}z0<7MI!H2v6|XAyR9!t50q-A)xk0nflgF4*OQlCGK==4S|wc zRMsSscNhRzHMBU8TdcHN!q^I}x0iXJ%uehac|Zs_B$p@CnF)HeXPpB_Za}F{<@6-4 zl%kml@}kHQ(ypD8FsPJ2=14xXJE|b20RUIgs!2|R3>LUMGF6X*B_I|$`Qg=;zm7C z{mEDy9dTmPbued7mlO@phdmAmJ7p@GR1bjCkMw6*G7#4+`k>fk1czdJUB!e@Q(~6# zwo%@p@V5RL0ABU2LH7Asq^quDUho@H>eTZH9f*no9fY0T zD_-9px3e}A!>>kv5wk91%C9R1J_Nh!*&Kk$J3KNxC}c_@zlgpJZ+5L)Nw|^p=2ue}CJtm;uj*Iqr)K})kA$xtNUEvX;4!Px*^&9T_`IN{D z{6~QY=Nau6EzpvufB^hflc#XIsSq0Y9(nf$d~6ZwK}fal92)fr%T3=q{0mP-EyP_G z)UR5h@IX}3Qll2b0oCAcBF>b*@Etu*aTLPU<%C>KoOrk=x?pN!#f_Og-w+;xbFgjQ zXp`et%lDBBh~OcFnMKMUoox0YwBNy`N0q~bSPh@+enQ=4RUw1) zpovN`QoV>vZ#5LvC;cl|6jPr}O5tu!Ipoyib8iXqy}TeJ;4+_7r<1kV0v5?Kv>fYp zg>9L`;XwXa&W7-jf|9~uP2iyF5`5AJ`Q~p4eBU$MCC00`rcSF>`&0fbd^_eqR+}mK z4n*PMMa&FOcc)vTUR zlDUAn-mh`ahi_`f`=39JYTNVjsTa_Y3b1GOIi)6dY)D}xeshB0T8Eov5%UhWd1)u}kjEQ|LDo{tqKKrYIfVz~@dp!! zMOnah@vp)%_-jDTUG09l+;{CkDCH|Q{NqX*uHa1YxFShy*1+;J`gywKaz|2Q{lG8x zP?KBur`}r`!WLKXY_K;C8$EWG>jY3UIh{+BLv0=2)KH%P}6xE2kg)%(-uA6lC?u8}{K(#P*c zE9C8t*u%j2r_{;Rpe1A{9nNXU;b_N0vNgyK!EZVut~}+R2rcbsHilqsOviYh-pYX= zHw@53nlmwYI5W5KP>&`dBZe0Jn?nAdC^HY1wlR6$u^PbpB#AS&5L6zqrXN&7*N2Q` z+Rae1EwS)H=aVSIkr8Ek^1jy2iS2o7mqm~Mr&g5=jjt7VxwglQ^`h#Mx+x2v|9ZAwE$i_9918MjJxTMr?n!bZ6n$}y11u8I9COTU`Z$Fi z!AeAQLMw^gp_{+0QTEJrhL424pVDp%wpku~XRlD3iv{vQ!lAf!_jyqd_h}+Tr1XG| z`*FT*NbPqvHCUsYAkFnM`@l4u_QH&bszpUK#M~XLJt{%?00GXY?u_{gj3Hvs!=N(I z(=AuWPijyoU!r?aFTsa8pLB&cx}$*%;K$e*XqF{~*rA-qn)h^!(-;e}O#B$|S~c+U zN4vyOK0vmtx$5K!?g*+J@G1NmlEI=pyZXZ69tAv=@`t%ag_Hk{LP~OH9iE)I= zaJ69b4kuCkV0V zo(M0#>phpQ_)@j;h%m{-a*LGi(72TP)ws2w*@4|C-3+;=5DmC4s7Lp95%n%@Ko zfdr3-a7m*dys9iIci$A=4NPJ`HfJ;hujLgU)ZRuJI`n;Pw|yksu!#LQnJ#dJysgNb z@@qwR^wrk(jbq4H?d!lNyy72~Dnn87KxsgQ!)|*m(DRM+eC$wh7KnS-mho3|KE)7h zK3k;qZ;K1Lj6uEXLYUYi)1FN}F@-xJ z@@3Hb84sl|j{4$3J}aTY@cbX@pzB_qM~APljrjju6P0tY{C@ zpUCOz_NFmALMv1*blCcwUD3?U6tYs+N%cmJ98D%3)%)Xu^uvzF zS5O!sc#X6?EwsYkvPo6A%O8&y8sCCQH<%f2togVwW&{M;PR!a(ZT_A+jVAbf{@5kL zB@Z(hb$3U{T_}SKA_CoQVU-;j>2J=L#lZ~aQCFg-d<9rzs$_gO&d5N6eFSc z1ml8)P*FSi+k@!^M9nDWR5e@ATD8oxtDu=36Iv2!;dZzidIS(PCtEuXAtlBb1;H%Z zwnC^Ek*D)EX4#Q>R$$WA2sxC_t(!!6Tr?C#@{3}n{<^o;9id1RA&-Pig1e-2B1XpG zliNjgmd3c&%A}s>qf{_j#!Z`fu0xIwm4L0)OF=u(OEmp;bLCIaZX$&J_^Z%4Sq4GZ zPn6sV_#+6pJmDN_lx@1;Zw6Md_p0w9h6mHtzpuIEwNn>OnuRSC2=>fP^Hqgc)xu^4 z<3!s`cORHJh#?!nKI`Et7{3C27+EuH)Gw1f)aoP|B3y?fuVfvpYYmmukx0ya-)TQX zR{ggy5cNf4X|g)nl#jC9p>7|09_S7>1D2GTRBUTW zAkQ=JMRogZqG#v;^=11O6@rPPwvJkr{bW-Qg8`q8GoD#K`&Y+S#%&B>SGRL>;ZunM@49!}Uy zN|bBCJ%sO;@3wl0>0gbl3L@1^O60ONObz8ZI7nder>(udj-jt`;yj^nTQ$L9`OU9W zX4alF#$|GiR47%x@s&LV>2Sz2R6?;2R~5k6V>)nz!o_*1Y!$p>BC5&?hJg_MiE6UBy>RkVZj`9UWbRkN-Hk!S`=BS3t3uyX6)7SF#)71*}`~Ogz z1rap5H6~dhBJ83;q-Y<5V35C2&F^JI-it(=5D#v!fAi9p#UwV~2tZQI+W(Dv?1t9? zfh*xpxxO{-(VGB>!Q&0%^YW_F!@aZS#ucP|YaD#>wd1Fv&Z*SR&mc;asi}1G) z_H>`!akh-Zxq9#io(7%;a$)w+{QH)Y$?UK1Dt^4)up!Szcxnu}kn$0afcfJL#IL+S z5gF_Y30j;{lNrG6m~$Ay?)*V9fZuU@3=kd40=LhazjFrau>(Y>SJNtOz>8x_X-BlA zIpl{i>OarVGj1v(4?^1`R}aQB&WCRQzS~;7R{tDZG=HhgrW@B`W|#cdyj%YBky)P= zpxuOZkW>S6%q7U{VsB#G(^FMsH5QuGXhb(sY+!-R8Bmv6Sx3WzSW<1MPPN1!&PurYky(@`bP9tz z52}LH9Q?+FF5jR6-;|+GVdRA!qtd;}*-h&iIw3Tq3qF9sDIb1FFxGbo&fbG5n8$3F zyY&PWL{ys^dTO}oZ#@sIX^BKW*bon=;te9j5k+T%wJ zNJtoN1~YVj4~YRrlZl)b&kJqp+Z`DqT!la$x&&IxgOQw#yZd-nBP3!7FijBXD|IsU8Zl^ zc6?MKpJQ+7ka|tZQLfchD$PD|;K(9FiLE|eUZX#EZxhG!S-63C$jWX1Yd!6-Yxi-u zjULIr|0-Q%D9jz}IF~S%>0(jOqZ(Ln<$9PxiySr&2Oic7vb<8q=46)Ln%Z|<*z5&> z3f~Zw@m;vR(bESB<=Jqkxn(=#hQw42l(7)h`vMQQTttz9XW6^|^8EK7qhju4r_c*b zJIi`)MB$w@9epwdIfnEBR+?~);yd6C(LeMC& zn&&N*?-g&BBJcV;8&UoZi4Lmxcj16ojlxR~zMrf=O_^i1wGb9X-0@6_rpjPYemIin zmJb+;lHe;Yp=8G)Q(L1bzH*}I>}uAqhj4;g)PlvD9_e_ScR{Ipq|$8NvAvLD8MYr}xl=bU~)f%B3E>r3Bu9_t|ThF3C5~BdOve zEbk^r&r#PT&?^V1cb{72yEWH}TXEE}w>t!cY~rA+hNOTK8FAtIEoszp!qqptS&;r$ zaYV-NX96-h$6aR@1xz6_E0^N49mU)-v#bwtGJm)ibygzJ8!7|WIrcb`$XH~^!a#s& z{Db-0IOTFq#9!^j!n_F}#Z_nX{YzBK8XLPVmc&X`fT7!@$U-@2KM9soGbmOSAmqV z{nr$L^MBo_u^Joyf0E^=eo{Rt0{{e$IFA(#*kP@SQd6lWT2-#>` zP1)7_@IO!9lk>Zt?#CU?cuhiLF&)+XEM9B)cS(gvQT!X3`wL*{fArTS;Ak`J<84du zALKPz4}3nlG8Fo^MH0L|oK2-4xIY!~Oux~1sw!+It)&D3p;+N8AgqKI`ld6v71wy8I!eP0o~=RVcFQR2Gr(eP_JbSytoQ$Yt}l*4r@A8Me94y z8cTDWhqlq^qoAhbOzGBXv^Wa4vUz$(7B!mX`T=x_ueKRRDfg&Uc-e1+z4x$jyW_Pm zp?U;-R#xt^Z8Ev~`m`iL4*c#65Nn)q#=Y0l1AuD&+{|8-Gsij3LUZXpM0Bx0u7WWm zH|%yE@-#XEph2}-$-thl+S;__ciBxSSzHveP%~v}5I%u!z_l_KoW{KRx2=eB33umE zIYFtu^5=wGU`Jab8#}cnYry@9p5UE#U|VVvx_4l49JQ;jQdp(uw=$^A$EA$LM%vmE zvdEOaIcp5qX8wX{mYf0;#51~imYYPn4=k&#DsKTxo{_Mg*;S495?OBY?#gv=edYC* z^O@-sd-qa+U24xvcbL0@C7_6o!$`)sVr-jSJE4XQUQ$?L7}2(}Eixqv;L8AdJAVqc zq}RPgpnDb@E_;?6K58r3h4-!4rT4Ab#rLHLX?eMOfluJk=3i1@Gt1i#iA=O`M0@x! z(HtJP9BMHXEzuD93m|B&woj0g6T?f#^)>J>|I4C5?Gam>n9!8CT%~aT;=oco5d6U8 zMXl(=W;$ND_8+DD*?|5bJ!;8ebESXMUKBAf7YBwNVJibGaJ*(2G`F%wx)grqVPjudiaq^Kl&g$8A2 zWMxMr@_$c}d+;_B`#kUX-t|4VKH&_f^^EP0&=DPLW)H)UzBG%%Tra*5 z%$kyZe3I&S#gfie^z5)!twG={3Cuh)FdeA!Kj<-9** zvT*5%Tb`|QbE!iW-XcOuy39>D3oe6x{>&<#E$o8Ac|j)wq#kQzz|ATd=Z0K!p2$QE zPu?jL8Lb^y3_CQE{*}sTDe!2!dtlFjq&YLY@2#4>XS`}v#PLrpvc4*@q^O{mmnr5D zmyJq~t?8>FWU5vZdE(%4cuZuao0GNjp3~Dt*SLaxI#g_u>hu@k&9Ho*#CZP~lFJHj z(e!SYlLigyc?&5-YxlE{uuk$9b&l6d`uIlpg_z15dPo*iU&|Khx2*A5Fp;8iK_bdP z?T6|^7@lcx2j0T@x>X7|kuuBSB7<^zeY~R~4McconTxA2flHC0_jFxmSTv-~?zVT| zG_|yDqa9lkF*B6_{j=T>=M8r<0s;@z#h)3BQ4NLl@`Xr__o7;~M&dL3J8fP&zLfDfy z);ckcTev{@OUlZ`bCo(-3? z1u1xD`PKgSg?RqeVVsF<1SLF;XYA@Bsa&cY!I48ZJn1V<3d!?s=St?TLo zC0cNr`qD*M#s6f~X>SCNVkva^9A2ZP>CoJ9bvgXe_c}WdX-)pHM5m7O zrHt#g$F0AO+nGA;7dSJ?)|Mo~cf{z2L)Rz!`fpi73Zv)H=a5K)*$5sf_IZypi($P5 zsPwUc4~P-J1@^3C6-r9{V-u0Z&Sl7vNfmuMY4yy*cL>_)BmQF!8Om9Dej%cHxbIzA zhtV0d{=%cr?;bpBPjt@4w=#<>k5ee=TiWAXM2~tUGfm z$s&!Dm0R^V$}fOR*B^kGaipi~rx~A2cS0;t&khV1a4u38*XRUP~f za!rZMtay8bsLt6yFYl@>-y^31(*P!L^^s@mslZy(SMsv9bVoX`O#yBgEcjCmGpyc* zeH$Dw6vB5P*;jor+JOX@;6K#+xc)Z9B8M=x2a@Wx-{snPGpRmOC$zpsqW*JCh@M2Y z#K+M(>=#d^>Of9C`))h<=Bsy)6zaMJ&x-t%&+UcpLjV`jo4R2025 zXaG8EA!0lQa)|dx-@{O)qP6`$rhCkoQqZ`^SW8g-kOwrwsK8 z3ms*AIcyj}-1x&A&vSq{r=QMyp3CHdWH35!sad#!Sm>^|-|afB+Q;|Iq@LFgqIp#Z zD1%H+3I?6RGnk&IFo|u+E0dCxXz4yI^1i!QTu7uvIEH>i3rR{srcST`LIRwdV1P;W z+%AN1NIf@xxvVLiSX`8ILA8MzNqE&7>%jMzGt9wm78bo9<;h*W84i29^w!>V>{N+S zd`5Zmz^G;f=icvoOZfK5#1ctx*~UwD=ab4DGQXehQ!XYnak*dee%YN$_ZPL%KZuz$ zD;$PpT;HM^$KwtQm@7uvT`i6>Hae1CoRVM2)NL<2-k2PiX=eAx+-6j#JI?M}(tuBW zkF%jjLR)O`gI2fcPBxF^HeI|DWwQWHVR!;;{BXXHskxh8F@BMDn`oEi-NHt;CLymW z=KSv5)3dyzec0T5B*`g-MQ<;gz=nIWKUi9ko<|4I(-E0k$QncH>E4l z**1w&#={&zv4Tvhgz#c29`m|;lU-jmaXFMC11 z*dlXDMEOG>VoLMc>!rApwOu2prKSi*!w%`yzGmS+k(zm*CsLK*wv{S_0WX^8A-rKy zbk^Gf_92^7iB_uUF)EE+ET4d|X|>d&mdN?x@vxKAQk`O+r4Qdu>XGy(a(19g;=jU} zFX{O*_NG>!$@jh!U369Lnc+D~qch3uT+_Amyi}*k#LAAwh}k8IPK5a-WZ81ufD>l> z$4cF}GSz>ce`3FAic}6W4Z7m9KGO?(eWqi@L|5Hq0@L|&2flN1PVl}XgQ2q*_n2s3 zt5KtowNkTYB5b;SVuoXA@i5irXO)A&%7?V`1@HGCB&)Wgk+l|^XXChq;u(nyPB}b3 zY>m5jkxpZgi)zfbgv&ec4Zqdvm+D<?Im*mXweS9H+V>)zF#Zp3)bhl$PbISY{5=_z!8&*Jv~NYtI-g!>fDs zmvL5O^U%!^VaKA9gvKw|5?-jk>~%CVGvctKmP$kpnpfN{D8@X*Aazi$txfa%vd-|E z>kYmV66W!lNekJPom29LdZ%(I+ZLZYTXzTg*to~m?7vp%{V<~>H+2}PQ?PPAq`36R z<%wR8v6UkS>Wt#hzGk#44W<%9S=nBfB);6clKwnxY}T*w21Qc3_?IJ@4gYzC7s;WP zVQNI(M=S=JT#xsZy7G`cR(BP9*je0bfeN8JN5~zY(DDs0t{LpHOIbN);?T-69Pf3R zSNe*&p2%AwXHL>__g+xd4Hlc_vu<25H?(`nafS%)3UPP7_4;gk-9ckt8SJRTv5v0M z_Hww`qPudL?ajIR&X*;$y-`<)6dxx1U~5eGS13CB!lX;3w7n&lDDiArbAhSycd}+b zya_3p@A`$kQy;|NJZ~s44Hqo7Hwt}X86NK=(ey>lgWTtGL6k@Gy;PbO!M%1~Wcn2k zUFP|*5d>t-X*RU8g%>|(wwj*~#l4z^Aatf^DWd1Wj#Q*AY0D^V@sC`M zjJc6qXu0I7Y*2;;gGu!plAFzG=J;1%eIOdn zQA>J&e05UN*7I5@yRhK|lbBSfJ+5Uq;!&HV@xfPZrgD}kE*1DSq^=%{o%|LChhl#0 zlMb<^a6ixzpd{kNZr|3jTGeEzuo}-eLT-)Q$#b{!vKx8Tg}swCni>{#%vDY$Ww$84 zew3c9BBovqb}_&BRo#^!G(1Eg((BScRZ}C)Oz?y`T5wOrv);)b^4XR8 zhJo7+<^7)qB>I;46!GySzdneZ>n_E1oWZY;kf94#)s)kWjuJN1c+wbVoNQcmnv}{> zN0pF+Sl3E}UQ$}slSZeLJrwT>Sr}#V(dVaezCQl2|4LN`7L7v&siYR|r7M(*JYfR$ zst3=YaDw$FSc{g}KHO&QiKxuhEzF{f%RJLKe3p*7=oo`WNP)M(9X1zIQPP0XHhY3c znrP{$4#Ol$A0s|4S7Gx2L23dv*Gv2o;h((XVn+9+$qvm}s%zi6nI-_s6?mG! zj{DV;qesJb&owKeEK?=J>UcAlYckA7Sl+I&IN=yasrZOkejir*kE@SN`fk<8Fgx*$ zy&fE6?}G)d_N`){P~U@1jRVA|2*69)KSe_}!~?+`Yb{Y=O~_+@!j<&oVQQMnhoIRU zA0CyF1OFfkK44n*JD~!2!SCPM;PRSk%1XL=0&rz00wxPs&-_eapJy#$h!eqY%nS0{ z!aGg58JIJPF3_ci%n)QSVpa2H`vIe$RD43;#IRfDV&Ibit z+?>HW4{2wOfC6Fw)}4x}i1maDxcE1qi@BS*qcxD2gE@h3#4cgU*D-&3z7D|tVZWt= z-Cy2+*Cm@P4GN_TPUtaVyVesbVDazF@)j8VJ4>XZv!f%}&eO1SvIgr}4`A*3#vat< z_MoByL(qW6L7SFZ#|Gc1fFN)L2PxY+{B8tJp+pxRyz*87)vXR}*=&ahXjBlQKguuf zX6x<<6fQulE^C*KH8~W%ptpaC0l?b=_{~*U4?5Vt;dgM4t_{&UZ1C2j?b>b+5}{IF_CUyvz-@QZPMlJ)r_tS$9kH%RPv#2_nMb zRLj5;chJ72*U`Z@Dqt4$@_+k$%|8m(HqLG!qT4P^DdfvGf&){gKnGCX#H0!;W=AGP zbA&Z`-__a)VTS}kKFjWGk z%|>yE?t*EJ!qeQ%dPk$;xIQ+P0;()PCBDgjJm6Buj{f^awNoVx+9<|lg3%-$G(*f) zll6oOkN|yamn1uyl2*N-lnqRI1cvs_JxLTeahEK=THV$Sz*gQhKNb*p0fNoda#-&F zB-qJgW^g}!TtM|0bS2QZekW7_tKu%GcJ!4?lObt0z_$mZ4rbQ0o=^curCs3bJK6sq z9fu-aW-l#>z~ca(B;4yv;2RZ?tGYAU)^)Kz{L|4oPj zdOf_?de|#yS)p2v8-N||+XL=O*%3+y)oI(HbM)Ds?q8~HPzIP(vs*G`iddbWq}! z(2!VjP&{Z1w+%eUq^ Date: Sat, 25 Mar 2023 20:52:38 +0300 Subject: [PATCH 011/164] docs: add license --- LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file From 157767213cfe2d6beacf9f9061a85209113f3dd0 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 21:15:00 +0300 Subject: [PATCH 012/164] refactor: add empty line in the end of src files --- src/main/kotlin/AVLTree.kt | 2 +- src/main/kotlin/RBTree.kt | 2 +- src/main/kotlin/abstractBalanceTree.kt | 2 +- src/main/kotlin/abstractBinTree.kt | 1 - src/test/kotlin/BSTreeTest.kt | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index dd071ae..35225aa 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -38,4 +38,4 @@ class AVLTree, Value> : BalanceTree() { override fun rebalancing() { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 6d16803..0f8472f 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -32,4 +32,4 @@ class RBTree, Value> : BalanceTree() { override fun remove(key: Key) { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 0ac1447..6a82f73 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -2,4 +2,4 @@ abstract class BalanceTree, Value>: BinTree() protected abstract fun rebalancing() protected abstract fun leftRotation() protected abstract fun rightRotation() -} \ No newline at end of file +} diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 4139498..1de1974 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -75,4 +75,3 @@ abstract class BinTree, Value> { return getNode(key)?.value } } - diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index ffdcc27..e394342 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -10,4 +10,4 @@ class BSTreeTest { tree.insert(4, "4k") Assertions.assertEquals(expected, tree.get(4)) } -} \ No newline at end of file +} From a73c384be32dc88116ca9395de72e61404119abb Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 25 Mar 2023 21:21:58 +0300 Subject: [PATCH 013/164] refactor: auto-formatting --- src/main/kotlin/AVLTree.kt | 2 +- src/main/kotlin/BSTree.kt | 16 +++++++++------- src/main/kotlin/RBTree.kt | 2 +- src/main/kotlin/abstractBalanceTree.kt | 2 +- src/main/kotlin/abstractBinTree.kt | 9 ++++++--- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 35225aa..fb8593b 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -24,7 +24,7 @@ class AVLTree, Value> : BalanceTree() { } override fun remove(key: Key) { - TODO("jfaj;l") + TODO("Not yet implemented") } override fun leftRotation() { diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index f5c1b8f..d2ebcfb 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -1,7 +1,8 @@ -open class BSTree, Value>: BinTree { - constructor(): super() - constructor(key: Key, value: Value): super(key, value) - constructor(vararg pairs: Pair): super(pairs) +open class BSTree, Value> : BinTree { + constructor() : super() + constructor(key: Key, value: Value) : super(key, value) + constructor(vararg pairs: Pair) : super(pairs) + open override fun insert(key: Key, value: Value) { if (rootNode == null) rootNode = Node(key, value) @@ -23,6 +24,7 @@ open class BSTree, Value>: BinTree { open override fun remove(key: Key) { removeNode(getNode(key)) } + protected open fun removeNode(node: Node?) { if ((node!!.left == null) && (node.right == null)) { if (node.parent == null) @@ -31,15 +33,15 @@ open class BSTree, Value>: BinTree { (node.parent)!!.left == null else (node.parent)!!.right == null - } - else if (node.left == null) + } else if (node.left == null) transplant(node, (node.right)!!) else if (node.right == null) transplant(node, (node.left)!!) - else{ + else { TODO("Юля когда-нибудь доделает") } } + protected open fun transplant(node1: Node, node2: Node) { if (node1.parent == null) rootNode = node2 diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 0f8472f..c7e07f9 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -2,7 +2,7 @@ class RBTree, Value> : BalanceTree() { protected class RBNode, Value>( key: Key, value: Value, - parent: RBNode?= null, + parent: RBNode? = null, left: RBNode? = null, right: RBNode? = null, var Black: Boolean = false diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 6a82f73..9a95997 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,4 +1,4 @@ -abstract class BalanceTree, Value>: BinTree() { +abstract class BalanceTree, Value> : BinTree() { protected abstract fun rebalancing() protected abstract fun leftRotation() protected abstract fun rightRotation() diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 1de1974..d949b3a 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,4 +1,5 @@ import kotlin.math.abs + abstract class BinTree, Value> { protected open class Node, Value>( val key: Key, @@ -16,20 +17,23 @@ abstract class BinTree, Value> { return this.compareTo(other) == 0 } } + protected open var rootNode: Node? = null constructor() constructor(key: Key, value: Value) { insert(key, value) } + private fun sortInsert(array: Array>) { val serArray = array.sortedBy { it.first }.toTypedArray() var indices = serArray.indices.toList() - indices = indices.sortedBy{ abs(serArray.size/2 - it) } + indices = indices.sortedBy { abs(serArray.size / 2 - it) } for (i in indices) { insert(serArray[i].first, serArray[i].second) } } + constructor(array: Array>) { sortInsert(array) } @@ -38,8 +42,7 @@ abstract class BinTree, Value> { abstract fun remove(key: Key) - - protected fun getParent(key: Key) : Node? { + protected fun getParent(key: Key): Node? { tailrec fun recFind(curNode: Node?): Node? { return if (curNode == null) null From 4f6e5798e6ad3458ba24e0b191cc6ed933c9c544 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sun, 26 Mar 2023 23:53:03 +0300 Subject: [PATCH 014/164] feat: add fun remove for BSTree --- src/main/kotlin/BSTree.kt | 59 ++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index d2ebcfb..f9680ca 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -22,23 +22,34 @@ open class BSTree, Value> : BinTree { } open override fun remove(key: Key) { - removeNode(getNode(key)) + val node = getNode(key) + if (node != null) + removeNode(node) } - - protected open fun removeNode(node: Node?) { - if ((node!!.left == null) && (node.right == null)) { + protected open fun removeNode(node: Node) { + if ((node.left == null) && (node.right == null)) { if (node.parent == null) - rootNode == null - else if (node == (node.parent)!!.left) - (node.parent)!!.left == null + rootNode = null + else if (node == node.parent!!.left) + node.parent!!.left = null else - (node.parent)!!.right == null - } else if (node.left == null) - transplant(node, (node.right)!!) + node.parent!!.right = null + } + else if (node.left == null) + transplant(node, node.right!!) else if (node.right == null) - transplant(node, (node.left)!!) - else { - TODO("Юля когда-нибудь доделает") + transplant(node, node.left!!) + else{ + val nextNode = nextElement(node) + if (nextNode != null) { + if (nextNode.right != null) + transplant(nextNode, nextNode.right!!) + nextNode.right = node.right + nextNode.left = node.left + nextNode.right!!.parent = nextNode + nextNode.left!!.parent = nextNode + transplant(node, nextNode) + } } } @@ -52,4 +63,26 @@ open class BSTree, Value> : BinTree { } node2.parent = node1.parent } + + protected open fun nextElement(node: Node): Node?{ + if (node.right == null) + return null + return minElement(node.right!!) + } + + protected fun minElement(node: Node): Node { + var node1: Node = node + while (node1.left != null) { + node1 = node1.left!! + } + return node1 + } + + protected fun maxElement(node: Node): Node { + var node1: Node = node + while (node1.right != null) { + node1 = node1.right!! + } + return node1 + } } From 734817e3a571ddce31e9c341b190c1c72011154b Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 27 Mar 2023 19:41:02 +0300 Subject: [PATCH 015/164] feat: breadthFirstSearch in BinTree --- src/main/kotlin/abstractBinTree.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index d949b3a..2517e9d 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -41,6 +41,18 @@ abstract class BinTree, Value> { abstract fun insert(key: Key, value: Value) abstract fun remove(key: Key) + protected fun breadthFirstSearch(function: (node: Node?) -> Unit) { + var list = mutableListOf(rootNode) + while (list.isNotEmpty()) { + val node = list.last() + list.removeLast() + function(node) + if (node != null) { + list.add(0, node.left) + list.add(0, node.right) + } + } + } protected fun getParent(key: Key): Node? { tailrec fun recFind(curNode: Node?): Node? { From 526637d8fbe3ec668779abc0fe9ba8d144d4c9b1 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 28 Mar 2023 02:47:02 +0300 Subject: [PATCH 016/164] feat: Debug and add null padding in BFS --- src/main/kotlin/abstractBinTree.kt | 61 ++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 2517e9d..2681c7d 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -18,6 +18,7 @@ abstract class BinTree, Value> { } } + protected open var rootNode: Node? = null constructor() @@ -41,18 +42,6 @@ abstract class BinTree, Value> { abstract fun insert(key: Key, value: Value) abstract fun remove(key: Key) - protected fun breadthFirstSearch(function: (node: Node?) -> Unit) { - var list = mutableListOf(rootNode) - while (list.isNotEmpty()) { - val node = list.last() - list.removeLast() - function(node) - if (node != null) { - list.add(0, node.left) - list.add(0, node.right) - } - } - } protected fun getParent(key: Key): Node? { tailrec fun recFind(curNode: Node?): Node? { @@ -89,4 +78,52 @@ abstract class BinTree, Value> { open fun get(key: Key): Value? { return getNode(key)?.value } + + protected fun breadthFirstSearch(function: (Node?) -> Unit, addNullNodes: Boolean) { + val queue = mutableListOf(rootNode) + + fun notNullInQueue(): Boolean { + for (i in queue) + if (i != null) + return true + return false + } + + while (queue.isNotEmpty()) { + val node = queue.last() + queue.removeLast() + function(node) + if (node != null) { + queue.add(0, node.left) + queue.add(0, node.right) + } else if (addNullNodes) { + queue.add(0, null) + queue.add(0, null) + } + if (!notNullInQueue()) + return + } + } + + open inner class Debug { + fun treeKeysInString(): String { + var sizeOfLevel = 1 + var elemInTheLevel = 0 + var string = "" + + fun function(node: Node?) { + string += node?.key ?: "-" + string += " " + elemInTheLevel += 1 + if (elemInTheLevel == sizeOfLevel) { + sizeOfLevel *= 2 + elemInTheLevel = 0 + string += "\n" + } + } + + breadthFirstSearch(::function, true) + return string + } + } } From cfdf0fa4317925cdeefeab1c59371c4cb94391fc Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 28 Mar 2023 02:53:12 +0300 Subject: [PATCH 017/164] feat: tests are allocated a separate class --- src/test/kotlin/BSTreeTest.kt | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index e394342..700ff72 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -1,13 +1,26 @@ -import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class BSTreeTest { - @Test - fun addFirstNodeCorrectTest() { - val tree = BSTree() - val expected = "4k" - tree.insert(4, "4k") - Assertions.assertEquals(expected, tree.get(4)) + @Nested + inner class `Test add` { + fun generateTreeWithAdd(vararg arr: Int): BinTree { + val tree = BSTree() + for (i in arr) + tree.insert(i, "${i}k") + return tree + } + + @Test + fun `add one node test`() { + val expected = "4k" + assertEquals(expected, generateTreeWithAdd(4).get(4)) + } + + @Test + fun `add three nodes test`() { + assertEquals("2 \n1 3 \n", generateTreeWithAdd(2, 1, 3).Debug().treeKeysInString()) + } } } From c3524161d15c00550fc669d9923824cdde38f6fa Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 28 Mar 2023 03:20:50 +0300 Subject: [PATCH 018/164] feat: add tests (insert tests) --- src/test/kotlin/BSTreeTest.kt | 42 ++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index 700ff72..845c566 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -4,8 +4,8 @@ import org.junit.jupiter.api.Test class BSTreeTest { @Nested - inner class `Test add` { - fun generateTreeWithAdd(vararg arr: Int): BinTree { + inner class `Test insert` { + fun generateTreeWithInsert(vararg arr: Int): BinTree { val tree = BSTree() for (i in arr) tree.insert(i, "${i}k") @@ -13,14 +13,44 @@ class BSTreeTest { } @Test - fun `add one node test`() { + fun `insert one node test`() { val expected = "4k" - assertEquals(expected, generateTreeWithAdd(4).get(4)) + assertEquals(expected, generateTreeWithInsert(4).get(4)) } @Test - fun `add three nodes test`() { - assertEquals("2 \n1 3 \n", generateTreeWithAdd(2, 1, 3).Debug().treeKeysInString()) + fun `insert three nodes test`() { + assertEquals("2 \n1 3 \n", generateTreeWithInsert(2, 1, 3).Debug().treeKeysInString()) + } + + @Test + fun `degenerate tree`() { + assertEquals("1 \n- 2 \n- - - 3 \n", generateTreeWithInsert(1, 2, 3).Debug().treeKeysInString()) + } + + @Nested + inner class `Equal key` { + @Test + fun `two inserts of the first node`() { + val tree = generateTreeWithInsert(4) + tree.insert(4, "5k") + assertEquals("5k", tree.get(4)) + } + + @Test + fun `two inserts of the first node in non-degenerate tree`() { + val tree = generateTreeWithInsert(4, 1, 5, 6) + tree.insert(4, "5k") + assertEquals("5k", tree.get(4)) + } + + @Test + fun `two inserts of node`() { + val tree = generateTreeWithInsert(5, 6, 4) + tree.insert(4, "5k") + assertEquals("5 \n4 6 \n", tree.Debug().treeKeysInString()) + assertEquals("5k", tree.get(4)) + } } } } From 54bde61f486b7998f8c9f1409e4f29632f909232 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 28 Mar 2023 03:33:15 +0300 Subject: [PATCH 019/164] fix: behavior when adding one key --- src/main/kotlin/BSTree.kt | 1 + src/main/kotlin/abstractBinTree.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index f9680ca..ddf7706 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -18,6 +18,7 @@ open class BSTree, Value> : BinTree { parent.left = Node(key, value) else (parent.left)?.value = value ?: error("unexpected null") } + else rootNode?.value = value ?: error("unexpected null") } } diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 2681c7d..2b8ca9e 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -52,6 +52,8 @@ abstract class BinTree, Value> { curNode else recFind(curNode.left) + } else if (curNode.equalKey(key)) { + return curNode.parent } else { if (curNode.right?.equalKey(key) != false) curNode @@ -59,7 +61,6 @@ abstract class BinTree, Value> { recFind(curNode.right) } } - return recFind(rootNode) } From 9e41ade1a0440bb873ace25920a9c25e52cb8b81 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov <113101817+Kuarni@users.noreply.github.com> Date: Tue, 28 Mar 2023 03:41:49 +0300 Subject: [PATCH 020/164] feat: removed the launch of actions at pull request --- .github/workflows/actions.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index ade9117..908cc74 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -2,8 +2,6 @@ name: test on: push: - pull_request: - branches: [main] jobs: build: From 47dabb7d3133b369079096f98b39e6829518c016 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 30 Mar 2023 10:13:52 +0300 Subject: [PATCH 021/164] feat: update actions.yml --- .github/workflows/actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 908cc74..01c715f 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -8,9 +8,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up JDK 18 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 18 From 7c677cc362f7c8d8dd6ef1ebc5c4bf9c84252a04 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 30 Mar 2023 10:15:35 +0300 Subject: [PATCH 022/164] fix: change setup-java on v1 --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 01c715f..eac6bc9 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 18 - uses: actions/setup-java@v2 + uses: actions/setup-java@v1 with: java-version: 18 From e23eef9f2c69b1cf2e0519d6f8425029b2734297 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 30 Mar 2023 11:06:20 +0300 Subject: [PATCH 023/164] feat: add Gradle cash --- .github/workflows/actions.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index eac6bc9..0c512e7 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -13,7 +13,13 @@ jobs: uses: actions/setup-java@v1 with: java-version: 18 - + + - name: Cache Gradle packages + uses: actions/cache@v1 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle From be0d0c1e804808bc2706ff89bea284eaa8217f22 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 30 Mar 2023 11:41:01 +0300 Subject: [PATCH 024/164] feat: add launch at pull request to the work --- .github/workflows/actions.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 0c512e7..6386801 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -2,6 +2,8 @@ name: test on: push: + pull_request: + branches: [work] jobs: build: From e1d880bb6fe5235dba049dc8bc836f2f47105f92 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 30 Mar 2023 11:41:34 +0300 Subject: [PATCH 025/164] feat: update actions/cache to v3 --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 6386801..809bd9e 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -17,7 +17,7 @@ jobs: java-version: 18 - name: Cache Gradle packages - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} From 3d78789e6103d92faaa8ab06e5c44f18dcfc3b0b Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 30 Mar 2023 11:54:42 +0300 Subject: [PATCH 026/164] feat: update setup-java to v3 --- .github/workflows/actions.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 809bd9e..2ad0e51 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -11,10 +11,11 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 18 - uses: actions/setup-java@v1 + - name: Set up JDK + uses: actions/setup-java@v3 with: java-version: 18 + distribution: 'temurin' - name: Cache Gradle packages uses: actions/cache@v3 From aa024b1a903d6934405fe909c352c9ab285e9c60 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 31 Mar 2023 17:51:29 +0300 Subject: [PATCH 027/164] feat: add fun minElement, maxElement, nextElement to abstractBinTree --- src/main/kotlin/abstractBinTree.kt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index d949b3a..89393ca 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -77,4 +77,25 @@ abstract class BinTree, Value> { open fun get(key: Key): Value? { return getNode(key)?.value } + + protected open fun nextElement(node: Node): Node? { + val nodeRight: Node = node.right ?: return null + return minElement(nodeRight.key) + } + + protected fun minElement(key: Key): Node? { + var minNode: Node? = getNode(key) ?: return null + while (minNode?.left != null) { + minNode = minNode.left ?: error("unexpected null") + } + return minNode + } + + protected fun maxElement(key: Key): Node? { + var maxNode: Node? = getNode(key) ?: return null + while (maxNode?.right != null) { + maxNode = maxNode.right ?: error("unexpected null") + } + return maxNode + } } From 28619b556f2d48f003d20f422b54d1613f27036e Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 31 Mar 2023 18:26:06 +0300 Subject: [PATCH 028/164] fix: fun remove for BSTree --- src/main/kotlin/BSTree.kt | 76 ++++++++++++++------------------------- 1 file changed, 26 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index f9680ca..692dfd7 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -3,7 +3,7 @@ open class BSTree, Value> : BinTree { constructor(key: Key, value: Value) : super(key, value) constructor(vararg pairs: Pair) : super(pairs) - open override fun insert(key: Key, value: Value) { + override fun insert(key: Key, value: Value) { if (rootNode == null) rootNode = Node(key, value) else { @@ -21,68 +21,44 @@ open class BSTree, Value> : BinTree { } } - open override fun remove(key: Key) { - val node = getNode(key) - if (node != null) - removeNode(node) - } - protected open fun removeNode(node: Node) { - if ((node.left == null) && (node.right == null)) { - if (node.parent == null) + override fun remove(key: Key) { + val node: Node? = getNode(key) + if (node == null) + return + else if ((node.left == null) && (node.right == null)) { + val parent: Node? = node.parent + if (parent == null) rootNode = null - else if (node == node.parent!!.left) - node.parent!!.left = null + else if (node == parent.left) + parent.left = null else - node.parent!!.right = null - } - else if (node.left == null) - transplant(node, node.right!!) + parent.right = null + } else if (node.left == null) + transplant(node, node.right ?: error("unexpected null")) else if (node.right == null) - transplant(node, node.left!!) - else{ + transplant(node, node.left ?: error("unexpected null")) + else { val nextNode = nextElement(node) if (nextNode != null) { - if (nextNode.right != null) - transplant(nextNode, nextNode.right!!) + nextNode.right?.let {transplant(nextNode, it)} nextNode.right = node.right nextNode.left = node.left - nextNode.right!!.parent = nextNode - nextNode.left!!.parent = nextNode + nextNode.right?.parent = nextNode + nextNode.left?.parent = nextNode transplant(node, nextNode) } } } - protected open fun transplant(node1: Node, node2: Node) { - if (node1.parent == null) - rootNode = node2 - else if (node1 == (node1.parent)!!.left) { - (node1.parent)!!.left = node2 + protected open fun transplant(oldNode: Node, newNode: Node) { + val parent: Node? = oldNode.parent + if (parent == null) + rootNode = newNode + else if (oldNode == parent.left) { + parent.left = newNode } else { - (node1.parent)!!.right = node2 - } - node2.parent = node1.parent - } - - protected open fun nextElement(node: Node): Node?{ - if (node.right == null) - return null - return minElement(node.right!!) - } - - protected fun minElement(node: Node): Node { - var node1: Node = node - while (node1.left != null) { - node1 = node1.left!! - } - return node1 - } - - protected fun maxElement(node: Node): Node { - var node1: Node = node - while (node1.right != null) { - node1 = node1.right!! + parent.right = newNode } - return node1 + newNode.parent = parent } } From e6f04a5c9ddb94a59f2b45207e316796029b0249 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 31 Mar 2023 18:38:50 +0300 Subject: [PATCH 029/164] feat: fun getGrandparent, getUncle --- src/main/kotlin/RBTree.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index c7e07f9..188a802 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -12,11 +12,28 @@ class RBTree, Value> : BalanceTree() { } } + override fun insert(key: Key, value: Value) { if (rootNode == null) rootNode = RBNode(key, value) } + protected fun getGrandparent(key: Key): RBNode? { + val parent = getParent(key) as RBNode? + parent?.let { it -> it.parent?.let { return it as RBNode} } + return null + } + + protected fun getUncle(key: Key): RBNode? { + val grandparent = getGrandparent(key) + val parent = getParent(key) as RBNode + grandparent?.let{ + if (it.left == parent) return it.right as RBNode + else if (it.right == parent) return it.left as RBNode + } + return null + } + override fun rebalancing() { TODO("Not yet implemented") } From 5681b32750113139170e4eea8513ea333f32bdeb Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 31 Mar 2023 19:40:38 +0300 Subject: [PATCH 030/164] feat: fun rotation --- src/main/kotlin/BSTree.kt | 14 +----------- src/main/kotlin/abstractBalanceTree.kt | 30 ++++++++++++++++++++++++-- src/main/kotlin/abstractBinTree.kt | 12 +++++++++++ 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 692dfd7..cf93a7a 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -40,7 +40,7 @@ open class BSTree, Value> : BinTree { else { val nextNode = nextElement(node) if (nextNode != null) { - nextNode.right?.let {transplant(nextNode, it)} + nextNode.right?.let { transplant(nextNode, it) } nextNode.right = node.right nextNode.left = node.left nextNode.right?.parent = nextNode @@ -49,16 +49,4 @@ open class BSTree, Value> : BinTree { } } } - - protected open fun transplant(oldNode: Node, newNode: Node) { - val parent: Node? = oldNode.parent - if (parent == null) - rootNode = newNode - else if (oldNode == parent.left) { - parent.left = newNode - } else { - parent.right = newNode - } - newNode.parent = parent - } } diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 9a95997..77f2d5c 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,5 +1,31 @@ abstract class BalanceTree, Value> : BinTree() { protected abstract fun rebalancing() - protected abstract fun leftRotation() - protected abstract fun rightRotation() + + protected fun rotation(key: Key, type: String) { + //giving the parentNode key + val parent: Node? = getNode(key) + parent?.let { + val node = if (type == "left") + it.right ?: error("rotation is not possible") + else it.left ?: error("rotation is not possible") + + when (type) { + "left" -> { + it.right = node.left + node.left?.parent = it + } + + "right" -> { + it.left = node.right + node.right?.parent = it + } + + else -> error("rotation is not possible: wrong type entered") + } + transplant(it, node) + it.parent = node + node.left = it + } + } + } diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 89393ca..be15c47 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -98,4 +98,16 @@ abstract class BinTree, Value> { } return maxNode } + + protected open fun transplant(oldNode: Node, newNode: Node) { + val parent: Node? = oldNode.parent + if (parent == null) + rootNode = newNode + else if (oldNode == parent.left) { + parent.left = newNode + } else { + parent.right = newNode + } + newNode.parent = parent + } } From d33eaf2878a84cc9fffb6a2c54c6a37c2afeafd1 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 31 Mar 2023 20:21:37 +0300 Subject: [PATCH 031/164] fix: fun rotation: add enum class RotationType --- src/main/kotlin/RBTree.kt | 8 -------- src/main/kotlin/abstractBalanceTree.kt | 16 ++++++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 188a802..b248152 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -38,14 +38,6 @@ class RBTree, Value> : BalanceTree() { TODO("Not yet implemented") } - override fun leftRotation() { - TODO("Not yet implemented") - } - - override fun rightRotation() { - TODO("Not yet implemented") - } - override fun remove(key: Key) { TODO("Not yet implemented") } diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 77f2d5c..970bf6b 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,31 +1,31 @@ abstract class BalanceTree, Value> : BinTree() { protected abstract fun rebalancing() - protected fun rotation(key: Key, type: String) { + enum class RotationType { Left, Right } + + protected fun rotation(key: Key, type: RotationType) { //giving the parentNode key val parent: Node? = getNode(key) parent?.let { - val node = if (type == "left") + val node = if (type.name == "Left") it.right ?: error("rotation is not possible") else it.left ?: error("rotation is not possible") - when (type) { - "left" -> { + when (type.name) { + "Left" -> { it.right = node.left node.left?.parent = it } - "right" -> { + "Right" -> { it.left = node.right node.right?.parent = it } - - else -> error("rotation is not possible: wrong type entered") } transplant(it, node) it.parent = node node.left = it } } - } + From d1ffa374907cefaf36a15ad594a4a6657880c82a Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sat, 1 Apr 2023 00:45:58 +0300 Subject: [PATCH 032/164] feat: fun insertNode, removeNode in abstractBinTree.kt --- src/main/kotlin/AVLTree.kt | 12 ------ src/main/kotlin/BSTree.kt | 43 +------------------- src/main/kotlin/RBTree.kt | 34 ++++++++++------ src/main/kotlin/abstractBalanceTree.kt | 2 - src/main/kotlin/abstractBinTree.kt | 54 ++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index fb8593b..c956ef2 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -26,16 +26,4 @@ class AVLTree, Value> : BalanceTree() { override fun remove(key: Key) { TODO("Not yet implemented") } - - override fun leftRotation() { - TODO("Not yet implemented") - } - - override fun rightRotation() { - TODO("Not yet implemented") - } - - override fun rebalancing() { - TODO("Not yet implemented") - } } diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index cf93a7a..abbba31 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -4,49 +4,10 @@ open class BSTree, Value> : BinTree { constructor(vararg pairs: Pair) : super(pairs) override fun insert(key: Key, value: Value) { - if (rootNode == null) - rootNode = Node(key, value) - else { - val parent = getParent(key) - if (parent != null) { - if (parent < key) - if (parent.right == null) - parent.right = Node(key, value) - else parent.right?.value = value ?: error("unexpected null") - else - if (parent.left == null) - parent.left = Node(key, value) - else (parent.left)?.value = value ?: error("unexpected null") - } - } + insertNode(key, value) } override fun remove(key: Key) { - val node: Node? = getNode(key) - if (node == null) - return - else if ((node.left == null) && (node.right == null)) { - val parent: Node? = node.parent - if (parent == null) - rootNode = null - else if (node == parent.left) - parent.left = null - else - parent.right = null - } else if (node.left == null) - transplant(node, node.right ?: error("unexpected null")) - else if (node.right == null) - transplant(node, node.left ?: error("unexpected null")) - else { - val nextNode = nextElement(node) - if (nextNode != null) { - nextNode.right?.let { transplant(nextNode, it) } - nextNode.right = node.right - nextNode.left = node.left - nextNode.right?.parent = nextNode - nextNode.left?.parent = nextNode - transplant(node, nextNode) - } - } + removeNode(key) } } diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index b248152..a4596c6 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -14,31 +14,43 @@ class RBTree, Value> : BalanceTree() { override fun insert(key: Key, value: Value) { - if (rootNode == null) - rootNode = RBNode(key, value) + val parent = insertNode(key, value) as RBNode? + rebalancing(parent) + } + + override fun remove(key: Key) { + val parent = removeNode(key) as RBNode? + rebalancing(parent) + } + + private fun rebalancing(parent: RBNode?) { + TODO("Not yet implemented") } protected fun getGrandparent(key: Key): RBNode? { - val parent = getParent(key) as RBNode? + val parent = getParent(key) parent?.let { it -> it.parent?.let { return it as RBNode} } return null } protected fun getUncle(key: Key): RBNode? { val grandparent = getGrandparent(key) - val parent = getParent(key) as RBNode + val parent = getParent(key) grandparent?.let{ - if (it.left == parent) return it.right as RBNode - else if (it.right == parent) return it.left as RBNode + if (it.left == parent) return it.right as RBNode? + else if (it.right == parent) return it.left as RBNode? } return null } - override fun rebalancing() { - TODO("Not yet implemented") + protected fun getBrother(key: Key): RBNode? { + val node = getNode(key) + val parent = getParent(key) + parent?.let { + if (parent.left == node) return parent.right as RBNode? + else if(parent.right == node) return parent.left as RBNode? + } + return null } - override fun remove(key: Key) { - TODO("Not yet implemented") - } } diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 970bf6b..220a9bc 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,6 +1,4 @@ abstract class BalanceTree, Value> : BinTree() { - protected abstract fun rebalancing() - enum class RotationType { Left, Right } protected fun rotation(key: Key, type: RotationType) { diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index be15c47..c43d655 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -39,8 +39,62 @@ abstract class BinTree, Value> { } abstract fun insert(key: Key, value: Value) + + protected fun insertNode(key: Key, value: Value): Node? { + if (rootNode == null) + rootNode = Node(key, value) + else { + val parent = getParent(key) + if (parent != null) { + if (parent < key) + if (parent.right == null) { + parent.right = Node(key, value) + Node(key, value).parent = parent + } + else parent.right?.value = value ?: error("unexpected null") + else + if (parent.left == null) { + parent.left = Node(key, value) + Node(key, value).parent = parent + } + else (parent.left)?.value = value ?: error("unexpected null") + } + return parent + } + return null + } + abstract fun remove(key: Key) + protected fun removeNode(key: Key): Node? { + val node: Node? = getNode(key) + if (node == null) + return null + else if ((node.left == null) && (node.right == null)) { + val parent: Node? = node.parent + if (parent == null) + rootNode = null + else if (node == parent.left) + parent.left = null + else + parent.right = null + } else if (node.left == null) + transplant(node, node.right ?: error("unexpected null")) + else if (node.right == null) + transplant(node, node.left ?: error("unexpected null")) + else { + val nextNode = nextElement(node) + if (nextNode != null) { + nextNode.right?.let { transplant(nextNode, it) } + nextNode.right = node.right + nextNode.left = node.left + nextNode.right?.parent = nextNode + nextNode.left?.parent = nextNode + transplant(node, nextNode) + } + } + return node.parent + } protected fun getParent(key: Key): Node? { tailrec fun recFind(curNode: Node?): Node? { From c7520bb58d818950b00d9b0b11634c87c6197380 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sat, 1 Apr 2023 12:13:20 +0300 Subject: [PATCH 033/164] feat: fun rebalancing for insert in RBTree --- src/main/kotlin/RBTree.kt | 59 ++++++++++++++++---------- src/main/kotlin/abstractBalanceTree.kt | 6 ++- src/main/kotlin/abstractBinTree.kt | 5 +-- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index a4596c6..551244b 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -14,43 +14,58 @@ class RBTree, Value> : BalanceTree() { override fun insert(key: Key, value: Value) { - val parent = insertNode(key, value) as RBNode? - rebalancing(parent) + val node = insertNode(key, value) as RBNode + rebalancing(node) } override fun remove(key: Key) { val parent = removeNode(key) as RBNode? - rebalancing(parent) + TODO("will do soon") } - private fun rebalancing(parent: RBNode?) { - TODO("Not yet implemented") + private tailrec fun rebalancing(node: RBNode) { + val parent = getParent(node.key) as RBNode? + if (parent == null) (rootNode as RBNode?)?.Black = true + else if (parent.Black) return + else { + val uncle = getUncle(node.key) ?: error("balancing error") + val grandparent = getGrandparent(node.key) ?: error("balancing error") + if (!uncle.Black) { + parent.swapColor() + uncle.swapColor() + grandparent.swapColor() + rebalancing(grandparent) + } else { + if (grandparent.left == parent) { + if (parent.right == node) rotation(parent.key, RotationType.Left) + val newNode = rotation(grandparent.key, RotationType.Right) as RBNode? + grandparent.swapColor() + newNode?.swapColor() ?: error("balancing error") + } else { + if (parent.left == node) rotation(parent.key, RotationType.Right) + val newNode = rotation(grandparent.key, RotationType.Left) as RBNode? + grandparent.swapColor() + newNode?.swapColor() ?: error("balancing error") + } + } + } } protected fun getGrandparent(key: Key): RBNode? { val parent = getParent(key) - parent?.let { it -> it.parent?.let { return it as RBNode} } - return null - } - - protected fun getUncle(key: Key): RBNode? { - val grandparent = getGrandparent(key) - val parent = getParent(key) - grandparent?.let{ - if (it.left == parent) return it.right as RBNode? - else if (it.right == parent) return it.left as RBNode? - } + parent?.let { it -> it.parent?.let { return it as RBNode } } return null } protected fun getBrother(key: Key): RBNode? { val node = getNode(key) - val parent = getParent(key) - parent?.let { - if (parent.left == node) return parent.right as RBNode? - else if(parent.right == node) return parent.left as RBNode? - } - return null + val parent = getParent(key) ?: return null + return if (parent.left == node) parent.right as RBNode? + else parent.left as RBNode? } + protected fun getUncle(key: Key): RBNode? { + val parent = getParent(key) ?: return null + return getBrother(parent.key) + } } diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 220a9bc..6afc38b 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,9 +1,9 @@ abstract class BalanceTree, Value> : BinTree() { enum class RotationType { Left, Right } - protected fun rotation(key: Key, type: RotationType) { + protected fun rotation(parentKey: Key, type: RotationType): Node? { //giving the parentNode key - val parent: Node? = getNode(key) + val parent: Node? = getNode(parentKey) parent?.let { val node = if (type.name == "Left") it.right ?: error("rotation is not possible") @@ -23,7 +23,9 @@ abstract class BalanceTree, Value> : BinTree() transplant(it, node) it.parent = node node.left = it + return node } + return null } } diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 8977fdd..90a526c 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -41,7 +41,7 @@ abstract class BinTree, Value> { abstract fun insert(key: Key, value: Value) - protected fun insertNode(key: Key, value: Value): Node? { + protected fun insertNode(key: Key, value: Value): Node { if (rootNode == null) rootNode = Node(key, value) else { @@ -60,9 +60,8 @@ abstract class BinTree, Value> { } else (parent.left)?.value = value ?: error("unexpected null") } - return parent } - return null + return Node(key, value) } abstract fun remove(key: Key) From 44a0e07273800d7dca0ad40c55d2f1907c233562 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 1 Apr 2023 00:18:33 +0300 Subject: [PATCH 034/164] feat: add function insertService in BinTree --- src/main/kotlin/abstractBinTree.kt | 46 +++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 2b8ca9e..154e0fe 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,12 +1,13 @@ +import java.lang.reflect.Constructor import kotlin.math.abs abstract class BinTree, Value> { - protected open class Node, Value>( + protected open class BinNode, Value>( val key: Key, var value: Value, - var parent: Node? = null, - var left: Node? = null, - var right: Node? = null + var parent: BinNode? = null, + var left: BinNode? = null, + var right: BinNode? = null ) : Comparable { override fun compareTo(other: Key): Int { @@ -19,7 +20,7 @@ abstract class BinTree, Value> { } - protected open var rootNode: Node? = null + protected open var rootNode: BinNode? = null constructor() constructor(key: Key, value: Value) { @@ -42,9 +43,34 @@ abstract class BinTree, Value> { abstract fun insert(key: Key, value: Value) abstract fun remove(key: Key) + //return the inserted node if the node with the same key wasn't in the tree and null in otherwise + //doesn't balance the tree + protected fun insertService(node: BinNode): BinNode? { + if (rootNode == null) { + rootNode = node + return node + } else { + val parent = getParent(node.key) + if (parent != null) { + if (parent < node.key) + if (parent.right == null) { + node.parent = parent + parent.right = node + return node + } else parent.right?.value = node.value ?: error("unexpected null") + else + if (parent.left == null) { + node.parent = parent + parent.left = node + return node + } else (parent.left)?.value = node.value ?: error("unexpected null") + } else rootNode?.value = node.value ?: error("unexpected null") + } + return null + } - protected fun getParent(key: Key): Node? { - tailrec fun recFind(curNode: Node?): Node? { + protected fun getParent(key: Key): BinNode? { + tailrec fun recFind(curNode: BinNode?): BinNode? { return if (curNode == null) null else if (curNode > key) { @@ -64,7 +90,7 @@ abstract class BinTree, Value> { return recFind(rootNode) } - protected fun getNode(key: Key): Node? { + protected fun getNode(key: Key): BinNode? { if (rootNode?.equalKey(key) == true) return rootNode val parent = getParent(key) @@ -80,7 +106,7 @@ abstract class BinTree, Value> { return getNode(key)?.value } - protected fun breadthFirstSearch(function: (Node?) -> Unit, addNullNodes: Boolean) { + protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean) { val queue = mutableListOf(rootNode) fun notNullInQueue(): Boolean { @@ -112,7 +138,7 @@ abstract class BinTree, Value> { var elemInTheLevel = 0 var string = "" - fun function(node: Node?) { + fun function(node: BinNode?) { string += node?.key ?: "-" string += " " elemInTheLevel += 1 From f414a2e7bef875a94c05a2e16f81372c1f428c0d Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 1 Apr 2023 00:20:33 +0300 Subject: [PATCH 035/164] feat: reworked insert in the trees --- src/main/kotlin/AVLTree.kt | 11 +++-------- src/main/kotlin/BSTree.kt | 31 ++++++++----------------------- src/main/kotlin/RBTree.kt | 3 ++- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index fb8593b..688f610 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -6,21 +6,16 @@ class AVLTree, Value> : BalanceTree() { left: AVLNode? = null, right: AVLNode? = null, var height: UByte = 0U - ) : Node(key, value, parent, left, right) + ) : BinNode(key, value, parent, left, right) override fun insert(key: Key, value: Value) { if (rootNode != null) { if ((rootNode as AVLNode).height != 255.toUByte()) { - val parent = getParent(key) - if (parent != null) { - if (parent < key) - parent.right = AVLNode(key, value) - else - parent.left = AVLNode(key, value) - } + insertService(AVLNode(key, value)) } } else rootNode = AVLNode(key, value) + TODO("add balancing") } override fun remove(key: Key) { diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index ddf7706..233aa74 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -4,22 +4,7 @@ open class BSTree, Value> : BinTree { constructor(vararg pairs: Pair) : super(pairs) open override fun insert(key: Key, value: Value) { - if (rootNode == null) - rootNode = Node(key, value) - else { - val parent = getParent(key) - if (parent != null) { - if (parent < key) - if (parent.right == null) - parent.right = Node(key, value) - else parent.right?.value = value ?: error("unexpected null") - else - if (parent.left == null) - parent.left = Node(key, value) - else (parent.left)?.value = value ?: error("unexpected null") - } - else rootNode?.value = value ?: error("unexpected null") - } + insertService(BinNode(key, value)) } open override fun remove(key: Key) { @@ -27,7 +12,7 @@ open class BSTree, Value> : BinTree { if (node != null) removeNode(node) } - protected open fun removeNode(node: Node) { + protected open fun removeNode(node: BinNode) { if ((node.left == null) && (node.right == null)) { if (node.parent == null) rootNode = null @@ -54,7 +39,7 @@ open class BSTree, Value> : BinTree { } } - protected open fun transplant(node1: Node, node2: Node) { + protected open fun transplant(node1: BinNode, node2: BinNode) { if (node1.parent == null) rootNode = node2 else if (node1 == (node1.parent)!!.left) { @@ -65,22 +50,22 @@ open class BSTree, Value> : BinTree { node2.parent = node1.parent } - protected open fun nextElement(node: Node): Node?{ + protected open fun nextElement(node: BinNode): BinNode?{ if (node.right == null) return null return minElement(node.right!!) } - protected fun minElement(node: Node): Node { - var node1: Node = node + protected fun minElement(node: BinNode): BinNode { + var node1: BinNode = node while (node1.left != null) { node1 = node1.left!! } return node1 } - protected fun maxElement(node: Node): Node { - var node1: Node = node + protected fun maxElement(node: BinNode): BinNode { + var node1: BinNode = node while (node1.right != null) { node1 = node1.right!! } diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index c7e07f9..21e330d 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -6,7 +6,7 @@ class RBTree, Value> : BalanceTree() { left: RBNode? = null, right: RBNode? = null, var Black: Boolean = false - ) : Node(key, value, parent, left, right) { + ) : BinNode(key, value, parent, left, right) { fun swapColor() { Black = !Black } @@ -15,6 +15,7 @@ class RBTree, Value> : BalanceTree() { override fun insert(key: Key, value: Value) { if (rootNode == null) rootNode = RBNode(key, value) + TODO("add balancing") } override fun rebalancing() { From fc50921716e7374acf85a0ed0f2fdb70e348e203 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 16:09:10 +0300 Subject: [PATCH 036/164] feat: add get, remove and insert with array arg --- src/main/kotlin/abstractBinTree.kt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 154e0fe..fdee76c 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,4 +1,3 @@ -import java.lang.reflect.Constructor import kotlin.math.abs abstract class BinTree, Value> { @@ -26,6 +25,9 @@ abstract class BinTree, Value> { constructor(key: Key, value: Value) { insert(key, value) } + constructor(array: Array>) { + sortInsert(array) + } private fun sortInsert(array: Array>) { val serArray = array.sortedBy { it.first }.toTypedArray() @@ -36,13 +38,19 @@ abstract class BinTree, Value> { } } - constructor(array: Array>) { + abstract fun insert(key: Key, value: Value) + + fun insert(vararg array: Pair) { sortInsert(array) } - abstract fun insert(key: Key, value: Value) abstract fun remove(key: Key) + fun remove(vararg array: Key) { + for (i in array) + remove(i) + } + //return the inserted node if the node with the same key wasn't in the tree and null in otherwise //doesn't balance the tree protected fun insertService(node: BinNode): BinNode? { @@ -106,6 +114,10 @@ abstract class BinTree, Value> { return getNode(key)?.value } + fun get(vararg keys: Key): List { + return List(keys.size, {get(keys[it])}) + } + protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean) { val queue = mutableListOf(rootNode) From 6c3b3f75872d30ef5b4e0fcc12ab2b3335215316 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 17:14:21 +0300 Subject: [PATCH 037/164] fix: get left leaf problem fix --- src/main/kotlin/abstractBinTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 154e0fe..48918c5 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,4 +1,3 @@ -import java.lang.reflect.Constructor import kotlin.math.abs abstract class BinTree, Value> { @@ -98,8 +97,9 @@ abstract class BinTree, Value> { null else if (parent.left?.equalKey(key) == true) parent.left - else + else if (parent.right?.equalKey(key) == true) parent.right + else null } open fun get(key: Key): Value? { From bf6d6758e15645f81d8a700a275350346c0c3603 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 18:48:20 +0300 Subject: [PATCH 038/164] feat: new tests for BSTree --- src/test/kotlin/BSTreeTest.kt | 121 +++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 18 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index 845c566..93b17da 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -1,33 +1,28 @@ import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assumptions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import kotlin.test.assertContains +import kotlin.test.expect +import org.junit.jupiter.api.assertAll class BSTreeTest { + fun generateTreeWithInsert(vararg arr: Int): BinTree { + val tree = BSTree() + for (i in arr) tree.insert(i, "${i}k") + return tree + } + @Nested inner class `Test insert` { - fun generateTreeWithInsert(vararg arr: Int): BinTree { - val tree = BSTree() - for (i in arr) - tree.insert(i, "${i}k") - return tree - } - @Test fun `insert one node test`() { val expected = "4k" assertEquals(expected, generateTreeWithInsert(4).get(4)) } - @Test - fun `insert three nodes test`() { - assertEquals("2 \n1 3 \n", generateTreeWithInsert(2, 1, 3).Debug().treeKeysInString()) - } - - @Test - fun `degenerate tree`() { - assertEquals("1 \n- 2 \n- - - 3 \n", generateTreeWithInsert(1, 2, 3).Debug().treeKeysInString()) - } - @Nested inner class `Equal key` { @Test @@ -44,13 +39,103 @@ class BSTreeTest { assertEquals("5k", tree.get(4)) } + @Test fun `two inserts of node`() { val tree = generateTreeWithInsert(5, 6, 4) tree.insert(4, "5k") - assertEquals("5 \n4 6 \n", tree.Debug().treeKeysInString()) assertEquals("5k", tree.get(4)) } } } + + @Nested + inner class `constructors test` { + @Test + fun `insert key, value`() { + val tree = BSTree(4, "4k") + assertEquals("4k", tree.get(4)) + } + + @Test + fun `insert two node`() { + val tree = BSTree(Pair(4, "4k"), Pair(5, "5k")) + assertArrayEquals(arrayOf("4k", "5k"), tree.get(4, 5).toTypedArray()) + } + + @Test + fun `insert equal nodes`() { + val tree = BSTree(Pair(4, "4k"), Pair(5, "5k"), Pair(4, "7k")) + assertAll({ assertContains(arrayOf("4k", "7k"), tree.get(4)) }, { assertEquals("5k", tree.get(5)) }) + } + } + + @Nested + inner class `remove node`() { + @Test + fun `remove one root node`() { + val tree = generateTreeWithInsert(4) + tree.remove(4) + assertNull(tree.get(4)) + } + + @Test + fun `remove non-root node`() { + val tree = generateTreeWithInsert(4, 6, 5, 7) + tree.remove(6) + assertAll({ assertNull(tree.get(6)) }, + { assertArrayEquals(arrayOf("4k", "5k", "7k"), tree.get(4, 5, 7).toTypedArray()) }) + } + + @Test + fun `remove left leaf`() { + val tree = generateTreeWithInsert(4, 6, 5, 7) + tree.remove(5) + assertAll({ assertNull(tree.get(5)) }, + { assertArrayEquals(arrayOf("4k", "6k", "7k"), tree.get(4, 6, 7).toTypedArray()) }) + } + + @Test + fun `remove right leaf`() { + val tree = generateTreeWithInsert(4, 6, 5, 7) + tree.remove(7) + assertAll({ assertNull(tree.get(7)) }, + { assertArrayEquals(arrayOf("4k", "6k", "5k"), tree.get(4, 6, 5).toTypedArray()) }) + } + + @Test + fun `remove root node in non-degenerate tree`() { + val tree = generateTreeWithInsert(4, 6, 5, 7) + tree.remove(4) + assertAll({ assertNull(tree.get(4)) }, + { assertArrayEquals(arrayOf("5k", "6k", "7k"), tree.get(5, 6, 7).toTypedArray()) }) + } + } + + @Nested + inner class `tests using debug` { + @Test + fun `insert three nodes test`() { + assertEquals("2 \n1 3 \n", generateTreeWithInsert(2, 1, 3).Debug().treeKeysInString()) + } + + @Test + fun `degenerate tree`() { + assertEquals("1 \n- 2 \n- - - 3 \n", generateTreeWithInsert(1, 2, 3).Debug().treeKeysInString()) + } + + @Test + fun `two inserts of node with equal keys`() { + val tree = generateTreeWithInsert(5, 6, 4) + tree.insert(4, "5k") + assertEquals("5 \n4 6 \n", tree.Debug().treeKeysInString()) + } + + @Test + fun `multiple removal`() { + val tree = generateTreeWithInsert(10, 7, 15, 13, 17, 16, 18, 14) + tree.remove(15) + assertEquals("10 \n7 16 \n- - 13 17 \n- - - - 12 14 - 18 \n", tree.Debug().treeKeysInString()) + } + } } From f29013dcdf44ab1bfc800d24cffc8ba3eca6e305 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 19:28:56 +0300 Subject: [PATCH 039/164] fix: now child in old parents == null in removeNode --- src/main/kotlin/BSTree.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 233aa74..3c368e9 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -16,10 +16,10 @@ open class BSTree, Value> : BinTree { if ((node.left == null) && (node.right == null)) { if (node.parent == null) rootNode = null - else if (node == node.parent!!.left) - node.parent!!.left = null + else if (node == node.parent?.left) + node.parent?.left = null else - node.parent!!.right = null + node.parent?.right = null } else if (node.left == null) transplant(node, node.right!!) @@ -28,12 +28,18 @@ open class BSTree, Value> : BinTree { else{ val nextNode = nextElement(node) if (nextNode != null) { - if (nextNode.right != null) + if (nextNode.right != null) { transplant(nextNode, nextNode.right!!) + } + if (nextNode == (nextNode.parent)!!.left) { + (nextNode.parent)!!.left = null + } else { + (nextNode.parent)!!.right = null + } nextNode.right = node.right nextNode.left = node.left - nextNode.right!!.parent = nextNode - nextNode.left!!.parent = nextNode + nextNode.right?.parent = nextNode + nextNode.left?.parent = nextNode transplant(node, nextNode) } } From 03cb12df1406aecb3507f1d703f9bd2a79f2610f Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 19:30:54 +0300 Subject: [PATCH 040/164] refactor: auto refactoring in BSTree --- src/main/kotlin/BSTree.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 3c368e9..89c710e 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -12,6 +12,7 @@ open class BSTree, Value> : BinTree { if (node != null) removeNode(node) } + protected open fun removeNode(node: BinNode) { if ((node.left == null) && (node.right == null)) { if (node.parent == null) @@ -20,12 +21,11 @@ open class BSTree, Value> : BinTree { node.parent?.left = null else node.parent?.right = null - } - else if (node.left == null) + } else if (node.left == null) transplant(node, node.right!!) else if (node.right == null) transplant(node, node.left!!) - else{ + else { val nextNode = nextElement(node) if (nextNode != null) { if (nextNode.right != null) { @@ -56,7 +56,7 @@ open class BSTree, Value> : BinTree { node2.parent = node1.parent } - protected open fun nextElement(node: BinNode): BinNode?{ + protected open fun nextElement(node: BinNode): BinNode? { if (node.right == null) return null return minElement(node.right!!) From 2ac84480eba265f91f2573056d446eb0765a4799 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 19:31:36 +0300 Subject: [PATCH 041/164] fix: fix remove test using debug --- src/test/kotlin/BSTreeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index 93b17da..d280410 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -133,7 +133,7 @@ class BSTreeTest { @Test fun `multiple removal`() { - val tree = generateTreeWithInsert(10, 7, 15, 13, 17, 16, 18, 14) + val tree = generateTreeWithInsert(10, 7, 15, 13, 17, 16, 18, 14, 12) tree.remove(15) assertEquals("10 \n7 16 \n- - 13 17 \n- - - - 12 14 - 18 \n", tree.Debug().treeKeysInString()) } From c40aaa9e4ca275d8ff57a228a3448869df265fea Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 5 Apr 2023 19:38:33 +0300 Subject: [PATCH 042/164] feat: expand multiple removal test --- src/test/kotlin/BSTreeTest.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index d280410..abdecc8 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -1,11 +1,7 @@ import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Assumptions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import kotlin.test.assertContains -import kotlin.test.expect import org.junit.jupiter.api.assertAll class BSTreeTest { @@ -133,9 +129,13 @@ class BSTreeTest { @Test fun `multiple removal`() { - val tree = generateTreeWithInsert(10, 7, 15, 13, 17, 16, 18, 14, 12) + val tree = generateTreeWithInsert(10, 7, 15, 13, 17, 16, 18, 14, 12, 6, 9) tree.remove(15) - assertEquals("10 \n7 16 \n- - 13 17 \n- - - - 12 14 - 18 \n", tree.Debug().treeKeysInString()) + assertEquals("10 \n7 16 \n6 9 13 17 \n- - - - 12 14 - 18 \n", tree.Debug().treeKeysInString()) + tree.remove(10) + assertEquals("12 \n7 16 \n6 9 13 17 \n- - - - - 14 - 18 \n", tree.Debug().treeKeysInString()) + tree.remove(17) + assertEquals("12 \n7 16 \n6 9 13 18 \n- - - - - 14 ", tree.Debug().treeKeysInString()) } } } From cd481cf36cbcb0e42584bf0f0f6581f7aa1e0f5a Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 6 Apr 2023 16:18:51 +0300 Subject: [PATCH 043/164] feat(small change): change type of Debug class to internal --- src/main/kotlin/abstractBinTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index d12924e..54e5002 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -145,7 +145,7 @@ abstract class BinTree, Value> { } } - open inner class Debug { + internal open inner class Debug { fun treeKeysInString(): String { var sizeOfLevel = 1 var elemInTheLevel = 0 From 3ff13464c7ef3c7e5522548d547b7d35991d32b4 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Thu, 6 Apr 2023 19:42:59 +0300 Subject: [PATCH 044/164] git: merge my work with RBTree_Balancer --- src/main/kotlin/BSTree.kt | 38 ++------------------------ src/main/kotlin/RBTree.kt | 34 +++++++++++++++++------ src/main/kotlin/abstractBalanceTree.kt | 2 +- src/main/kotlin/abstractBinTree.kt | 37 +++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 48 deletions(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index fe6a981..cd270a1 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -7,41 +7,7 @@ open class BSTree, Value> : BinTree { insertService(BinNode(key, value)) } - open override fun remove(key: Key) { - val node = getNode(key) - if (node != null) - removeNode(node) - } - - protected open fun removeNode(node: BinNode) { - if ((node.left == null) && (node.right == null)) { - if (node.parent == null) - rootNode = null - else if (node == node.parent?.left) - node.parent?.left = null - else - node.parent?.right = null - } else if (node.left == null) - transplant(node, node.right!!) - else if (node.right == null) - transplant(node, node.left!!) - else { - val nextNode = nextElement(node) - if (nextNode != null) { - if (nextNode.right != null) { - transplant(nextNode, nextNode.right!!) - } - if (nextNode == (nextNode.parent)!!.left) { - (nextNode.parent)!!.left = null - } else { - (nextNode.parent)!!.right = null - } - nextNode.right = node.right - nextNode.left = node.left - nextNode.right?.parent = nextNode - nextNode.left?.parent = nextNode - transplant(node, nextNode) - } - } + override fun remove(key: Key) { + removeNode(key) } } diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index e133ad5..bd8033e 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -12,18 +12,27 @@ class RBTree, Value> : BalanceTree() { } } + override fun insert(key: Key, value: Value) { - val node = insertService(RBNode(key, value)) as RBNode - rebalancing(node) - TODO("add balancing") + val node = insertService(BinNode(key, value)) as RBNode? + rebalancingInsert(node) } override fun remove(key: Key) { + val node = getNode(key) as RBNode? ?: error("remove is not possible: there is no node with this key") + val nextNode = nextElement(node) as RBNode? + if ((node.left != null) && (node.right != null)) { + if ((nextNode?.Black ?: error("remove is not possible: unexpected null")) != node.Black) { + node.swapColor() + nextNode.swapColor() + } + } val parent = removeNode(key) as RBNode? - TODO("will do soon") + rebalancingRemove(parent, node.Black) } - private tailrec fun rebalancing(node: RBNode) { + private tailrec fun rebalancingInsert(node: RBNode?) { + if (node == null) error("can't insert node") val parent = getParent(node.key) as RBNode? if (parent == null) (rootNode as RBNode?)?.Black = true else if (parent.Black) return @@ -34,19 +43,28 @@ class RBTree, Value> : BalanceTree() { parent.swapColor() uncle.swapColor() grandparent.swapColor() - rebalancing(grandparent) + rebalancingInsert(grandparent) } else { if (grandparent.left == parent) { if (parent.right == node) rotation(parent.key, RotationType.Left) val newNode = rotation(grandparent.key, RotationType.Right) as RBNode? - grandparent.swapColor() newNode?.swapColor() ?: error("balancing error") } else { if (parent.left == node) rotation(parent.key, RotationType.Right) val newNode = rotation(grandparent.key, RotationType.Left) as RBNode? - grandparent.swapColor() newNode?.swapColor() ?: error("balancing error") } + grandparent.swapColor() + } + } + } + + protected fun rebalancingRemove(parent: RBNode?, black: Boolean) { + if (parent == null) return + else if (!black) return + else { + if (parent.Black) { + return } } } diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index bb9c594..19cb2fb 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -20,7 +20,7 @@ abstract class BalanceTree, Value> : BinTree() node.right?.parent = it } } - transplant(it, node) + replaceNodeParent(it, node) it.parent = node node.left = it return node diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 6f25dfa..5b9b6d3 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -77,6 +77,37 @@ abstract class BinTree, Value> { return null } + protected fun removeNode(key: Key): BinNode? { + val node: BinNode = getNode(key) ?: return null + if ((node.left == null) && (node.right == null)) { + val parent: BinNode? = node.parent + if (parent == null) + rootNode = null + else if (node == parent.left) + parent.left = null + else + parent.right = null + return parent + } else if (node.left == null) + replaceNodeParent(node, node.right ?: error("remove error: unexpected null")) + else if (node.right == null) + replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) + else { + val nextNode = nextElement(node) + if (nextNode != null) { + val parent = nextNode.parent + nextNode.right?.let { replaceNodeParent(nextNode, it) } + nextNode.right = node.right + nextNode.left = node.left + nextNode.right?.parent = nextNode + nextNode.left?.parent = nextNode + replaceNodeParent(node, nextNode) + return parent + } + } + return node.parent + } + protected fun getParent(key: Key): BinNode? { tailrec fun recFind(curNode: BinNode?): BinNode? { return if (curNode == null) @@ -127,7 +158,7 @@ abstract class BinTree, Value> { protected fun minElement(key: Key): BinNode? { var minNode: BinNode? = getNode(key) ?: return null while (minNode?.left != null) { - minNode = minNode.left ?: error("unexpected null") + minNode = minNode.left ?: error("min element not found: unexpected null") } return minNode } @@ -135,12 +166,12 @@ abstract class BinTree, Value> { protected fun maxElement(key: Key): BinNode? { var maxNode: BinNode? = getNode(key) ?: return null while (maxNode?.right != null) { - maxNode = maxNode.right ?: error("unexpected null") + maxNode = maxNode.right ?: error("max element not found: unexpected null") } return maxNode } - protected open fun transplant(oldNode: BinNode, newNode: BinNode) { + protected open fun replaceNodeParent(oldNode: BinNode, newNode: BinNode) { val parent: BinNode? = oldNode.parent if (parent == null) rootNode = newNode From 20e5c7dd02f29d1f17ec9617a1c028ede479d0ea Mon Sep 17 00:00:00 2001 From: Owner Date: Thu, 6 Apr 2023 22:15:55 +0300 Subject: [PATCH 045/164] feat: insert with autobalancing is implemented --- src/main/kotlin/AVLTree.kt | 47 ++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index b76aca8..7e265a2 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -8,14 +8,47 @@ class AVLTree, Value> : BalanceTree() { var height: UByte = 0U ) : BinNode(key, value, parent, left, right) + private fun createNode(key: Key, value: Value) { + val node = AVLNode + return node + } + + private fun rotationAVL(parentKey: Key, type: RotationType): BinNode? { + val node = rotation(parentKey, type) + + node.height = max(node.left.height, node.right.height) + 1 + node.parent.height = max(node.parent.left.height, node.parent.right.height) + 1 + + return node + } + override fun insert(key: Key, value: Value) { - if (rootNode != null) { - if ((rootNode as AVLNode).height != 255.toUByte()) { - insertService(AVLNode(key, value)) - } - } else - rootNode = AVLNode(key, value) - TODO("add balancing") + val node = createNode(key, value) + + if (key < node.key) node.left = insert(node.left, key) + else if (key > node.key) node.right = insert(node.right, key) + else return node + + node.height = 1 + (max(node.left.height, node.left.right)) + + val heightDifference = node.left.height - node.right.height + + if (heightDifference > 1 and key < node.left.key) { + return rotationAVL(parent.key, RotationType.right) + } + if (heightDifference < -1 and key > node.right.key) { + return rotationAVL(parent.key, RotationType.left) + } + if (heightDifference > 1 and key > node.left.key) { + node.left = rotationAVL(parent.key, RotationType.left) + return rotationAVL(parent.key, RotationType.right) + } + if (heightDifference < -1 and key < node.right.key) { + node.right = rotationAVL(parent.key, RotationType.right) + return rotationAVL(parent.key, RotationType.left) + } + + return node } override fun remove(key: Key) { From 05b753b216d9ff6b4fbeeaac1ef540de1e1f1e55 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 9 Apr 2023 21:42:56 +0300 Subject: [PATCH 046/164] feat: add new dependencies in gradle for junit (bom) --- build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 766f636..0ff6d56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,8 @@ repositories { dependencies { testImplementation(kotlin("test")) + testImplementation(platform("org.junit:junit-bom:5.9.2")) + testImplementation("org.junit.jupiter:junit-jupiter") } tasks.test { From 5c786cbd9d516a2c7bda06d31d1d754e3d9b8090 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 9 Apr 2023 21:44:39 +0300 Subject: [PATCH 047/164] feat: unite some old tests in param tests, add new tests --- src/test/kotlin/BSTreeTest.kt | 131 ++++++++++++++++------------------ 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index abdecc8..4f7fe94 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -1,8 +1,13 @@ import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import kotlin.random.Random import kotlin.test.assertContains -import org.junit.jupiter.api.assertAll class BSTreeTest { fun generateTreeWithInsert(vararg arr: Int): BinTree { @@ -11,37 +16,60 @@ class BSTreeTest { return tree } - @Nested - inner class `Test insert` { - @Test - fun `insert one node test`() { - val expected = "4k" - assertEquals(expected, generateTreeWithInsert(4).get(4)) + fun keysToValues(vararg arr: Int, remove: Int? = null, chValue: Pair? = null): Array { + return Array(arr.size) { + if (arr[it] != remove) { + if (arr[it] == chValue?.first) chValue.second else "${arr[it]}k" + } else null + } + } + + @ParameterizedTest(name = "{2}") + @MethodSource("insertTestsFactory") + @DisplayName("insert-get simple tests") + fun `insert-get simple tests`(arrKeys: Array, extraInsert: Pair?, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + if (extraInsert != null) tree.insert(extraInsert.first, extraInsert.second) + assertArrayEquals(keysToValues(*arrKeys.toIntArray(), chValue = extraInsert), tree.get(*arrKeys).toTypedArray()) + } + + @ParameterizedTest(name = "{2}") + @MethodSource("removeTestsFactory") + @DisplayName("remove tests") + fun `remove tests`(arrKeys: Array, remove: Int, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + tree.remove(remove) + assertArrayEquals(keysToValues(*arrKeys.toIntArray(), remove = remove), tree.get(*arrKeys).toTypedArray()) + } + + companion object { + @JvmStatic + fun insertTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), null, "insert one node test"), + Arguments.of(arrayOf(4), Pair(4, "5k"), "two inserts with eq. keys of the first node"), + Arguments.of( + arrayOf(4, 1, 5, 6), + Pair(4, "5k"), + "two inserts with eq. keys of the first node in non-degenerate tree" + ), + Arguments.of(arrayOf(5, 6, 4), Pair(4, "5k"), "two inserts with eq. keys of node"), + Arguments.of(Array(1000) { Random.nextInt() }, Pair(Random.nextInt(), "random"), "random insert") + ) } - @Nested - inner class `Equal key` { - @Test - fun `two inserts of the first node`() { - val tree = generateTreeWithInsert(4) - tree.insert(4, "5k") - assertEquals("5k", tree.get(4)) - } - - @Test - fun `two inserts of the first node in non-degenerate tree`() { - val tree = generateTreeWithInsert(4, 1, 5, 6) - tree.insert(4, "5k") - assertEquals("5k", tree.get(4)) - } - - - @Test - fun `two inserts of node`() { - val tree = generateTreeWithInsert(5, 6, 4) - tree.insert(4, "5k") - assertEquals("5k", tree.get(4)) - } + @JvmStatic + fun removeTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), 4, "remove one root node"), + Arguments.of(arrayOf(4, 6, 5, 7), 6, "remove non-root node"), + Arguments.of(arrayOf(4, 6, 5, 7), 5, "remove left leaf"), + Arguments.of(arrayOf(4, 6, 5, 7), 7, "remove right leaf"), + Arguments.of(arrayOf(4, 6, 5, 7), 4, "remove root node in non-degenerate tree"), + Arguments.of(Array(0) { it }, 4, "remove in empty tree"), + Arguments.of(arrayOf(2, 1, 3, 5), 4, "remove non-inserted node"), + Arguments.of(Array(1000) { Random.nextInt() }, Random.nextInt(), "random remove") + ) } } @@ -66,47 +94,6 @@ class BSTreeTest { } } - @Nested - inner class `remove node`() { - @Test - fun `remove one root node`() { - val tree = generateTreeWithInsert(4) - tree.remove(4) - assertNull(tree.get(4)) - } - - @Test - fun `remove non-root node`() { - val tree = generateTreeWithInsert(4, 6, 5, 7) - tree.remove(6) - assertAll({ assertNull(tree.get(6)) }, - { assertArrayEquals(arrayOf("4k", "5k", "7k"), tree.get(4, 5, 7).toTypedArray()) }) - } - - @Test - fun `remove left leaf`() { - val tree = generateTreeWithInsert(4, 6, 5, 7) - tree.remove(5) - assertAll({ assertNull(tree.get(5)) }, - { assertArrayEquals(arrayOf("4k", "6k", "7k"), tree.get(4, 6, 7).toTypedArray()) }) - } - - @Test - fun `remove right leaf`() { - val tree = generateTreeWithInsert(4, 6, 5, 7) - tree.remove(7) - assertAll({ assertNull(tree.get(7)) }, - { assertArrayEquals(arrayOf("4k", "6k", "5k"), tree.get(4, 6, 5).toTypedArray()) }) - } - - @Test - fun `remove root node in non-degenerate tree`() { - val tree = generateTreeWithInsert(4, 6, 5, 7) - tree.remove(4) - assertAll({ assertNull(tree.get(4)) }, - { assertArrayEquals(arrayOf("5k", "6k", "7k"), tree.get(5, 6, 7).toTypedArray()) }) - } - } @Nested inner class `tests using debug` { @@ -138,4 +125,6 @@ class BSTreeTest { assertEquals("12 \n7 16 \n6 9 13 18 \n- - - - - 14 ", tree.Debug().treeKeysInString()) } } + + } From 29b61c5c8fed37fd8458afb3c4cd71d0bc2bc367 Mon Sep 17 00:00:00 2001 From: Owner Date: Mon, 10 Apr 2023 22:55:44 +0300 Subject: [PATCH 048/164] feat: balancing is implemented --- src/main/kotlin/AVLTree.kt | 112 +++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 7e265a2..e2066d7 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,5 +1,5 @@ class AVLTree, Value> : BalanceTree() { - protected class AVLNode, Value>( + private class AVLNode, Value>( key: Key, value: Value, parent: AVLNode? = null, @@ -8,50 +8,78 @@ class AVLTree, Value> : BalanceTree() { var height: UByte = 0U ) : BinNode(key, value, parent, left, right) - private fun createNode(key: Key, value: Value) { - val node = AVLNode - return node + override fun insert(key: Key, value: Value) { + if (rootNode != null) { + if ((rootNode as AVLNode).height != 255.toUByte()) { + insertService(AVLNode(key, value)) + } + } else + rootNode = AVLNode(key, value) + balance(rootNode as AVLNode) } - - private fun rotationAVL(parentKey: Key, type: RotationType): BinNode? { - val node = rotation(parentKey, type) - - node.height = max(node.left.height, node.right.height) + 1 - node.parent.height = max(node.parent.left.height, node.parent.right.height) + 1 - - return node + override fun remove(key: Key) { + TODO("Not yet implemented") } - - override fun insert(key: Key, value: Value) { - val node = createNode(key, value) - - if (key < node.key) node.left = insert(node.left, key) - else if (key > node.key) node.right = insert(node.right, key) - else return node - - node.height = 1 + (max(node.left.height, node.left.right)) - - val heightDifference = node.left.height - node.right.height - - if (heightDifference > 1 and key < node.left.key) { - return rotationAVL(parent.key, RotationType.right) - } - if (heightDifference < -1 and key > node.right.key) { - return rotationAVL(parent.key, RotationType.left) - } - if (heightDifference > 1 and key > node.left.key) { - node.left = rotationAVL(parent.key, RotationType.left) - return rotationAVL(parent.key, RotationType.right) + private fun balance(node: AVLNode) { + if (node.left != null && node.right != null) { + if ((node.left as AVLNode).height > (node.right as AVLNode).height + 1.toUByte()) { + if ((node.left!!.left as AVLNode).height >= (node.left!!.right as AVLNode).height) { + rotateRight(node) + } else { + rotateLeft(node.left!! as AVLNode) + rotateRight(node) + } + } else if ((node.right as AVLNode).height > (node.left as AVLNode).height + 1.toUByte()) { + if ((node.right!!.right as AVLNode).height >= (node.right!!.left as AVLNode).height) { + rotateLeft(node) + } else { + rotateRight(node.right!! as AVLNode) + rotateLeft(node) + } + } + updateHeight(node) + if (node.parent != null) { + balance(node.parent!! as AVLNode) + } } - if (heightDifference < -1 and key < node.right.key) { - node.right = rotationAVL(parent.key, RotationType.right) - return rotationAVL(parent.key, RotationType.left) + } + private fun updateHeight(node: AVLNode) { + val left = node.left?.let { (it as AVLNode).height } ?: 0U + val right = node.right?.let { (it as AVLNode).height } ?: 0U + node.height = (maxOf(left, right) + 1.toUByte()).toUByte() + } + private fun rotateLeft(node: AVLNode) { + val child = node.right!! + child.parent = node.parent + if (node.parent == null) { + rootNode = child + } else if (node.parent!!.left == node) { + node.parent!!.left = child + } else { + node.parent!!.right = child } - - return node + node.right = child.left + child.left?.parent = node + node.parent = child + child.left = node + updateHeight(node) + updateHeight(child as AVLNode) } - - override fun remove(key: Key) { - TODO("Not yet implemented") + private fun rotateRight(node: AVLNode) { + val child = node.left!! + child.parent = node.parent + if (node.parent == null) { + rootNode = child + } else if (node.parent!!.left == node) { + node.parent!!.left = child + } else { + node.parent!!.right = child + } + node.left = child.right + child.right?.parent = node + node.parent = child + child.right = node + updateHeight(node) + updateHeight(child as AVLNode) } -} +} \ No newline at end of file From 0b903137675c77f9d36b4c26e5c4c31df02a9132 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 11 Apr 2023 02:18:31 +0300 Subject: [PATCH 049/164] fix: fixed bugs in the rotation fun --- src/main/kotlin/abstractBalanceTree.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 19cb2fb..48f44dc 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -5,24 +5,25 @@ abstract class BalanceTree, Value> : BinTree() //giving the parentNode key val parent: BinNode? = getNode(parentKey) parent?.let { - val node = if (type.name == "Left") + val node = if (type == RotationType.Left) it.right ?: error("rotation is not possible") else it.left ?: error("rotation is not possible") - when (type.name) { - "Left" -> { + when (type) { + RotationType.Left -> { it.right = node.left node.left?.parent = it + node.left = it } - "Right" -> { + RotationType.Right -> { it.left = node.right node.right?.parent = it + node.right = it } } replaceNodeParent(it, node) it.parent = node - node.left = it return node } return null From 0ab6adeed926bbe335c389c87bf9d6ef01facc05 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Fri, 14 Apr 2023 21:39:28 +0300 Subject: [PATCH 050/164] feat: add new test with non-standard struct --- src/test/kotlin/BSTreeTest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index 4f7fe94..ed9aecf 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -126,5 +126,18 @@ class BSTreeTest { } } + @Test + fun `my struct`() { + class my( + val arg1: String + ) : Comparable { + override fun compareTo(other: my): Int = arg1.compareTo(other.arg1) + } + val tree = BSTree(Pair(my("11"), 1), Pair(my("111"), 111), Pair(my("321"), 321)) + tree.remove(my("321")) + assertAll({ assertEquals(1, tree.get(my("11"))) }, + { assertEquals(111, tree.get(my("111"))) }, + { assertNull(tree.get(my("321"))) }) + } } From 8535f65fc7e6137d4b16671cfac53e76c5b8b423 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sun, 16 Apr 2023 19:05:26 +0300 Subject: [PATCH 051/164] fix: fun removeService --- src/main/kotlin/abstractBinTree.kt | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 5b9b6d3..5548dca 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,8 +1,9 @@ import kotlin.math.abs + abstract class BinTree, Value> { protected open class BinNode, Value>( - val key: Key, + var key: Key, var value: Value, var parent: BinNode? = null, var left: BinNode? = null, @@ -77,8 +78,7 @@ abstract class BinTree, Value> { return null } - protected fun removeNode(key: Key): BinNode? { - val node: BinNode = getNode(key) ?: return null + protected fun removeService(node: BinNode) { if ((node.left == null) && (node.right == null)) { val parent: BinNode? = node.parent if (parent == null) @@ -87,25 +87,22 @@ abstract class BinTree, Value> { parent.left = null else parent.right = null - return parent } else if (node.left == null) replaceNodeParent(node, node.right ?: error("remove error: unexpected null")) else if (node.right == null) replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) else { - val nextNode = nextElement(node) - if (nextNode != null) { - val parent = nextNode.parent + val nextNode = nextElement(node) ?: error("remove error: unexpected null") + val parent = nextNode.parent + if (parent != node) { nextNode.right?.let { replaceNodeParent(nextNode, it) } nextNode.right = node.right - nextNode.left = node.left nextNode.right?.parent = nextNode - nextNode.left?.parent = nextNode - replaceNodeParent(node, nextNode) - return parent } + nextNode.left = node.left + nextNode.left?.parent = nextNode + replaceNodeParent(node, nextNode) } - return node.parent } protected fun getParent(key: Key): BinNode? { @@ -171,7 +168,7 @@ abstract class BinTree, Value> { return maxNode } - protected open fun replaceNodeParent(oldNode: BinNode, newNode: BinNode) { + protected open fun replaceNodeParent(oldNode: BinNode, newNode: BinNode?) { val parent: BinNode? = oldNode.parent if (parent == null) rootNode = newNode @@ -180,7 +177,7 @@ abstract class BinTree, Value> { } else { parent.right = newNode } - newNode.parent = parent + newNode?.let { it.parent = parent} } protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean) { From a862322fd14f26e2ca6577fdd61b8c59a9ba7fba Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sun, 16 Apr 2023 19:06:04 +0300 Subject: [PATCH 052/164] fix: fun removeService for BSTree --- src/main/kotlin/BSTree.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index cd270a1..5e69e8d 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -8,6 +8,7 @@ open class BSTree, Value> : BinTree { } override fun remove(key: Key) { - removeNode(key) + val node = getNode(key) ?: error("remove is not possible: there is no node with this key") + removeService(node) } } From b31e509345487a6610cc481f08b4ee5946824309 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sun, 16 Apr 2023 19:07:24 +0300 Subject: [PATCH 053/164] fix: fun rotation accepts a node --- src/main/kotlin/abstractBalanceTree.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 48f44dc..bb7c70a 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,11 +1,10 @@ abstract class BalanceTree, Value> : BinTree() { enum class RotationType { Left, Right } - protected fun rotation(parentKey: Key, type: RotationType): BinNode? { - //giving the parentNode key - val parent: BinNode? = getNode(parentKey) + protected fun rotation(parent: BinNode?, type: RotationType): BinNode? { + //giving the parentNode parent?.let { - val node = if (type == RotationType.Left) + val node = if (type == RotationType.Left) it.right ?: error("rotation is not possible") else it.left ?: error("rotation is not possible") From 73e5424cb46b3a4849a351b567aec2dbcd572b91 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Sun, 16 Apr 2023 19:08:14 +0300 Subject: [PATCH 054/164] feat: fun remove for RBTree --- src/main/kotlin/RBTree.kt | 167 ++++++++++++++++++++++++++++---------- 1 file changed, 123 insertions(+), 44 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index bd8033e..603fc38 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,3 +1,6 @@ +const val RED = false +const val BLACK = true + class RBTree, Value> : BalanceTree() { protected class RBNode, Value>( key: Key, @@ -5,10 +8,10 @@ class RBTree, Value> : BalanceTree() { parent: RBNode? = null, left: RBNode? = null, right: RBNode? = null, - var Black: Boolean = false + var color: Boolean = RED ) : BinNode(key, value, parent, left, right) { fun swapColor() { - Black = !Black + color = if (color == BLACK) RED else BLACK } } @@ -19,39 +22,79 @@ class RBTree, Value> : BalanceTree() { } override fun remove(key: Key) { - val node = getNode(key) as RBNode? ?: error("remove is not possible: there is no node with this key") - val nextNode = nextElement(node) as RBNode? + val node = getNode(key) as RBNode? ?: return + val removeNode: RBNode + //find removeNode if ((node.left != null) && (node.right != null)) { - if ((nextNode?.Black ?: error("remove is not possible: unexpected null")) != node.Black) { - node.swapColor() - nextNode.swapColor() + val nextNode = nextElement(node) as RBNode? ?: error("remove is not possible: unexpected null") + node.key = nextNode.key + node.value = nextNode.value + removeNode = nextNode + } else removeNode = node + + //delete node without child + if ((removeNode.left == null) && (removeNode.right == null)) { + val parent: BinNode? = removeNode.parent + if (parent == null) + rootNode = null + else if (removeNode.color == RED) + replaceNodeParent(removeNode, null) + + //special case for black node without child + else { + rebalancingRemove(removeNode) + replaceNodeParent(removeNode, null) } + + } + //delete black node with one red child + else if (node.left == null) { + replaceNodeParent(node, node.right ?: error("remove error: unexpected null")) + (node.right as RBNode).swapColor() + } else { + replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) + (node.left as RBNode).swapColor() } - val parent = removeNode(key) as RBNode? - rebalancingRemove(parent, node.Black) } - private tailrec fun rebalancingInsert(node: RBNode?) { + protected fun getGrandparent(node: RBNode?): RBNode? { + val parent = node?.parent + parent?.let { it -> it.parent?.let { return it as RBNode } } + return null + } + + protected fun getSibling(node: RBNode?): RBNode? { + val parent = node?.parent ?: return null + return if (parent.left == node) parent.right as RBNode? + else parent.left as RBNode? + } + + protected fun getUncle(node: RBNode?): RBNode? { + val parent = node?.parent ?: return null + return getSibling(parent as RBNode?) + } + + private fun rebalancingInsert(node: RBNode?) { if (node == null) error("can't insert node") val parent = getParent(node.key) as RBNode? - if (parent == null) (rootNode as RBNode?)?.Black = true - else if (parent.Black) return + if (parent == null) (rootNode as RBNode?)?.color = BLACK + else if (parent.color == BLACK) return else { - val uncle = getUncle(node.key) ?: error("balancing error") - val grandparent = getGrandparent(node.key) ?: error("balancing error") - if (!uncle.Black) { + val uncle = getUncle(node) ?: error("balancing error") + val grandparent = getGrandparent(node) ?: error("balancing error") + if (uncle.color == RED) { parent.swapColor() uncle.swapColor() grandparent.swapColor() rebalancingInsert(grandparent) } else { if (grandparent.left == parent) { - if (parent.right == node) rotation(parent.key, RotationType.Left) - val newNode = rotation(grandparent.key, RotationType.Right) as RBNode? + if (parent.right == node) rotation(parent, RotationType.Left) + val newNode = rotation(grandparent, RotationType.Right) as RBNode? newNode?.swapColor() ?: error("balancing error") } else { - if (parent.left == node) rotation(parent.key, RotationType.Right) - val newNode = rotation(grandparent.key, RotationType.Left) as RBNode? + if (parent.left == node) rotation(parent, RotationType.Right) + val newNode = rotation(grandparent, RotationType.Left) as RBNode? newNode?.swapColor() ?: error("balancing error") } grandparent.swapColor() @@ -59,31 +102,67 @@ class RBTree, Value> : BalanceTree() { } } - protected fun rebalancingRemove(parent: RBNode?, black: Boolean) { - if (parent == null) return - else if (!black) return - else { - if (parent.Black) { - return - } - } - } - protected fun getGrandparent(key: Key): RBNode? { - val parent = getParent(key) - parent?.let { it -> it.parent?.let { return it as RBNode } } - return null - } + protected fun rebalancingRemove(removeNode: RBNode?) { + var node = removeNode - protected fun getBrother(key: Key): RBNode? { - val node = getNode(key) - val parent = getParent(key) ?: return null - return if (parent.left == node) parent.right as RBNode? - else parent.left as RBNode? - } - - protected fun getUncle(key: Key): RBNode? { - val parent = getParent(key) ?: return null - return getBrother(parent.key) + while ((node != rootNode) && (node?.color == BLACK)) { + var brother = getSibling(node) + //balancing when a node is the left child of its parent + if (node == node.parent?.left) { + if (brother?.color == RED) { + (node.parent as RBNode?)?.swapColor() + brother.swapColor() + rotation(node.parent, RotationType.Left) + brother = (node.parent as RBNode?)?.right as RBNode? + } + if ((((brother?.left as RBNode?) == null) || (brother?.left as RBNode?)?.color == BLACK) && + (((brother?.right as RBNode?) == null) || (brother?.right as RBNode?)?.color == BLACK) + ) { + brother?.color = RED + node = node.parent as RBNode + } else { + if ((brother?.right as RBNode?)?.color == BLACK) { + (brother?.left as RBNode?)?.color = BLACK + brother?.color = RED + rotation(brother, RotationType.Right) + brother = node.parent?.right as RBNode? + } + brother?.color = (node.parent as RBNode).color + (node.parent as RBNode).color = BLACK + (brother?.right as RBNode).color = BLACK + rotation(node.parent as RBNode?, RotationType.Left) + node = rootNode as RBNode + } + } + //balancing when a node is the right child of its parent + else { + if (brother?.color == RED) { + (node.parent as RBNode?)?.swapColor() + brother.swapColor() + rotation(node.parent, RotationType.Right) + brother = (node.parent as RBNode?)?.left as RBNode? + } + if ((((brother?.left as RBNode?) == null) || (brother?.left as RBNode?)?.color == BLACK) && + (((brother?.right as RBNode?) == null) || (brother?.right as RBNode?)?.color == BLACK) + ) { + brother?.color = RED + node = node.parent as RBNode + } else { + if ((brother?.left as RBNode?)?.color == BLACK) { + (brother?.right as RBNode?)?.color = BLACK + brother?.color = RED + rotation(brother, RotationType.Left) + brother = node.parent?.left as RBNode? + } + brother?.color = (node.parent as RBNode).color + (node.parent as RBNode).color = BLACK + (brother?.left as RBNode).color = BLACK + rotation(node.parent as RBNode?, RotationType.Right) + node = rootNode as RBNode + } + } + node.color = BLACK + } } -} +} \ No newline at end of file From 90280c4867651522852888b2f3c1d8806b6213ed Mon Sep 17 00:00:00 2001 From: Owner Date: Sun, 16 Apr 2023 21:13:17 +0300 Subject: [PATCH 055/164] remove is implemented --- src/main/kotlin/AVLTree.kt | 48 +++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index e2066d7..0fc6e78 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -17,8 +17,54 @@ class AVLTree, Value> : BalanceTree() { rootNode = AVLNode(key, value) balance(rootNode as AVLNode) } + + private fun searchNode(key: Key, node: AVLNode?): AVLNode? { + if (node == null || key == node.key) { + return node + } + return if (key < node.key) { + searchNode(key, node.left as AVLNode?) + } else { + searchNode(key, node.right as AVLNode?) + } + } + private fun transplant(node: AVLNode, newNode: AVLNode) { + if (node.parent == null) { + rootNode = newNode + } else if (node == node.parent?.left) { + node.parent?.left = newNode + } else { + node.parent?.right = newNode + } + newNode.parent = node.parent + } + + private fun minNode(node: AVLNode): AVLNode { + var currentNode = node + while (currentNode.left != null) { + currentNode = currentNode.left as AVLNode + } + return currentNode + } + override fun remove(key: Key) { - TODO("Not yet implemented") + val node = searchNode(key, rootNode as AVLNode) ?: return + when { + node.left == null -> transplant(node, node.right as AVLNode) + node.right == null -> transplant(node, node.left as AVLNode) + else -> { + val successor = minNode(node.right!! as AVLNode) + if (successor.parent != node) { + transplant(successor, successor.right as AVLNode) + successor.right = node.right + successor.right?.parent = successor + } + transplant(node, successor) + successor.left = node.left + successor.left?.parent = successor + } + } + balance(node.parent as AVLNode) } private fun balance(node: AVLNode) { if (node.left != null && node.right != null) { From 440736029ff140c55202e82000787177f54722e1 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 16 Apr 2023 22:04:47 +0300 Subject: [PATCH 056/164] refactor: moved Parametized tests under companion object --- src/test/kotlin/BSTreeTest.kt | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index ed9aecf..e25c935 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -24,24 +24,6 @@ class BSTreeTest { } } - @ParameterizedTest(name = "{2}") - @MethodSource("insertTestsFactory") - @DisplayName("insert-get simple tests") - fun `insert-get simple tests`(arrKeys: Array, extraInsert: Pair?, name: String) { - val tree = generateTreeWithInsert(*arrKeys.toIntArray()) - if (extraInsert != null) tree.insert(extraInsert.first, extraInsert.second) - assertArrayEquals(keysToValues(*arrKeys.toIntArray(), chValue = extraInsert), tree.get(*arrKeys).toTypedArray()) - } - - @ParameterizedTest(name = "{2}") - @MethodSource("removeTestsFactory") - @DisplayName("remove tests") - fun `remove tests`(arrKeys: Array, remove: Int, name: String) { - val tree = generateTreeWithInsert(*arrKeys.toIntArray()) - tree.remove(remove) - assertArrayEquals(keysToValues(*arrKeys.toIntArray(), remove = remove), tree.get(*arrKeys).toTypedArray()) - } - companion object { @JvmStatic fun insertTestsFactory(): Stream { @@ -73,6 +55,24 @@ class BSTreeTest { } } + @ParameterizedTest(name = "{2}") + @MethodSource("insertTestsFactory") + @DisplayName("insert-get simple tests") + fun `insert-get simple tests`(arrKeys: Array, extraInsert: Pair?, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + if (extraInsert != null) tree.insert(extraInsert.first, extraInsert.second) + assertArrayEquals(keysToValues(*arrKeys.toIntArray(), chValue = extraInsert), tree.get(*arrKeys).toTypedArray()) + } + + @ParameterizedTest(name = "{2}") + @MethodSource("removeTestsFactory") + @DisplayName("remove tests") + fun `remove tests`(arrKeys: Array, remove: Int, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + tree.remove(remove) + assertArrayEquals(keysToValues(*arrKeys.toIntArray(), remove = remove), tree.get(*arrKeys).toTypedArray()) + } + @Nested inner class `constructors test` { @Test From dc89ccfbc39454056e883ce515cf074169a9ce16 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 16 Apr 2023 23:55:16 +0300 Subject: [PATCH 057/164] feat: add constructors to AVLTree --- src/main/kotlin/AVLTree.kt | 6 +++++- src/main/kotlin/abstractBalanceTree.kt | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 688f610..2876793 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,4 +1,4 @@ -class AVLTree, Value> : BalanceTree() { +class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, value: Value, @@ -8,6 +8,10 @@ class AVLTree, Value> : BalanceTree() { var height: UByte = 0U ) : BinNode(key, value, parent, left, right) + constructor() : super() + constructor(key: Key, value: Value) : super(key, value) + constructor(vararg pairs: Pair) : super(pairs) + override fun insert(key: Key, value: Value) { if (rootNode != null) { if ((rootNode as AVLNode).height != 255.toUByte()) { diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index 9a95997..9a376b5 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,4 +1,8 @@ -abstract class BalanceTree, Value> : BinTree() { +abstract class BalanceTree, Value> : BinTree { + constructor() : super() + constructor(key: Key, value: Value) : super(key, value) + constructor(pairs: Array>) : super(pairs) + protected abstract fun rebalancing() protected abstract fun leftRotation() protected abstract fun rightRotation() From 738b697f5975c5dcfd7ea6f6bbaff28dc3e19187 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 16 Apr 2023 23:55:43 +0300 Subject: [PATCH 058/164] test: add tests for AVLTree --- src/test/kotlin/AVLTreeTest.kt | 152 +++++++++++++++++++++++++++++++++ src/test/kotlin/BSTreeTest.kt | 16 ++-- 2 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 src/test/kotlin/AVLTreeTest.kt diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt new file mode 100644 index 0000000..1179860 --- /dev/null +++ b/src/test/kotlin/AVLTreeTest.kt @@ -0,0 +1,152 @@ +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import kotlin.random.Random +import kotlin.test.assertContains + +class AVLTreeTest { + fun generateTreeWithInsert(vararg arr: Int): BinTree { + val tree = AVLTree() + for (i in arr) tree.insert(i, "${i}k") + return tree + } + + companion object { + @JvmStatic + fun insertTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), null, "insert one node test"), + Arguments.of(arrayOf(4), Pair(4, "5k"), "two inserts with eq. keys of the first node"), + Arguments.of( + arrayOf(4, 1, 5, 6), + Pair(4, "5k"), + "two inserts with eq. keys of the first node in non-degenerate tree" + ), + Arguments.of(arrayOf(5, 6, 4), Pair(4, "5k"), "two inserts with eq. keys of node"), + Arguments.of(Array(1000) { Random.nextInt() }, Pair(Random.nextInt(), "random"), "random insert") + ) + } + + @JvmStatic + fun removeTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), 4, "remove one root node"), + Arguments.of(arrayOf(4, 6, 5, 7), 6, "remove non-root node"), + Arguments.of(arrayOf(4, 6, 5, 7), 4, "remove left leaf"), + Arguments.of(arrayOf(4, 6, 5, 7), 7, "remove right leaf"), + Arguments.of(arrayOf(4, 6, 5, 7), 5, "remove root node in non-degenerate tree"), + Arguments.of(Array(0) { it }, 4, "remove in empty tree"), + Arguments.of(arrayOf(2, 1, 3, 5), 4, "remove non-inserted node"), + Arguments.of(Array(1000) { Random.nextInt() }, Random.nextInt(), "random remove") + ) + } + + @JvmStatic + fun debugTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4, 2, 1), "2 \n1 4 \n", "right rotation"), + Arguments.of(arrayOf(2, 5, 4, 3, 1, 0), "2 \n1 4 \n0 - 3 5 \n", "difficult right rotation 1"), + Arguments.of(arrayOf(2, 5, 4, 3, 0, 1), "2 \n0 4 \n- 1 3 5 \n", "difficult right rotation 2"), + Arguments.of(arrayOf(2, 1, 4, 3, 5, 6), "4 \n2 5\n1 3 - 6 \n", "difficult left rotation 1"), + Arguments.of(arrayOf(2, 1, 4, 3, 6, 5), "4 \n2 6\n1 3 5 - \n", "difficult left rotation 2"), + Arguments.of(arrayOf(1, 3, 2), "2 \n1 3 \n", "right left rotation"), + Arguments.of(arrayOf(3, 1, 2), "2 \n1 3 \n", "left right rotation"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), "6 \n4 8\n2 5 7 - \n1 3 ", "combine"), + ) + } + + @JvmStatic + fun debugRemoveTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 1, "6 \n4 8\n2 5 7 - \n- 3 ", "remove right leaf"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 3, "6 \n4 8\n2 5 7 - \n1 ", "remove left leaf"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 2, "6 \n4 8\n1 5 7 - \n- 3 ", "remove root of two leafs"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 8, "4 \n2 6\n1 3 5 7 \n", "remove with rebalancing"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 6, "5 \n2 8\n1 4 7 - \n- - 3 ", "remove root"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 4, "6 \n3 8\n2 5 7 - \n1 ", "one more remove"), + ) + } + } + + @ParameterizedTest(name = "{2}") + @MethodSource("insertTestsFactory") + @DisplayName("insert-get simple tests") + fun `insert-get simple tests`(arrKeys: Array, extraInsert: Pair?, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + if (extraInsert != null) tree.insert(extraInsert.first, extraInsert.second) + Assertions.assertArrayEquals( + keysToValues(*arrKeys.toIntArray(), chValue = extraInsert), tree.get(*arrKeys).toTypedArray() + ) + } + + @ParameterizedTest(name = "{2}") + @MethodSource("removeTestsFactory") + @DisplayName("remove tests") + fun `remove tests`(arrKeys: Array, remove: Int, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + tree.remove(remove) + Assertions.assertArrayEquals( + keysToValues(*arrKeys.toIntArray(), remove = remove), tree.get(*arrKeys).toTypedArray() + ) + } + + @ParameterizedTest(name = "{2}") + @MethodSource("debugTestsFactory") + @DisplayName("insert tests using debug") + fun testsWithDebug(keys: Array, treeInString: String) { + val tree = generateTreeWithInsert(*keys.toIntArray()) + Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) + } + + @ParameterizedTest(name = "{2}") + @MethodSource("debugRemoveTestsFactory") + @DisplayName("remove tests using debug") + fun removeTestsWithDebug(keys: Array, remove: Int, treeInString: String) { + val tree = generateTreeWithInsert(*keys.toIntArray()) + tree.remove(remove) + Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) + } + + @Nested + inner class `constructors test` { + @Test + fun `insert key, value`() { + val tree = AVLTree(4, "4k") + Assertions.assertEquals("4k", tree.get(4)) + } + + @Test + fun `insert two node`() { + val tree = AVLTree(Pair(4, "4k"), Pair(5, "5k")) + Assertions.assertArrayEquals(arrayOf("4k", "5k"), tree.get(4, 5).toTypedArray()) + } + + @Test + fun `insert equal nodes`() { + val tree = AVLTree(Pair(4, "4k"), Pair(5, "5k"), Pair(4, "7k")) + Assertions.assertAll({ assertContains(arrayOf("4k", "7k"), tree.get(4)) }, + { Assertions.assertEquals("5k", tree.get(5)) }) + } + } + + + @Test + fun `my struct`() { + class my( + val arg1: String + ) : Comparable { + override fun compareTo(other: my): Int = arg1.compareTo(other.arg1) + } + + val tree = AVLTree(Pair(my("11"), 1), Pair(my("111"), 111), Pair(my("321"), 321)) + tree.remove(my("321")) + Assertions.assertAll({ Assertions.assertEquals(1, tree.get(my("11"))) }, + { Assertions.assertEquals(111, tree.get(my("111"))) }, + { Assertions.assertNull(tree.get(my("321"))) }) + } +} diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index e25c935..3bc1765 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -9,6 +9,14 @@ import java.util.stream.Stream import kotlin.random.Random import kotlin.test.assertContains +fun keysToValues(vararg arr: Int, remove: Int? = null, chValue: Pair? = null): Array { + return Array(arr.size) { + if (arr[it] != remove) { + if (arr[it] == chValue?.first) chValue.second else "${arr[it]}k" + } else null + } +} + class BSTreeTest { fun generateTreeWithInsert(vararg arr: Int): BinTree { val tree = BSTree() @@ -16,14 +24,6 @@ class BSTreeTest { return tree } - fun keysToValues(vararg arr: Int, remove: Int? = null, chValue: Pair? = null): Array { - return Array(arr.size) { - if (arr[it] != remove) { - if (arr[it] == chValue?.first) chValue.second else "${arr[it]}k" - } else null - } - } - companion object { @JvmStatic fun insertTestsFactory(): Stream { From ccd61ec2e089d85d88713096cfbfa2ece6049170 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 17 Apr 2023 01:18:35 +0300 Subject: [PATCH 059/164] refactor: change List in BFS to Queue --- src/main/kotlin/abstractBinTree.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 54e5002..8541bd9 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,3 +1,5 @@ +import java.util.LinkedList +import java.util.Queue import kotlin.math.abs abstract class BinTree, Value> { @@ -120,7 +122,7 @@ abstract class BinTree, Value> { } protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean) { - val queue = mutableListOf(rootNode) + val queue: Queue?> = LinkedList(listOf(rootNode)) fun notNullInQueue(): Boolean { for (i in queue) @@ -130,15 +132,14 @@ abstract class BinTree, Value> { } while (queue.isNotEmpty()) { - val node = queue.last() - queue.removeLast() + val node = queue.remove() function(node) if (node != null) { - queue.add(0, node.left) - queue.add(0, node.right) + queue.add(node.left) + queue.add(node.right) } else if (addNullNodes) { - queue.add(0, null) - queue.add(0, null) + queue.add(null) + queue.add(null) } if (!notNullInQueue()) return From 5d972f245dc26ed50385f4edd0a1bc06c0d009cf Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 17 Apr 2023 21:35:58 +0300 Subject: [PATCH 060/164] fix: fun remove for BinTree --- src/main/kotlin/abstractBinTree.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 5548dca..052218f 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -93,9 +93,10 @@ abstract class BinTree, Value> { replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) else { val nextNode = nextElement(node) ?: error("remove error: unexpected null") - val parent = nextNode.parent + val parent = nextNode.parent ?: error("remove error: unexpected null") if (parent != node) { - nextNode.right?.let { replaceNodeParent(nextNode, it) } + if (nextNode.right != null) replaceNodeParent(nextNode, nextNode.right) + else parent.left = null nextNode.right = node.right nextNode.right?.parent = nextNode } From 374f15bdb0859d08ac96c2ab214a836e26daff06 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 17 Apr 2023 21:41:23 +0300 Subject: [PATCH 061/164] fix: delete error message from remove BSTree --- src/main/kotlin/BSTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 5e69e8d..84b19aa 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -8,7 +8,7 @@ open class BSTree, Value> : BinTree { } override fun remove(key: Key) { - val node = getNode(key) ?: error("remove is not possible: there is no node with this key") + val node = getNode(key) ?: return removeService(node) } } From 5331dff5fbc6383e1a7da94fbd2412f04173299b Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 17 Apr 2023 21:44:07 +0300 Subject: [PATCH 062/164] refactor: rename rotation types --- src/main/kotlin/RBTree.kt | 20 ++++++++++---------- src/main/kotlin/abstractBalanceTree.kt | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 603fc38..ee0e6da 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -89,12 +89,12 @@ class RBTree, Value> : BalanceTree() { rebalancingInsert(grandparent) } else { if (grandparent.left == parent) { - if (parent.right == node) rotation(parent, RotationType.Left) - val newNode = rotation(grandparent, RotationType.Right) as RBNode? + if (parent.right == node) rotation(parent, RotationType.LEFT) + val newNode = rotation(grandparent, RotationType.RIGHT) as RBNode? newNode?.swapColor() ?: error("balancing error") } else { - if (parent.left == node) rotation(parent, RotationType.Right) - val newNode = rotation(grandparent, RotationType.Left) as RBNode? + if (parent.left == node) rotation(parent, RotationType.RIGHT) + val newNode = rotation(grandparent, RotationType.LEFT) as RBNode? newNode?.swapColor() ?: error("balancing error") } grandparent.swapColor() @@ -113,7 +113,7 @@ class RBTree, Value> : BalanceTree() { if (brother?.color == RED) { (node.parent as RBNode?)?.swapColor() brother.swapColor() - rotation(node.parent, RotationType.Left) + rotation(node.parent, RotationType.LEFT) brother = (node.parent as RBNode?)?.right as RBNode? } if ((((brother?.left as RBNode?) == null) || (brother?.left as RBNode?)?.color == BLACK) && @@ -125,13 +125,13 @@ class RBTree, Value> : BalanceTree() { if ((brother?.right as RBNode?)?.color == BLACK) { (brother?.left as RBNode?)?.color = BLACK brother?.color = RED - rotation(brother, RotationType.Right) + rotation(brother, RotationType.RIGHT) brother = node.parent?.right as RBNode? } brother?.color = (node.parent as RBNode).color (node.parent as RBNode).color = BLACK (brother?.right as RBNode).color = BLACK - rotation(node.parent as RBNode?, RotationType.Left) + rotation(node.parent as RBNode?, RotationType.LEFT) node = rootNode as RBNode } } @@ -140,7 +140,7 @@ class RBTree, Value> : BalanceTree() { if (brother?.color == RED) { (node.parent as RBNode?)?.swapColor() brother.swapColor() - rotation(node.parent, RotationType.Right) + rotation(node.parent, RotationType.RIGHT) brother = (node.parent as RBNode?)?.left as RBNode? } if ((((brother?.left as RBNode?) == null) || (brother?.left as RBNode?)?.color == BLACK) && @@ -152,13 +152,13 @@ class RBTree, Value> : BalanceTree() { if ((brother?.left as RBNode?)?.color == BLACK) { (brother?.right as RBNode?)?.color = BLACK brother?.color = RED - rotation(brother, RotationType.Left) + rotation(brother, RotationType.LEFT) brother = node.parent?.left as RBNode? } brother?.color = (node.parent as RBNode).color (node.parent as RBNode).color = BLACK (brother?.left as RBNode).color = BLACK - rotation(node.parent as RBNode?, RotationType.Right) + rotation(node.parent as RBNode?, RotationType.RIGHT) node = rootNode as RBNode } } diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractBalanceTree.kt index bb7c70a..d589874 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractBalanceTree.kt @@ -1,21 +1,21 @@ abstract class BalanceTree, Value> : BinTree() { - enum class RotationType { Left, Right } + enum class RotationType { LEFT, RIGHT } protected fun rotation(parent: BinNode?, type: RotationType): BinNode? { //giving the parentNode parent?.let { - val node = if (type == RotationType.Left) + val node = if (type == RotationType.LEFT) it.right ?: error("rotation is not possible") else it.left ?: error("rotation is not possible") when (type) { - RotationType.Left -> { + RotationType.LEFT -> { it.right = node.left node.left?.parent = it node.left = it } - RotationType.Right -> { + RotationType.RIGHT -> { it.left = node.right node.right?.parent = it node.right = it From 5fa6b5c7f212792b0fdfcbd65eb93db4321ccc46 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 17 Apr 2023 22:48:46 +0300 Subject: [PATCH 063/164] test: disable AVLTreeTest --- src/test/kotlin/AVLTreeTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt index 1179860..13048e5 100644 --- a/src/test/kotlin/AVLTreeTest.kt +++ b/src/test/kotlin/AVLTreeTest.kt @@ -1,4 +1,5 @@ import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -9,6 +10,7 @@ import java.util.stream.Stream import kotlin.random.Random import kotlin.test.assertContains +@Disabled class AVLTreeTest { fun generateTreeWithInsert(vararg arr: Int): BinTree { val tree = AVLTree() From 31b0c9a8e8724f839506d24fbb197d9af607affa Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 02:56:05 +0300 Subject: [PATCH 064/164] test: tests for RBTree --- src/test/kotlin/RBTreeTest.kt | 169 ++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/test/kotlin/RBTreeTest.kt diff --git a/src/test/kotlin/RBTreeTest.kt b/src/test/kotlin/RBTreeTest.kt new file mode 100644 index 0000000..b41dfcd --- /dev/null +++ b/src/test/kotlin/RBTreeTest.kt @@ -0,0 +1,169 @@ +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import kotlin.random.Random +import kotlin.test.assertContains + +class RBTreeTest { + fun generateTreeWithInsert(vararg arr: Int): RBTree { + val tree = RBTree() + for (i in arr) tree.insert(i, "${i}k") + return tree + } + + companion object { + @JvmStatic + fun insertTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), null, "insert one node test"), + Arguments.of(arrayOf(4), Pair(4, "5k"), "two inserts with eq. keys of the first node"), + Arguments.of( + arrayOf(4, 1, 5, 6), + Pair(4, "5k"), + "two inserts with eq. keys of the first node in non-degenerate tree" + ), + Arguments.of(arrayOf(5, 6, 4), Pair(4, "5k"), "two inserts with eq. keys of node"), + Arguments.of(Array(1000) { Random.nextInt() }, Pair(Random.nextInt(), "random"), "random insert") + ) + } + + @JvmStatic + fun removeTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), 4, "remove one root node"), + Arguments.of(arrayOf(4, 6, 5, 7), 6, "remove non-root node"), + Arguments.of(arrayOf(4, 6, 5, 7), 4, "remove left leaf"), + Arguments.of(arrayOf(4, 6, 5, 7), 7, "remove right leaf"), + Arguments.of(arrayOf(4, 6, 5, 7), 5, "remove root node in non-degenerate tree"), + Arguments.of(Array(0) { it }, 4, "remove in empty tree"), + Arguments.of(arrayOf(2, 1, 3, 5), 4, "remove non-inserted node"), + Arguments.of(Array(1000) { Random.nextInt() }, Random.nextInt(), "random remove") + ) + } + + @JvmStatic + fun debugTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4), "4 \n", "insert root"), + Arguments.of( + arrayOf(5, 6, 3, 4, 1, 2), "5 \n3 6 \n1 4 - - \n- 2 ", "grandfather isn't root, uncle red" + ), + Arguments.of(arrayOf(6, 3, 8, 4), "6 \n3 8 \n- 4 ", "grandfather root, red uncle)"), + Arguments.of(arrayOf(6, 4, 5), "5 \n4 6 \n", "zigzag, null uncle"), + Arguments.of(arrayOf(5, 4, 3), "4 \n3 5 \n", "straight line, null uncle"), + Arguments.of(arrayOf(8, 9, 5, 6, 3, 2, 4, 1), "5 \n3 8 \n2 4 6 9 \n1 ", "change color, right rotation"), + Arguments.of(arrayOf(8, 9, 5, 6, 3, 1, 2), "8 \n5 9 \n2 6 - - \n1 3 ", "two rotation"), + ) + } + + @JvmStatic + fun debugRemoveTestsFactory(): Stream { + return Stream.of( + Arguments.of(arrayOf(4, 2, 5, 3), 3, "4 \n2 5 \n", "remove red leaf"), + Arguments.of(arrayOf(4, 2, 5, 3), 2, "4 \n3 5 \n", "remove black with red child"), + Arguments.of( + arrayOf(5, 2, 8, 7, 9, 10, 6), 2, "8 \n6 9 \n5 7 - 10 \n", "remove left black with red brother 1" + ), + Arguments.of( + arrayOf(5, 2, 8, 10, 6, 7, 9), 2, "8 \n6 10 \n5 7 9 ", "remove left black with red brother 2" + ), + Arguments.of( + arrayOf(5, 2, 8, 1, 3, 0, 4), 8, "2 \n1 4 \n0 - 3 5 \n", "remove right black with red brother 1" + ), + Arguments.of( + arrayOf(5, 2, 8, 0, 4, 1, 3), 8, "2 \n0 4 \n- 1 3 5 \n", "remove right black with red brother 2" + ), + Arguments.of(arrayOf(5, 2, 8, 7, 9), 2, "8 \n5 9 \n- 7 ", "remove black with black brother right"), + Arguments.of(arrayOf(5, 2, 8, 1, 3), 8, "2 \n1 5 \n- - 3 ", "remove black with black brother left"), + Arguments.of( + arrayOf(3, 1, 9, 7, 11, 5, 8), + 7, + "3 \n1 9 \n- - 8 11 \n- - - - 5 ", + "remove node with two red children" + ), + ) + } + } + + @ParameterizedTest(name = "{2} ({1}, {2})") + @MethodSource("insertTestsFactory") + @DisplayName("insert-get simple tests") + fun `insert-get simple tests`(arrKeys: Array, extraInsert: Pair?, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + if (extraInsert != null) tree.insert(extraInsert.first, extraInsert.second) + Assertions.assertArrayEquals( + keysToValues(*arrKeys.toIntArray(), chValue = extraInsert), tree.get(*arrKeys).toTypedArray() + ) + } + + @ParameterizedTest(name = "{2}, ({0}, {1})") + @MethodSource("removeTestsFactory") + @DisplayName("remove tests") + fun `remove tests`(arrKeys: Array, remove: Int, name: String) { + val tree = generateTreeWithInsert(*arrKeys.toIntArray()) + tree.remove(remove) + Assertions.assertArrayEquals( + keysToValues(*arrKeys.toIntArray(), remove = remove), tree.get(*arrKeys).toTypedArray() + ) + } + + @ParameterizedTest(name = "{2} ({0})") + @MethodSource("debugTestsFactory") + @DisplayName("insert tests using debug") + fun testsWithDebug(keys: Array, treeInString: String, name: String) { + val tree = generateTreeWithInsert(*keys.toIntArray()) + Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) + } + + @ParameterizedTest(name = "{3} ({0}, {1}") + @MethodSource("debugRemoveTestsFactory") + @DisplayName("remove tests using debug") + fun removeTestsWithDebug(keys: Array, remove: Int, treeInString: String, name: String) { + val tree = generateTreeWithInsert(*keys.toIntArray()) + tree.remove(remove) + Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) + } + + @Nested + inner class `constructors test` { + @Test + fun `insert key, value`() { + val tree = RBTree(4, "4k") + Assertions.assertEquals("4k", tree.get(4)) + } + + @Test + fun `insert two node`() { + val tree = RBTree(Pair(4, "4k"), Pair(5, "5k")) + Assertions.assertArrayEquals(arrayOf("4k", "5k"), tree.get(4, 5).toTypedArray()) + } + + @Test + fun `insert equal nodes`() { + val tree = RBTree(Pair(4, "4k"), Pair(5, "5k"), Pair(4, "7k")) + Assertions.assertAll({ assertContains(arrayOf("4k", "7k"), tree.get(4)) }, + { Assertions.assertEquals("5k", tree.get(5)) }) + } + } + + + @Test + fun `my struct`() { + class my( + val arg1: String + ) : Comparable { + override fun compareTo(other: my): Int = arg1.compareTo(other.arg1) + } + + val tree = RBTree(Pair(my("11"), 1), Pair(my("111"), 111), Pair(my("321"), 321)) + tree.remove(my("321")) + Assertions.assertAll({ Assertions.assertEquals(1, tree.get(my("11"))) }, + { Assertions.assertEquals(111, tree.get(my("111"))) }, + { Assertions.assertNull(tree.get(my("321"))) }) + } +} From 22226c348a21588ce56716d09b4e2a90726bc9f6 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 02:56:59 +0300 Subject: [PATCH 065/164] fix: multiple fixes after tests --- src/main/kotlin/RBTree.kt | 60 ++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index ee0e6da..0c2d046 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,7 +1,7 @@ const val RED = false const val BLACK = true -class RBTree, Value> : BalanceTree() { +class RBTree, Value> : BalanceTree { protected class RBNode, Value>( key: Key, value: Value, @@ -11,14 +11,17 @@ class RBTree, Value> : BalanceTree() { var color: Boolean = RED ) : BinNode(key, value, parent, left, right) { fun swapColor() { - color = if (color == BLACK) RED else BLACK + color = !color } } + constructor() : super() + constructor(key: Key, value: Value) : super(key, value) + constructor(vararg pairs: Pair) : super(pairs) override fun insert(key: Key, value: Value) { - val node = insertService(BinNode(key, value)) as RBNode? - rebalancingInsert(node) + val node = insertService(RBNode(key, value)) + if (node != null) balancingInsert(node as RBNode) } override fun remove(key: Key) { @@ -35,25 +38,23 @@ class RBTree, Value> : BalanceTree() { //delete node without child if ((removeNode.left == null) && (removeNode.right == null)) { val parent: BinNode? = removeNode.parent - if (parent == null) - rootNode = null - else if (removeNode.color == RED) - replaceNodeParent(removeNode, null) + if (parent == null) rootNode = null + else if (removeNode.color == RED) replaceNodeParent(removeNode, null) //special case for black node without child else { - rebalancingRemove(removeNode) + balancingRemove(removeNode) replaceNodeParent(removeNode, null) } } //delete black node with one red child - else if (node.left == null) { - replaceNodeParent(node, node.right ?: error("remove error: unexpected null")) - (node.right as RBNode).swapColor() + else if (removeNode.left == null) { + replaceNodeParent(removeNode, removeNode.right ?: error("remove error: unexpected null")) + (removeNode.right as RBNode).swapColor() } else { - replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) - (node.left as RBNode).swapColor() + replaceNodeParent(removeNode, removeNode.left ?: error("remove error: unexpected null")) + (removeNode.left as RBNode).swapColor() } } @@ -74,19 +75,18 @@ class RBTree, Value> : BalanceTree() { return getSibling(parent as RBNode?) } - private fun rebalancingInsert(node: RBNode?) { - if (node == null) error("can't insert node") + private fun balancingInsert(node: RBNode) { val parent = getParent(node.key) as RBNode? if (parent == null) (rootNode as RBNode?)?.color = BLACK else if (parent.color == BLACK) return else { - val uncle = getUncle(node) ?: error("balancing error") + val uncle = getUncle(node) val grandparent = getGrandparent(node) ?: error("balancing error") - if (uncle.color == RED) { + if (uncle?.color == RED) { parent.swapColor() uncle.swapColor() grandparent.swapColor() - rebalancingInsert(grandparent) + balancingInsert(grandparent) } else { if (grandparent.left == parent) { if (parent.right == node) rotation(parent, RotationType.LEFT) @@ -103,7 +103,7 @@ class RBTree, Value> : BalanceTree() { } - protected fun rebalancingRemove(removeNode: RBNode?) { + protected fun balancingRemove(removeNode: RBNode?) { var node = removeNode while ((node != rootNode) && (node?.color == BLACK)) { @@ -116,15 +116,13 @@ class RBTree, Value> : BalanceTree() { rotation(node.parent, RotationType.LEFT) brother = (node.parent as RBNode?)?.right as RBNode? } - if ((((brother?.left as RBNode?) == null) || (brother?.left as RBNode?)?.color == BLACK) && - (((brother?.right as RBNode?) == null) || (brother?.right as RBNode?)?.color == BLACK) - ) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { - if ((brother?.right as RBNode?)?.color == BLACK) { - (brother?.left as RBNode?)?.color = BLACK - brother?.color = RED + if ((brother.right == null) || (brother.right as RBNode?)?.color == BLACK) { + (brother.left as RBNode?)?.color = BLACK + brother.color = RED rotation(brother, RotationType.RIGHT) brother = node.parent?.right as RBNode? } @@ -143,15 +141,13 @@ class RBTree, Value> : BalanceTree() { rotation(node.parent, RotationType.RIGHT) brother = (node.parent as RBNode?)?.left as RBNode? } - if ((((brother?.left as RBNode?) == null) || (brother?.left as RBNode?)?.color == BLACK) && - (((brother?.right as RBNode?) == null) || (brother?.right as RBNode?)?.color == BLACK) - ) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { - if ((brother?.left as RBNode?)?.color == BLACK) { - (brother?.right as RBNode?)?.color = BLACK - brother?.color = RED + if ((brother.left == null) || (brother.left as RBNode?)?.color == BLACK) { + (brother.right as RBNode?)?.color = BLACK + brother.color = RED rotation(brother, RotationType.LEFT) brother = node.parent?.left as RBNode? } From b1d3124fa744e2a873148cc2066783adc646724c Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 03:03:05 +0300 Subject: [PATCH 066/164] refactor: refactor tests --- src/test/kotlin/BSTreeTest.kt | 22 ++++++++++++---------- src/test/kotlin/RBTreeTest.kt | 21 +++++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index 3bc1765..48a3410 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -74,7 +74,8 @@ class BSTreeTest { } @Nested - inner class `constructors test` { + @DisplayName("constructors test") + inner class ConstructorsTest { @Test fun `insert key, value`() { val tree = BSTree(4, "4k") @@ -96,7 +97,8 @@ class BSTreeTest { @Nested - inner class `tests using debug` { + @DisplayName("tests using debug") + inner class TestsUsingDebug { @Test fun `insert three nodes test`() { assertEquals("2 \n1 3 \n", generateTreeWithInsert(2, 1, 3).Debug().treeKeysInString()) @@ -128,16 +130,16 @@ class BSTreeTest { @Test fun `my struct`() { - class my( + class My( val arg1: String - ) : Comparable { - override fun compareTo(other: my): Int = arg1.compareTo(other.arg1) + ) : Comparable { + override fun compareTo(other: My): Int = arg1.compareTo(other.arg1) } - val tree = BSTree(Pair(my("11"), 1), Pair(my("111"), 111), Pair(my("321"), 321)) - tree.remove(my("321")) - assertAll({ assertEquals(1, tree.get(my("11"))) }, - { assertEquals(111, tree.get(my("111"))) }, - { assertNull(tree.get(my("321"))) }) + val tree = BSTree(Pair(My("11"), 1), Pair(My("111"), 111), Pair(My("321"), 321)) + tree.remove(My("321")) + assertAll({ assertEquals(1, tree.get(My("11"))) }, + { assertEquals(111, tree.get(My("111"))) }, + { assertNull(tree.get(My("321"))) }) } } diff --git a/src/test/kotlin/RBTreeTest.kt b/src/test/kotlin/RBTreeTest.kt index b41dfcd..d4d2dbc 100644 --- a/src/test/kotlin/RBTreeTest.kt +++ b/src/test/kotlin/RBTreeTest.kt @@ -10,7 +10,7 @@ import kotlin.random.Random import kotlin.test.assertContains class RBTreeTest { - fun generateTreeWithInsert(vararg arr: Int): RBTree { + private fun generateTreeWithInsert(vararg arr: Int): RBTree { val tree = RBTree() for (i in arr) tree.insert(i, "${i}k") return tree @@ -130,7 +130,8 @@ class RBTreeTest { } @Nested - inner class `constructors test` { + @DisplayName("constructors test") + inner class ConstructorsTest { @Test fun `insert key, value`() { val tree = RBTree(4, "4k") @@ -154,16 +155,16 @@ class RBTreeTest { @Test fun `my struct`() { - class my( + class My( val arg1: String - ) : Comparable { - override fun compareTo(other: my): Int = arg1.compareTo(other.arg1) + ) : Comparable { + override fun compareTo(other: My): Int = arg1.compareTo(other.arg1) } - val tree = RBTree(Pair(my("11"), 1), Pair(my("111"), 111), Pair(my("321"), 321)) - tree.remove(my("321")) - Assertions.assertAll({ Assertions.assertEquals(1, tree.get(my("11"))) }, - { Assertions.assertEquals(111, tree.get(my("111"))) }, - { Assertions.assertNull(tree.get(my("321"))) }) + val tree = RBTree(Pair(My("11"), 1), Pair(My("111"), 111), Pair(My("321"), 321)) + tree.remove(My("321")) + Assertions.assertAll({ Assertions.assertEquals(1, tree.get(My("11"))) }, + { Assertions.assertEquals(111, tree.get(My("111"))) }, + { Assertions.assertNull(tree.get(My("321"))) }) } } From a2222265b5143f99dccb11de230d0291c95a2c77 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 03:16:53 +0300 Subject: [PATCH 067/164] fix: AVLTree constructor --- src/main/kotlin/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 288b98c..c496dcb 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,4 +1,4 @@ -class AVLTree, Value> : BalanceTree() { +class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, value: Value, From 24d41e8360a0d927ff07c7302aa6de54cdd8c457 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 03:19:01 +0300 Subject: [PATCH 068/164] refactor: move lambda argument out parenthesis --- src/main/kotlin/abstractBinTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 2820277..2baa915 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -145,7 +145,7 @@ abstract class BinTree, Value> { } fun get(vararg keys: Key): List { - return List(keys.size, {get(keys[it])}) + return List(keys.size) { get(keys[it]) } } protected open fun nextElement(node: BinNode): BinNode? { From 6edef8953176e3627f60b505af8e2bd9be9b5cb3 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 18 Apr 2023 05:03:59 +0300 Subject: [PATCH 069/164] fix: tests for AVLTree --- src/test/kotlin/AVLTreeTest.kt | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt index 13048e5..50e2e2f 100644 --- a/src/test/kotlin/AVLTreeTest.kt +++ b/src/test/kotlin/AVLTreeTest.kt @@ -10,9 +10,8 @@ import java.util.stream.Stream import kotlin.random.Random import kotlin.test.assertContains -@Disabled class AVLTreeTest { - fun generateTreeWithInsert(vararg arr: Int): BinTree { + fun generateTreeWithInsert(vararg arr: Int): AVLTree { val tree = AVLTree() for (i in arr) tree.insert(i, "${i}k") return tree @@ -54,23 +53,23 @@ class AVLTreeTest { Arguments.of(arrayOf(4, 2, 1), "2 \n1 4 \n", "right rotation"), Arguments.of(arrayOf(2, 5, 4, 3, 1, 0), "2 \n1 4 \n0 - 3 5 \n", "difficult right rotation 1"), Arguments.of(arrayOf(2, 5, 4, 3, 0, 1), "2 \n0 4 \n- 1 3 5 \n", "difficult right rotation 2"), - Arguments.of(arrayOf(2, 1, 4, 3, 5, 6), "4 \n2 5\n1 3 - 6 \n", "difficult left rotation 1"), - Arguments.of(arrayOf(2, 1, 4, 3, 6, 5), "4 \n2 6\n1 3 5 - \n", "difficult left rotation 2"), + Arguments.of(arrayOf(2, 1, 4, 3, 5, 6), "4 \n2 5 \n1 3 - 6 \n", "difficult left rotation 1"), + Arguments.of(arrayOf(2, 1, 4, 3, 6, 5), "4 \n2 6 \n1 3 5 ", "difficult left rotation 2"), Arguments.of(arrayOf(1, 3, 2), "2 \n1 3 \n", "right left rotation"), Arguments.of(arrayOf(3, 1, 2), "2 \n1 3 \n", "left right rotation"), - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), "6 \n4 8\n2 5 7 - \n1 3 ", "combine"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), "6 \n4 8 \n2 5 7 - \n1 3 ", "combine"), ) } @JvmStatic fun debugRemoveTestsFactory(): Stream { return Stream.of( - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 1, "6 \n4 8\n2 5 7 - \n- 3 ", "remove right leaf"), - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 3, "6 \n4 8\n2 5 7 - \n1 ", "remove left leaf"), - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 2, "6 \n4 8\n1 5 7 - \n- 3 ", "remove root of two leafs"), - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 8, "4 \n2 6\n1 3 5 7 \n", "remove with rebalancing"), - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 6, "5 \n2 8\n1 4 7 - \n- - 3 ", "remove root"), - Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 4, "6 \n3 8\n2 5 7 - \n1 ", "one more remove"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 1, "6 \n4 8 \n2 5 7 - \n- 3 ", "remove right leaf"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 3, "6 \n4 8 \n2 5 7 - \n1 ", "remove left leaf"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 2, "6 \n4 8 \n3 5 7 - \n1 ", "remove root of two leafs"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 8, "4 \n2 6 \n1 3 5 7 \n", "remove with rebalancing"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 6, "4 \n2 7 \n1 3 5 8 \n", "remove root"), + Arguments.of(arrayOf(4, 3, 6, 5, 8, 7, 1, 2), 4, "6 \n2 8 \n1 5 7 - \n- - 3 ", "one more remove"), ) } } @@ -97,7 +96,7 @@ class AVLTreeTest { ) } - @ParameterizedTest(name = "{2}") + @ParameterizedTest(name = "{1}") @MethodSource("debugTestsFactory") @DisplayName("insert tests using debug") fun testsWithDebug(keys: Array, treeInString: String) { From c74b15023a7a0023eee8840dccb15473c315a034 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 18 Apr 2023 05:06:29 +0300 Subject: [PATCH 070/164] fix: many fixes + general refactoring --- src/main/kotlin/AVLTree.kt | 155 +++++++++++++------------------------ 1 file changed, 52 insertions(+), 103 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index f58a382..023f560 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,4 +1,4 @@ -class AVLTree, Value> : BalanceTree() { +class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, value: Value, @@ -13,124 +13,73 @@ class AVLTree, Value> : BalanceTree() { constructor(vararg pairs: Pair) : super(pairs) override fun insert(key: Key, value: Value) { - if (rootNode != null) { - if ((rootNode as AVLNode).height != 255.toUByte()) { - insertService(AVLNode(key, value)) - } - } else - rootNode = AVLNode(key, value) - balance(rootNode as AVLNode) + rootNode?.let { if ((rootNode as AVLNode).height == 255.toUByte()) return } + val node = insertService(AVLNode(key, value)) as AVLNode? + balancing(node ?: return) } - - private fun searchNode(key: Key, node: AVLNode?): AVLNode? { - if (node == null || key == node.key) { - return node - } - return if (key < node.key) { - searchNode(key, node.left as AVLNode?) - } else { - searchNode(key, node.right as AVLNode?) - } - } - private fun transplant(node: AVLNode, newNode: AVLNode) { - if (node.parent == null) { - rootNode = newNode - } else if (node == node.parent?.left) { - node.parent?.left = newNode + override fun remove(key: Key) { + val removeNode = getNode(key) ?: return + if ((removeNode.right != null) && (removeNode.left != null)) { + val node = nextElement(removeNode) as AVLNode + if (node.parent == removeNode) { + removeService(removeNode) + balancing(node) + } else { + val nextNodeParent = node.parent as AVLNode + removeService(removeNode) + balancing(nextNodeParent) + } } else { - node.parent?.right = newNode + removeService(removeNode) + if (rootNode != null) balancing(removeNode.parent as AVLNode) } - newNode.parent = node.parent + } - private fun minNode(node: AVLNode): AVLNode { + private tailrec fun balancing(node: AVLNode): AVLNode { var currentNode = node - while (currentNode.left != null) { - currentNode = currentNode.left as AVLNode - } - return currentNode - } + updateHeight(currentNode) - override fun remove(key: Key) { - val node = searchNode(key, rootNode as AVLNode) ?: return - when { - node.left == null -> transplant(node, node.right as AVLNode) - node.right == null -> transplant(node, node.left as AVLNode) - else -> { - val successor = minNode(node.right!! as AVLNode) - if (successor.parent != node) { - transplant(successor, successor.right as AVLNode) - successor.right = node.right - successor.right?.parent = successor - } - transplant(node, successor) - successor.left = node.left - successor.left?.parent = successor - } - } - balance(node.parent as AVLNode) - } - private fun balance(node: AVLNode) { - if (node.left != null && node.right != null) { - if ((node.left as AVLNode).height > (node.right as AVLNode).height + 1.toUByte()) { - if ((node.left!!.left as AVLNode).height >= (node.left!!.right as AVLNode).height) { - rotateRight(node) - } else { - rotateLeft(node.left!! as AVLNode) - rotateRight(node) - } - } else if ((node.right as AVLNode).height > (node.left as AVLNode).height + 1.toUByte()) { - if ((node.right!!.right as AVLNode).height >= (node.right!!.left as AVLNode).height) { - rotateLeft(node) - } else { - rotateRight(node.right!! as AVLNode) - rotateLeft(node) - } + if (balanceFactor(currentNode) >= 2) { + if (balanceFactor(currentNode.right as AVLNode) >= 0) { + currentNode = rotation(currentNode, RotationType.LEFT) as AVLNode + updateHeightAfterRotation(currentNode.left as AVLNode?) + } else { + currentNode = rotation(currentNode.right, RotationType.RIGHT) as AVLNode + updateHeightAfterRotation(currentNode.right as AVLNode?) + currentNode = rotation(currentNode.parent, RotationType.LEFT) as AVLNode + updateHeightAfterRotation(currentNode.left as AVLNode?) } - updateHeight(node) - if (node.parent != null) { - balance(node.parent!! as AVLNode) + } else if (balanceFactor(currentNode) <= -2) { + if (balanceFactor(currentNode.left as AVLNode) <= 0) { + currentNode = rotation(currentNode, RotationType.RIGHT) as AVLNode + updateHeightAfterRotation(currentNode.right as AVLNode?) + } else { + currentNode = rotation(currentNode.left, RotationType.LEFT) as AVLNode + updateHeightAfterRotation(currentNode.left as AVLNode?) + currentNode = rotation(currentNode.parent, RotationType.RIGHT) as AVLNode + updateHeightAfterRotation(currentNode.right as AVLNode?) } } + (currentNode.parent as AVLNode?)?.let { return balancing(it) } + return currentNode } + private fun updateHeight(node: AVLNode) { val left = node.left?.let { (it as AVLNode).height } ?: 0U val right = node.right?.let { (it as AVLNode).height } ?: 0U - node.height = (maxOf(left, right) + 1.toUByte()).toUByte() + node.height = (maxOf(left, right) + 1U).toUByte() } - private fun rotateLeft(node: AVLNode) { - val child = node.right!! - child.parent = node.parent - if (node.parent == null) { - rootNode = child - } else if (node.parent!!.left == node) { - node.parent!!.left = child - } else { - node.parent!!.right = child - } - node.right = child.left - child.left?.parent = node - node.parent = child - child.left = node - updateHeight(node) - updateHeight(child as AVLNode) + + private fun updateHeightAfterRotation(node: AVLNode?) { + node?.let { updateHeight(it) } ?: 0U + node?.parent?.let { updateHeight(it as AVLNode) } ?: 0U } - private fun rotateRight(node: AVLNode) { - val child = node.left!! - child.parent = node.parent - if (node.parent == null) { - rootNode = child - } else if (node.parent!!.left == node) { - node.parent!!.left = child - } else { - node.parent!!.right = child - } - node.left = child.right - child.right?.parent = node - node.parent = child - child.right = node - updateHeight(node) - updateHeight(child as AVLNode) + + private fun balanceFactor(node: AVLNode): Int { + val left = node.left?.let { (it as AVLNode).height.toInt() } ?: 0 + val right = node.right?.let { (it as AVLNode).height.toInt() } ?: 0 + return (right - left) } } \ No newline at end of file From 1bddda0f10201238f97c7ee9d442727f58de93f1 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 05:18:58 +0300 Subject: [PATCH 071/164] refactor: rename some tests, fix names of some parametrized tests --- src/test/kotlin/AVLTreeTest.kt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt index 50e2e2f..e6945d1 100644 --- a/src/test/kotlin/AVLTreeTest.kt +++ b/src/test/kotlin/AVLTreeTest.kt @@ -1,5 +1,4 @@ import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -74,7 +73,7 @@ class AVLTreeTest { } } - @ParameterizedTest(name = "{2}") + @ParameterizedTest(name = "{2} ({0}, {1})") @MethodSource("insertTestsFactory") @DisplayName("insert-get simple tests") fun `insert-get simple tests`(arrKeys: Array, extraInsert: Pair?, name: String) { @@ -85,7 +84,7 @@ class AVLTreeTest { ) } - @ParameterizedTest(name = "{2}") + @ParameterizedTest(name = "{2} ({0}, {1})") @MethodSource("removeTestsFactory") @DisplayName("remove tests") fun `remove tests`(arrKeys: Array, remove: Int, name: String) { @@ -96,25 +95,26 @@ class AVLTreeTest { ) } - @ParameterizedTest(name = "{1}") + @ParameterizedTest(name = "{2} ({0})") @MethodSource("debugTestsFactory") @DisplayName("insert tests using debug") - fun testsWithDebug(keys: Array, treeInString: String) { + fun testsWithDebug(keys: Array, treeInString: String, name: String) { val tree = generateTreeWithInsert(*keys.toIntArray()) Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) } - @ParameterizedTest(name = "{2}") + @ParameterizedTest(name = "{3} ({0}, {1})") @MethodSource("debugRemoveTestsFactory") @DisplayName("remove tests using debug") - fun removeTestsWithDebug(keys: Array, remove: Int, treeInString: String) { + fun removeTestsWithDebug(keys: Array, remove: Int, treeInString: String, name: String) { val tree = generateTreeWithInsert(*keys.toIntArray()) tree.remove(remove) Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) } @Nested - inner class `constructors test` { + @DisplayName("constructors test") + inner class ConstructorsTest { @Test fun `insert key, value`() { val tree = AVLTree(4, "4k") @@ -138,16 +138,16 @@ class AVLTreeTest { @Test fun `my struct`() { - class my( + class My ( val arg1: String - ) : Comparable { - override fun compareTo(other: my): Int = arg1.compareTo(other.arg1) + ) : Comparable { + override fun compareTo(other: My): Int = arg1.compareTo(other.arg1) } - val tree = AVLTree(Pair(my("11"), 1), Pair(my("111"), 111), Pair(my("321"), 321)) - tree.remove(my("321")) - Assertions.assertAll({ Assertions.assertEquals(1, tree.get(my("11"))) }, - { Assertions.assertEquals(111, tree.get(my("111"))) }, - { Assertions.assertNull(tree.get(my("321"))) }) + val tree = AVLTree(Pair(My("11"), 1), Pair(My("111"), 111), Pair(My("321"), 321)) + tree.remove(My("321")) + Assertions.assertAll({ Assertions.assertEquals(1, tree.get(My("11"))) }, + { Assertions.assertEquals(111, tree.get(My("111"))) }, + { Assertions.assertNull(tree.get(My("321"))) }) } } From e252c512b93047f4e03d994c18c67d1c627f2aae Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 18 Apr 2023 17:28:02 +0300 Subject: [PATCH 072/164] refactor: add some commit --- src/main/kotlin/AVLTree.kt | 13 ++++++++++++- src/main/kotlin/RBTree.kt | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 023f560..4183f3d 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -13,15 +13,20 @@ class AVLTree, Value> : BalanceTree { constructor(vararg pairs: Pair) : super(pairs) override fun insert(key: Key, value: Value) { + //if the tree is too big, we can't insert something rootNode?.let { if ((rootNode as AVLNode).height == 255.toUByte()) return } + val node = insertService(AVLNode(key, value)) as AVLNode? balancing(node ?: return) } override fun remove(key: Key) { val removeNode = getNode(key) ?: return + + //special case when the node has two children if ((removeNode.right != null) && (removeNode.left != null)) { val node = nextElement(removeNode) as AVLNode + if (node.parent == removeNode) { removeService(removeNode) balancing(node) @@ -30,17 +35,23 @@ class AVLTree, Value> : BalanceTree { removeService(removeNode) balancing(nextNodeParent) } - } else { + } + + //when the node has zero or one child, just remove it and balance the tree + else { removeService(removeNode) if (rootNode != null) balancing(removeNode.parent as AVLNode) } } + //use tail recursion to balance after removing and inserting a node private tailrec fun balancing(node: AVLNode): AVLNode { var currentNode = node updateHeight(currentNode) + //if the balancing factor by module is greater than one, + //it is necessary to do the balancing if (balanceFactor(currentNode) >= 2) { if (balanceFactor(currentNode.right as AVLNode) >= 0) { currentNode = rotation(currentNode, RotationType.LEFT) as AVLNode diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 0c2d046..82c7607 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -38,10 +38,14 @@ class RBTree, Value> : BalanceTree { //delete node without child if ((removeNode.left == null) && (removeNode.right == null)) { val parent: BinNode? = removeNode.parent + if (parent == null) rootNode = null + + //when the color of the node is red, just delete it else if (removeNode.color == RED) replaceNodeParent(removeNode, null) - //special case for black node without child + //when the color of the node without children is black, + //the tree needs to be balanced else { balancingRemove(removeNode) replaceNodeParent(removeNode, null) @@ -77,11 +81,15 @@ class RBTree, Value> : BalanceTree { private fun balancingInsert(node: RBNode) { val parent = getParent(node.key) as RBNode? + + //root color should always be black if (parent == null) (rootNode as RBNode?)?.color = BLACK + else if (parent.color == BLACK) return else { val uncle = getUncle(node) val grandparent = getGrandparent(node) ?: error("balancing error") + if (uncle?.color == RED) { parent.swapColor() uncle.swapColor() @@ -116,7 +124,8 @@ class RBTree, Value> : BalanceTree { rotation(node.parent, RotationType.LEFT) brother = (node.parent as RBNode?)?.right as RBNode? } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && + ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { @@ -141,7 +150,8 @@ class RBTree, Value> : BalanceTree { rotation(node.parent, RotationType.RIGHT) brother = (node.parent as RBNode?)?.left as RBNode? } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && + ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { From b92711852b3556b78eadc65536abb1ad59afdd92 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 18 Apr 2023 22:11:11 +0300 Subject: [PATCH 073/164] feat: change constructors and insert functions, now sort isn't default behavior --- src/main/kotlin/abstractBinTree.kt | 92 ++++++++++++------------------ 1 file changed, 36 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 07c5a57..4a3b298 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -2,7 +2,6 @@ import java.util.LinkedList import java.util.Queue import kotlin.math.abs - abstract class BinTree, Value> { protected open class BinNode, Value>( var key: Key, @@ -28,11 +27,13 @@ abstract class BinTree, Value> { constructor(key: Key, value: Value) { insert(key, value) } - constructor(array: Array>) { - sortInsert(array) + + constructor(array: Array>, sort: Boolean = false) { + if (sort) sortInsert(*array) + else insert(*array) } - private fun sortInsert(array: Array>) { + fun sortInsert(vararg array: Pair) { val serArray = array.sortedBy { it.first }.toTypedArray() var indices = serArray.indices.toList() indices = indices.sortedBy { abs(serArray.size / 2 - it) } @@ -44,14 +45,13 @@ abstract class BinTree, Value> { abstract fun insert(key: Key, value: Value) fun insert(vararg array: Pair) { - sortInsert(array) + for (i in array) insert(i.first, i.second) } abstract fun remove(key: Key) fun remove(vararg array: Key) { - for (i in array) - remove(i) + for (i in array) remove(i) } //return the inserted node if the node with the same key wasn't in the tree and null in otherwise @@ -63,18 +63,16 @@ abstract class BinTree, Value> { } else { val parent = getParent(node.key) if (parent != null) { - if (parent < node.key) - if (parent.right == null) { - node.parent = parent - parent.right = node - return node - } else parent.right?.value = node.value ?: error("unexpected null") - else - if (parent.left == null) { - node.parent = parent - parent.left = node - return node - } else (parent.left)?.value = node.value ?: error("unexpected null") + if (parent < node.key) if (parent.right == null) { + node.parent = parent + parent.right = node + return node + } else parent.right?.value = node.value ?: error("unexpected null") + else if (parent.left == null) { + node.parent = parent + parent.left = node + return node + } else (parent.left)?.value = node.value ?: error("unexpected null") } else rootNode?.value = node.value ?: error("unexpected null") } return null @@ -83,16 +81,11 @@ abstract class BinTree, Value> { protected fun removeService(node: BinNode) { if ((node.left == null) && (node.right == null)) { val parent: BinNode? = node.parent - if (parent == null) - rootNode = null - else if (node == parent.left) - parent.left = null - else - parent.right = null - } else if (node.left == null) - replaceNodeParent(node, node.right ?: error("remove error: unexpected null")) - else if (node.right == null) - replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) + if (parent == null) rootNode = null + else if (node == parent.left) parent.left = null + else parent.right = null + } else if (node.left == null) replaceNodeParent(node, node.right ?: error("remove error: unexpected null")) + else if (node.right == null) replaceNodeParent(node, node.left ?: error("remove error: unexpected null")) else { val nextNode = nextElement(node) ?: error("remove error: unexpected null") val parent = nextNode.parent ?: error("remove error: unexpected null") @@ -110,35 +103,26 @@ abstract class BinTree, Value> { protected fun getParent(key: Key): BinNode? { tailrec fun recFind(curNode: BinNode?): BinNode? { - return if (curNode == null) - null + return if (curNode == null) null else if (curNode > key) { - if (curNode.left?.equalKey(key) != false) - curNode - else - recFind(curNode.left) + if (curNode.left?.equalKey(key) != false) curNode + else recFind(curNode.left) } else if (curNode.equalKey(key)) { return curNode.parent } else { - if (curNode.right?.equalKey(key) != false) - curNode - else - recFind(curNode.right) + if (curNode.right?.equalKey(key) != false) curNode + else recFind(curNode.right) } } return recFind(rootNode) } protected fun getNode(key: Key): BinNode? { - if (rootNode?.equalKey(key) == true) - return rootNode + if (rootNode?.equalKey(key) == true) return rootNode val parent = getParent(key) - return if (parent == null) - null - else if (parent.left?.equalKey(key) == true) - parent.left - else if (parent.right?.equalKey(key) == true) - parent.right + return if (parent == null) null + else if (parent.left?.equalKey(key) == true) parent.left + else if (parent.right?.equalKey(key) == true) parent.right else null } @@ -173,23 +157,20 @@ abstract class BinTree, Value> { protected open fun replaceNodeParent(oldNode: BinNode, newNode: BinNode?) { val parent: BinNode? = oldNode.parent - if (parent == null) - rootNode = newNode + if (parent == null) rootNode = newNode else if (oldNode == parent.left) { parent.left = newNode } else { parent.right = newNode } - newNode?.let { it.parent = parent} + newNode?.let { it.parent = parent } } - protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean) { + protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean = false) { val queue: Queue?> = LinkedList(listOf(rootNode)) fun notNullInQueue(): Boolean { - for (i in queue) - if (i != null) - return true + for (i in queue) if (i != null) return true return false } @@ -203,8 +184,7 @@ abstract class BinTree, Value> { queue.add(null) queue.add(null) } - if (!notNullInQueue()) - return + if (!notNullInQueue()) return } } From 4493c06064e6e3dc48591b670042527d8d2a70a5 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 02:15:24 +0300 Subject: [PATCH 074/164] feat: add tree with save to neo4j --- build.gradle.kts | 5 ++- src/main/kotlin/StoredCoordBSTree.kt | 62 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/StoredCoordBSTree.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0ff6d56..3e69be7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,9 +11,10 @@ repositories { } dependencies { - testImplementation(kotlin("test")) + testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.10") testImplementation(platform("org.junit:junit-bom:5.9.2")) - testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") + implementation("org.neo4j.driver:neo4j-java-driver:5.7.0") } tasks.test { diff --git a/src/main/kotlin/StoredCoordBSTree.kt b/src/main/kotlin/StoredCoordBSTree.kt new file mode 100644 index 0000000..360543b --- /dev/null +++ b/src/main/kotlin/StoredCoordBSTree.kt @@ -0,0 +1,62 @@ +import org.neo4j.driver.AuthTokens +import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.exceptions.value.Uncoercible +import java.io.Closeable +import java.io.IOException + +class StoredCoordBSTree : BSTree>> { + constructor() : super() + constructor(key: String, value: Pair>) : super(key, value) + constructor(vararg pairs: Pair>>) : super(*pairs) + + inner class neo4j(uri: String, user: String, password: String) : Closeable { + private val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) + private val session = driver.session() + + fun addTree() { + cleanDB() + breadthFirstSearch({ node -> addNode(node) }) + } + + private fun addNode(node: BinNode>>?) { + if (node == null) return + session.executeWrite { tx -> + tx.run( + "CREATE (:Node{key:\$key, value:\$value, x:\$x, y:\$y}) ", mutableMapOf( + "key" to node.key, + "value" to node.value.first, + "x" to node.value.second.first, + "y" to node.value.second.second + ) as Map + ) + } + } + + fun readTree() { + session.executeRead { tx -> + val result = tx.run( + "MATCH (node: Node) " + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY node.id" + ) + result.stream().forEach { + try { + insert( + it["key"].asString(), + Pair(it["value"].asString(), Pair(it["x"].asDouble(), it["y"].asDouble())) + ) + } catch (e: Uncoercible) { + throw IOException("Corrupted data in the database.\n Possible solution: Clear the data.") + } + } + } + } + + fun cleanDB() { + session.run("MATCH (n) DETACH DELETE n") + } + + override fun close() { + session.close() + driver.close() + } + } +} From 6028753b79f5a2f13630db76933891a7bf5df4b2 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 02:19:13 +0300 Subject: [PATCH 075/164] refactor: change name of some functions --- src/main/kotlin/StoredCoordBSTree.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/StoredCoordBSTree.kt b/src/main/kotlin/StoredCoordBSTree.kt index 360543b..6d0ae67 100644 --- a/src/main/kotlin/StoredCoordBSTree.kt +++ b/src/main/kotlin/StoredCoordBSTree.kt @@ -13,12 +13,12 @@ class StoredCoordBSTree : BSTree>> { private val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) private val session = driver.session() - fun addTree() { + fun saveTree() { cleanDB() - breadthFirstSearch({ node -> addNode(node) }) + breadthFirstSearch({ node -> saveNode(node) }) } - private fun addNode(node: BinNode>>?) { + private fun saveNode(node: BinNode>>?) { if (node == null) return session.executeWrite { tx -> tx.run( From ead6d5b66c3bf855dddcd2cac353954048b178f5 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 04:54:28 +0300 Subject: [PATCH 076/164] feat: make save smarter --- src/main/kotlin/StoredCoordBSTree.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/StoredCoordBSTree.kt b/src/main/kotlin/StoredCoordBSTree.kt index 6d0ae67..813f536 100644 --- a/src/main/kotlin/StoredCoordBSTree.kt +++ b/src/main/kotlin/StoredCoordBSTree.kt @@ -12,17 +12,22 @@ class StoredCoordBSTree : BSTree>> { inner class neo4j(uri: String, user: String, password: String) : Closeable { private val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) private val session = driver.session() + val treeName = "Tree" fun saveTree() { cleanDB() - breadthFirstSearch({ node -> saveNode(node) }) + session.run("CREATE (:$treeName)") + session.run("CREATE (:gljlkj)") + var prevKey: String? = null + breadthFirstSearch({ node -> saveNode(node, prevKey); prevKey = node?.key ?: prevKey }) } - private fun saveNode(node: BinNode>>?) { + private fun saveNode(node: BinNode>>?, prevKey: String?) { if (node == null) return session.executeWrite { tx -> tx.run( - "CREATE (:Node{key:\$key, value:\$value, x:\$x, y:\$y}) ", mutableMapOf( + "OPTIONAL MATCH (prevNode:${if (prevKey == null) treeName else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", mutableMapOf( "key" to node.key, "value" to node.value.first, "x" to node.value.second.first, @@ -35,7 +40,8 @@ class StoredCoordBSTree : BSTree>> { fun readTree() { session.executeRead { tx -> val result = tx.run( - "MATCH (node: Node) " + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY node.id" + "OPTIONAL MATCH ($treeName)-[n:next*]->(node)" + + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" ) result.stream().forEach { try { @@ -51,7 +57,7 @@ class StoredCoordBSTree : BSTree>> { } fun cleanDB() { - session.run("MATCH (n) DETACH DELETE n") + session.run("MATCH ($treeName)-[:next*]->(node) DETACH DELETE node, $treeName") } override fun close() { From 33ba133091a63cedd398918524281de42666cedb Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 05:21:54 +0300 Subject: [PATCH 077/164] feat: remove init driver, session, close from Tree and added the possibility of storing several trees at the same time --- src/main/kotlin/StoredCoordBSTree.kt | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/StoredCoordBSTree.kt b/src/main/kotlin/StoredCoordBSTree.kt index 813f536..de18ca8 100644 --- a/src/main/kotlin/StoredCoordBSTree.kt +++ b/src/main/kotlin/StoredCoordBSTree.kt @@ -1,7 +1,5 @@ -import org.neo4j.driver.AuthTokens -import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.Session import org.neo4j.driver.exceptions.value.Uncoercible -import java.io.Closeable import java.io.IOException class StoredCoordBSTree : BSTree>> { @@ -9,15 +7,10 @@ class StoredCoordBSTree : BSTree>> { constructor(key: String, value: Pair>) : super(key, value) constructor(vararg pairs: Pair>>) : super(*pairs) - inner class neo4j(uri: String, user: String, password: String) : Closeable { - private val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) - private val session = driver.session() - val treeName = "Tree" - + inner class neo4j(val session: Session, val treeName: String) { fun saveTree() { - cleanDB() + removeTreeFromDB() session.run("CREATE (:$treeName)") - session.run("CREATE (:gljlkj)") var prevKey: String? = null breadthFirstSearch({ node -> saveNode(node, prevKey); prevKey = node?.key ?: prevKey }) } @@ -56,13 +49,8 @@ class StoredCoordBSTree : BSTree>> { } } - fun cleanDB() { - session.run("MATCH ($treeName)-[:next*]->(node) DETACH DELETE node, $treeName") - } - - override fun close() { - session.close() - driver.close() + fun removeTreeFromDB() { + session.run("OPTIONAL MATCH (tree:$treeName)-[:next*]->(node) DETACH DELETE node, tree") } } } From 133ec5ea166beae095ae6b70052ccaf1cf4a5c44 Mon Sep 17 00:00:00 2001 From: juliakononov <113186929+juliakononov@users.noreply.github.com> Date: Wed, 19 Apr 2023 05:35:39 +0300 Subject: [PATCH 078/164] docs: add readme --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccac7ff --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ + +# Tree Structure + +Tree is a widely used abstract data type that represents a hierarchical tree structure with a set of connected nodes. + + +[![Apache License](https://img.shields.io/badge/license-Apache%202.0-black.svg)](https://www.apache.org/licenses/LICENSE-2.0) + + + +## What's ready now: + + - Implemented the logic of three trees: AVLTree, RBTree, BSTree + - We have written tests for each tree + - Tree can be saved and read from neo4j !!!! + + ## What is planned to do: + + - Support saving and restoring trees from Json and SQL + - Implement a user interface + + +## Build tool + +The Gradle build tool is used to manage the project + +The files with the source code must be located in the directory `src/main/kotlin` \ +The test files must be located in the directory `src/test/kotlin` + +Build code without running tests with command +```bash +./gradlew assemble +``` + +Build the code and run the tests with command +```bash +./gradlew test +``` +## Neo4j + +About neo4j read in [here](https://neo4j.com/). From 021801ac02b5a1358e7c7d692a9ca096501b482a Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 05:36:22 +0300 Subject: [PATCH 079/164] feat: add directories and neo4j functions --- .../StoredCoordBSTree.kt | 7 +++-- src/main/kotlin/dataBase/neo4j functions.kt | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) rename src/main/kotlin/{ => CoordinateTreeWithStored}/StoredCoordBSTree.kt (93%) create mode 100644 src/main/kotlin/dataBase/neo4j functions.kt diff --git a/src/main/kotlin/StoredCoordBSTree.kt b/src/main/kotlin/CoordinateTreeWithStored/StoredCoordBSTree.kt similarity index 93% rename from src/main/kotlin/StoredCoordBSTree.kt rename to src/main/kotlin/CoordinateTreeWithStored/StoredCoordBSTree.kt index de18ca8..3710017 100644 --- a/src/main/kotlin/StoredCoordBSTree.kt +++ b/src/main/kotlin/CoordinateTreeWithStored/StoredCoordBSTree.kt @@ -1,3 +1,6 @@ +package CoordinateTreeWithStored + +import BSTree import org.neo4j.driver.Session import org.neo4j.driver.exceptions.value.Uncoercible import java.io.IOException @@ -15,7 +18,7 @@ class StoredCoordBSTree : BSTree>> { breadthFirstSearch({ node -> saveNode(node, prevKey); prevKey = node?.key ?: prevKey }) } - private fun saveNode(node: BinNode>>?, prevKey: String?) { + private fun saveNode(node: BinTree.BinNode>>?, prevKey: String?) { if (node == null) return session.executeWrite { tx -> tx.run( @@ -53,4 +56,4 @@ class StoredCoordBSTree : BSTree>> { session.run("OPTIONAL MATCH (tree:$treeName)-[:next*]->(node) DETACH DELETE node, tree") } } -} +} \ No newline at end of file diff --git a/src/main/kotlin/dataBase/neo4j functions.kt b/src/main/kotlin/dataBase/neo4j functions.kt new file mode 100644 index 0000000..c34124f --- /dev/null +++ b/src/main/kotlin/dataBase/neo4j functions.kt @@ -0,0 +1,30 @@ +package dataBase + +import org.neo4j.driver.AuthTokens +import org.neo4j.driver.Driver +import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.Session +import java.io.IOException + +fun startSession(uri: String, user: String, password: String): Session { + try { + val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) + return driver.session() + } catch (e: IllegalArgumentException) { + throw IOException("can't start session, try to chainge uri, user name or password") + } +} + +fun closeSession(session: Session, driver: Driver) { + session.close() + driver.close() +} + +fun cleanDB(session: Session) { + session.run("MATCH (n) DETACH DELETE n") +} + +fun cleanAndClose(session: Session, driver: Driver) { + cleanDB(session) + closeSession(session, driver) +} \ No newline at end of file From dd8a2609e4a250a486838f6c522f8c6bc051086f Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 05:37:28 +0300 Subject: [PATCH 080/164] refactor: rename directory --- .../StoredCoordBSTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/main/kotlin/{CoordinateTreeWithStored => coordinateTreeWithStored}/StoredCoordBSTree.kt (94%) diff --git a/src/main/kotlin/CoordinateTreeWithStored/StoredCoordBSTree.kt b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt similarity index 94% rename from src/main/kotlin/CoordinateTreeWithStored/StoredCoordBSTree.kt rename to src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt index 3710017..c2954c4 100644 --- a/src/main/kotlin/CoordinateTreeWithStored/StoredCoordBSTree.kt +++ b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt @@ -1,4 +1,4 @@ -package CoordinateTreeWithStored +package coordinateTreeWithStored import BSTree import org.neo4j.driver.Session @@ -18,7 +18,7 @@ class StoredCoordBSTree : BSTree>> { breadthFirstSearch({ node -> saveNode(node, prevKey); prevKey = node?.key ?: prevKey }) } - private fun saveNode(node: BinTree.BinNode>>?, prevKey: String?) { + private fun saveNode(node: BinNode>>?, prevKey: String?) { if (node == null) return session.executeWrite { tx -> tx.run( From d22480c27ca4e8820c3bfb220d70430860a4d351 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 05:56:46 +0300 Subject: [PATCH 081/164] feat: add interfaces --- src/main/kotlin/interfaces/DataBase.kt | 7 +++++++ src/main/kotlin/interfaces/Tree.kt | 10 ++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/main/kotlin/interfaces/DataBase.kt create mode 100644 src/main/kotlin/interfaces/Tree.kt diff --git a/src/main/kotlin/interfaces/DataBase.kt b/src/main/kotlin/interfaces/DataBase.kt new file mode 100644 index 0000000..292cc91 --- /dev/null +++ b/src/main/kotlin/interfaces/DataBase.kt @@ -0,0 +1,7 @@ +package interfaces + +interface DataBase { + fun saveTree() + fun readTree() + fun removeTree() +} \ No newline at end of file diff --git a/src/main/kotlin/interfaces/Tree.kt b/src/main/kotlin/interfaces/Tree.kt new file mode 100644 index 0000000..bf41c2e --- /dev/null +++ b/src/main/kotlin/interfaces/Tree.kt @@ -0,0 +1,10 @@ +package interfaces + +interface Tree { + fun insert(key: Key, value: Value) + fun insert(vararg array: Pair) + fun remove(key: Key) + fun remove(vararg keys: Key) + fun get(key: Key): Value? + fun get(vararg keys: Key): List +} \ No newline at end of file From ee9f9e28d27247f014b76a18d65f1fc71f32e990 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 05:58:51 +0300 Subject: [PATCH 082/164] refactor: add inheritance from interfaces --- src/main/kotlin/abstractBinTree.kt | 17 +++++++---------- .../StoredCoordBSTree.kt | 18 +++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractBinTree.kt index 4a3b298..cfbb434 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractBinTree.kt @@ -1,8 +1,9 @@ +import interfaces.Tree import java.util.LinkedList import java.util.Queue import kotlin.math.abs -abstract class BinTree, Value> { +abstract class BinTree, Value> : Tree { protected open class BinNode, Value>( var key: Key, var value: Value, @@ -42,16 +43,12 @@ abstract class BinTree, Value> { } } - abstract fun insert(key: Key, value: Value) - - fun insert(vararg array: Pair) { + override fun insert(vararg array: Pair) { for (i in array) insert(i.first, i.second) } - abstract fun remove(key: Key) - - fun remove(vararg array: Key) { - for (i in array) remove(i) + override fun remove(vararg keys: Key) { + for (i in keys) remove(i) } //return the inserted node if the node with the same key wasn't in the tree and null in otherwise @@ -126,11 +123,11 @@ abstract class BinTree, Value> { else null } - open fun get(key: Key): Value? { + override fun get(key: Key): Value? { return getNode(key)?.value } - fun get(vararg keys: Key): List { + override fun get(vararg keys: Key): List { return List(keys.size) { get(keys[it]) } } diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt index c2954c4..c1cc1d7 100644 --- a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt +++ b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt @@ -1,6 +1,7 @@ package coordinateTreeWithStored import BSTree +import interfaces.DataBase import org.neo4j.driver.Session import org.neo4j.driver.exceptions.value.Uncoercible import java.io.IOException @@ -10,9 +11,9 @@ class StoredCoordBSTree : BSTree>> { constructor(key: String, value: Pair>) : super(key, value) constructor(vararg pairs: Pair>>) : super(*pairs) - inner class neo4j(val session: Session, val treeName: String) { - fun saveTree() { - removeTreeFromDB() + inner class Neo4j(private val session: Session, private val treeName: String) : DataBase { + override fun saveTree() { + removeTree() session.run("CREATE (:$treeName)") var prevKey: String? = null breadthFirstSearch({ node -> saveNode(node, prevKey); prevKey = node?.key ?: prevKey }) @@ -22,8 +23,8 @@ class StoredCoordBSTree : BSTree>> { if (node == null) return session.executeWrite { tx -> tx.run( - "OPTIONAL MATCH (prevNode:${if (prevKey == null) treeName else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + - "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", mutableMapOf( + "OPTIONAL MATCH (prevNode:${if (prevKey == null) treeName else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", + mutableMapOf( "key" to node.key, "value" to node.value.first, "x" to node.value.second.first, @@ -33,11 +34,10 @@ class StoredCoordBSTree : BSTree>> { } } - fun readTree() { + override fun readTree() { session.executeRead { tx -> val result = tx.run( - "OPTIONAL MATCH ($treeName)-[n:next*]->(node)" + - "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" + "OPTIONAL MATCH ($treeName)-[n:next*]->(node)" + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" ) result.stream().forEach { try { @@ -52,7 +52,7 @@ class StoredCoordBSTree : BSTree>> { } } - fun removeTreeFromDB() { + override fun removeTree() { session.run("OPTIONAL MATCH (tree:$treeName)-[:next*]->(node) DETACH DELETE node, tree") } } From 8fafc3590ef123424d8a975a57ed8c49a53a586e Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 05:59:03 +0300 Subject: [PATCH 083/164] refactor: clean code --- src/main/kotlin/AVLTree.kt | 5 +---- src/main/kotlin/RBTree.kt | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 4183f3d..f333daa 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -2,11 +2,8 @@ class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, value: Value, - parent: AVLNode? = null, - left: AVLNode? = null, - right: AVLNode? = null, var height: UByte = 0U - ) : BinNode(key, value, parent, left, right) + ) : BinNode(key, value) constructor() : super() constructor(key: Key, value: Value) : super(key, value) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 82c7607..3ad8c72 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -5,11 +5,8 @@ class RBTree, Value> : BalanceTree { protected class RBNode, Value>( key: Key, value: Value, - parent: RBNode? = null, - left: RBNode? = null, - right: RBNode? = null, var color: Boolean = RED - ) : BinNode(key, value, parent, left, right) { + ) : BinNode(key, value) { fun swapColor() { color = !color } From bcb92eaa4d7a671f9cfe43faeb6b4f147b51ac60 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:09:10 +0300 Subject: [PATCH 084/164] refactor: change directory --- src/main/kotlin/{ => abstractTree}/abstractBalanceTree.kt | 5 +++-- src/main/kotlin/{ => abstractTree}/abstractBinTree.kt | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) rename src/main/kotlin/{ => abstractTree}/abstractBalanceTree.kt (89%) rename src/main/kotlin/{ => abstractTree}/abstractBinTree.kt (99%) diff --git a/src/main/kotlin/abstractBalanceTree.kt b/src/main/kotlin/abstractTree/abstractBalanceTree.kt similarity index 89% rename from src/main/kotlin/abstractBalanceTree.kt rename to src/main/kotlin/abstractTree/abstractBalanceTree.kt index 0751641..fc20d64 100644 --- a/src/main/kotlin/abstractBalanceTree.kt +++ b/src/main/kotlin/abstractTree/abstractBalanceTree.kt @@ -1,3 +1,5 @@ +package abstractTree + abstract class BalanceTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) @@ -8,8 +10,7 @@ abstract class BalanceTree, Value> : BinTree { protected fun rotation(parent: BinNode?, type: RotationType): BinNode? { //giving the parentNode parent?.let { - val node = if (type == RotationType.LEFT) - it.right ?: error("rotation is not possible") + val node = if (type == RotationType.LEFT) it.right ?: error("rotation is not possible") else it.left ?: error("rotation is not possible") when (type) { diff --git a/src/main/kotlin/abstractBinTree.kt b/src/main/kotlin/abstractTree/abstractBinTree.kt similarity index 99% rename from src/main/kotlin/abstractBinTree.kt rename to src/main/kotlin/abstractTree/abstractBinTree.kt index cfbb434..9a81c78 100644 --- a/src/main/kotlin/abstractBinTree.kt +++ b/src/main/kotlin/abstractTree/abstractBinTree.kt @@ -1,3 +1,5 @@ +package abstractTree + import interfaces.Tree import java.util.LinkedList import java.util.Queue From 7eeb6f542245be6857b030e076485cb56fc7c8c6 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:09:30 +0300 Subject: [PATCH 085/164] refactor: make classes open --- src/main/kotlin/AVLTree.kt | 8 ++++---- src/main/kotlin/BSTree.kt | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index f333daa..ab5b5b7 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,8 +1,8 @@ -class AVLTree, Value> : BalanceTree { +import abstractTree.BalanceTree + +open class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( - key: Key, - value: Value, - var height: UByte = 0U + key: Key, value: Value, var height: UByte = 0U ) : BinNode(key, value) constructor() : super() diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 84b19aa..a559fb7 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -1,3 +1,5 @@ +import abstractTree.BinTree + open class BSTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) From b2691dd6014da05de5b2c2580b79308515d8f2df Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:10:57 +0300 Subject: [PATCH 086/164] test(fix): change type of return tree --- src/test/kotlin/BSTreeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/BSTreeTest.kt b/src/test/kotlin/BSTreeTest.kt index 48a3410..ba1db74 100644 --- a/src/test/kotlin/BSTreeTest.kt +++ b/src/test/kotlin/BSTreeTest.kt @@ -18,7 +18,7 @@ fun keysToValues(vararg arr: Int, remove: Int? = null, chValue: Pair { + fun generateTreeWithInsert(vararg arr: Int): BSTree { val tree = BSTree() for (i in arr) tree.insert(i, "${i}k") return tree From e160aa1d1277a337d5c60f05de20d29e8314212b Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:11:42 +0300 Subject: [PATCH 087/164] refactor: make open --- src/main/kotlin/RBTree.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 3ad8c72..29e2964 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,11 +1,11 @@ +import abstractTree.BalanceTree + const val RED = false const val BLACK = true -class RBTree, Value> : BalanceTree { +open class RBTree, Value> : BalanceTree { protected class RBNode, Value>( - key: Key, - value: Value, - var color: Boolean = RED + key: Key, value: Value, var color: Boolean = RED ) : BinNode(key, value) { fun swapColor() { color = !color @@ -81,7 +81,6 @@ class RBTree, Value> : BalanceTree { //root color should always be black if (parent == null) (rootNode as RBNode?)?.color = BLACK - else if (parent.color == BLACK) return else { val uncle = getUncle(node) @@ -121,8 +120,7 @@ class RBTree, Value> : BalanceTree { rotation(node.parent, RotationType.LEFT) brother = (node.parent as RBNode?)?.right as RBNode? } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && - ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { @@ -147,8 +145,7 @@ class RBTree, Value> : BalanceTree { rotation(node.parent, RotationType.RIGHT) brother = (node.parent as RBNode?)?.left as RBNode? } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && - ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { From 3a70699c2687a310dc36f93a02295d3f523858b9 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:12:30 +0300 Subject: [PATCH 088/164] Revert "refactor: make open" This reverts commit e160aa1d1277a337d5c60f05de20d29e8314212b. --- src/main/kotlin/RBTree.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 29e2964..3ad8c72 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,11 +1,11 @@ -import abstractTree.BalanceTree - const val RED = false const val BLACK = true -open class RBTree, Value> : BalanceTree { +class RBTree, Value> : BalanceTree { protected class RBNode, Value>( - key: Key, value: Value, var color: Boolean = RED + key: Key, + value: Value, + var color: Boolean = RED ) : BinNode(key, value) { fun swapColor() { color = !color @@ -81,6 +81,7 @@ open class RBTree, Value> : BalanceTree { //root color should always be black if (parent == null) (rootNode as RBNode?)?.color = BLACK + else if (parent.color == BLACK) return else { val uncle = getUncle(node) @@ -120,7 +121,8 @@ open class RBTree, Value> : BalanceTree { rotation(node.parent, RotationType.LEFT) brother = (node.parent as RBNode?)?.right as RBNode? } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && + ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { @@ -145,7 +147,8 @@ open class RBTree, Value> : BalanceTree { rotation(node.parent, RotationType.RIGHT) brother = (node.parent as RBNode?)?.left as RBNode? } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && + ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { brother?.color = RED node = node.parent as RBNode } else { From 61a4035fe6838d41e7381fed8e4ed8dee007ab94 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:12:42 +0300 Subject: [PATCH 089/164] Revert "refactor: make classes open" This reverts commit 7eeb6f542245be6857b030e076485cb56fc7c8c6. --- src/main/kotlin/AVLTree.kt | 8 ++++---- src/main/kotlin/BSTree.kt | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index ab5b5b7..f333daa 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,8 +1,8 @@ -import abstractTree.BalanceTree - -open class AVLTree, Value> : BalanceTree { +class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( - key: Key, value: Value, var height: UByte = 0U + key: Key, + value: Value, + var height: UByte = 0U ) : BinNode(key, value) constructor() : super() diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index a559fb7..84b19aa 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -1,5 +1,3 @@ -import abstractTree.BinTree - open class BSTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) From 7889061d8afa84253dc47906f153d40bd546cce0 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 06:14:39 +0300 Subject: [PATCH 090/164] fix: make open and add import of abstract classes --- src/main/kotlin/AVLTree.kt | 4 +++- src/main/kotlin/BSTree.kt | 2 ++ src/main/kotlin/RBTree.kt | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index f333daa..61ed065 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,4 +1,6 @@ -class AVLTree, Value> : BalanceTree { +import abstractTree.BalanceTree + +open class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, value: Value, diff --git a/src/main/kotlin/BSTree.kt b/src/main/kotlin/BSTree.kt index 84b19aa..a559fb7 100644 --- a/src/main/kotlin/BSTree.kt +++ b/src/main/kotlin/BSTree.kt @@ -1,3 +1,5 @@ +import abstractTree.BinTree + open class BSTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 3ad8c72..025467d 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,7 +1,9 @@ +import abstractTree.BalanceTree + const val RED = false const val BLACK = true -class RBTree, Value> : BalanceTree { +open class RBTree, Value> : BalanceTree { protected class RBNode, Value>( key: Key, value: Value, From b3f677f9763d2e997bb696a1e15c32c98077c95f Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 08:07:17 +0300 Subject: [PATCH 091/164] fix: AVLTree remove --- src/main/kotlin/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 61ed065..7852f89 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -39,7 +39,7 @@ open class AVLTree, Value> : BalanceTree { //when the node has zero or one child, just remove it and balance the tree else { removeService(removeNode) - if (rootNode != null) balancing(removeNode.parent as AVLNode) + if (removeNode.parent != null) balancing(removeNode.parent as AVLNode) } } From 8731346286f4e55568e0cd9833a4f43e5d9e1732 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 08:08:29 +0300 Subject: [PATCH 092/164] refactor: rename savedTree to removeTree --- src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt | 2 +- src/main/kotlin/interfaces/DataBase.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt index c1cc1d7..67e0041 100644 --- a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt +++ b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt @@ -12,7 +12,7 @@ class StoredCoordBSTree : BSTree>> { constructor(vararg pairs: Pair>>) : super(*pairs) inner class Neo4j(private val session: Session, private val treeName: String) : DataBase { - override fun saveTree() { + override fun writeTree() { removeTree() session.run("CREATE (:$treeName)") var prevKey: String? = null diff --git a/src/main/kotlin/interfaces/DataBase.kt b/src/main/kotlin/interfaces/DataBase.kt index 292cc91..36bc1f2 100644 --- a/src/main/kotlin/interfaces/DataBase.kt +++ b/src/main/kotlin/interfaces/DataBase.kt @@ -1,7 +1,7 @@ package interfaces interface DataBase { - fun saveTree() + fun writeTree() fun readTree() fun removeTree() } \ No newline at end of file From 4f99f1b49065bcb036919ff8f3d61fa45b84cb6d Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 08:09:12 +0300 Subject: [PATCH 093/164] feat: add json save --- build.gradle.kts | 1 + .../StoredCoordAVLTree.kt | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3e69be7..5212756 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { testImplementation(platform("org.junit:junit-bom:5.9.2")) testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") implementation("org.neo4j.driver:neo4j-java-driver:5.7.0") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2") } tasks.test { diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt b/src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt new file mode 100644 index 0000000..53546c6 --- /dev/null +++ b/src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt @@ -0,0 +1,35 @@ +package coordinateTreeWithStored + +import AVLTree +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import interfaces.DataBase +import java.io.File + +class StoredCoordAVLTree: AVLTree>> { + constructor() : super() + constructor(key: String, value: Pair>) : super(key, value) + constructor(vararg pairs: Pair>>) : super(*pairs) + + inner class Json(private val fileName: String) : DataBase { + val jsonFile = File(fileName) + val mapper = jacksonObjectMapper() + + override fun writeTree() { + removeTree() + jsonFile.createNewFile() +// val nodeList = mutableListOf>>>() + val nodeList = mutableListOf>>>() + breadthFirstSearch({ node -> if (node != null) nodeList.add(Pair(node.key, node.value))}) + jsonFile.appendText(mapper.writeValueAsString(nodeList)) + } + + override fun readTree() { + insert(*mapper.readValue(jsonFile)) + } + + override fun removeTree() { + jsonFile.delete() + } + } +} \ No newline at end of file From e42a68e459ebc6889108daaa48f45fa0ebadda05 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 09:25:30 +0300 Subject: [PATCH 094/164] feat: add SQLite save --- build.gradle.kts | 3 + gradle.properties | 1 + .../StoredCoordRBTree.kt | 100 ++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5212756..7944c24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,12 +10,15 @@ repositories { mavenCentral() } +val sqliteJdbcVersion: String by project + dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.10") testImplementation(platform("org.junit:junit-bom:5.9.2")) testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") implementation("org.neo4j.driver:neo4j-java-driver:5.7.0") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2") + implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion) } tasks.test { diff --git a/gradle.properties b/gradle.properties index 7fc6f1f..1319bf0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ kotlin.code.style=official +sqliteJdbcVersion=3.41.2.1 \ No newline at end of file diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt b/src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt new file mode 100644 index 0000000..2a9b970 --- /dev/null +++ b/src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt @@ -0,0 +1,100 @@ +package coordinateTreeWithStored + +import RBTree +import abstractTree.BinTree +import interfaces.DataBase +import java.io.IOException +import java.sql.DriverManager +import java.sql.SQLException + +class StoredCoordRBTree: RBTree>> { + constructor() : super() + constructor(key: String, value: Pair>) : super(key, value) + constructor(vararg pairs: Pair>>) : super(*pairs) + + inner class SQLite(private val dbPath: String) : DataBase { + private val connection = DriverManager.getConnection("$DB_DRIVER:$dbPath") + ?: throw SQLException("Cannot connect to database") + private val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } + private val getAllNodesStatement by lazy { connection.prepareStatement("SELECT node.key as key, node.value as value, node.x as x, node.y as y FROM nodes;") } + + fun createDb() { + connection.createStatement() + .also { stmt -> + try { + stmt.execute("CREATE TABLE if not exists nodes (nodeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "key varchar(255), " + + "value varchar(255), " + + "x INTEGER, " + + "y INTEGER);" + ) + } catch (ex: Exception) { + throw IOException("Cannot create table in database") + } finally { + stmt.close() + } + } + } + + fun executeQuery(query: String) { + connection.createStatement().also { stmt -> + try { + stmt.execute(query) + } catch (ex: Exception) { + createDb() + } finally { + stmt.close() + } + } + } + + override fun writeTree() { + removeTree() + breadthFirstSearch({ node -> saveNode(node)}) + } + + private fun saveNode(node: BinTree.BinNode>>?) { + if (node != null) + try { + addNodeStatement.setString(1, node.key) + addNodeStatement.setString(2, node.value.first) + addNodeStatement.setDouble(3, node.value.second.first) + addNodeStatement.setDouble(4, node.value.second.second) + + addNodeStatement.execute() + } catch (ex: Exception) { + throw IOException("Cannot add user: ${node.key}") + } + } + + override fun readTree() { + try { + val resSet = getAllNodesStatement.executeQuery() + while (resSet.next()) { + insert( + resSet.getString("key"), + Pair(resSet.getString("value"), + Pair(resSet.getDouble("x"), resSet.getDouble("y")) + ) + ) + } + } catch (ex: Exception) { + throw IOException("Cannot get nodes from database") + } + } + + override fun removeTree() { + executeQuery("DELETE FROM nodes;") + } + + fun close() { + addNodeStatement.close() + getAllNodesStatement.close() + connection.close() + } + } + + companion object { + private const val DB_DRIVER = "jdbc:sqlite" + } +} \ No newline at end of file From 54e5abbd9e57fe4a49241b1be41bb05c28b5f4ca Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 19 Apr 2023 09:26:14 +0300 Subject: [PATCH 095/164] refactor: add line break --- src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt index 67e0041..6578b35 100644 --- a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt +++ b/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt @@ -23,7 +23,8 @@ class StoredCoordBSTree : BSTree>> { if (node == null) return session.executeWrite { tx -> tx.run( - "OPTIONAL MATCH (prevNode:${if (prevKey == null) treeName else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", + "OPTIONAL MATCH (prevNode:${if (prevKey == null) treeName else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", mutableMapOf( "key" to node.key, "value" to node.value.first, From d634e886b13b6ca77fd355ca8a662252c0157660 Mon Sep 17 00:00:00 2001 From: juliakononov <113186929+juliakononov@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:27:06 +0300 Subject: [PATCH 096/164] docs: change readme --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ccac7ff..35f7cdf 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,41 @@ # Tree Structure -Tree is a widely used abstract data type that represents a hierarchical tree structure with a set of connected nodes. +Trees are a data structures that link nodes in a parent/child relationship, in the sense that there're nodes that depend on or come off other nodes. Each node contains a key and a value. [![Apache License](https://img.shields.io/badge/license-Apache%202.0-black.svg)](https://www.apache.org/licenses/LICENSE-2.0) -## What's ready now: +### What's ready now: - Implemented the logic of three trees: AVLTree, RBTree, BSTree - - We have written tests for each tree - - Tree can be saved and read from neo4j !!!! + - Tree can be saved and read from neo4j, json, SQLite. - ## What is planned to do: + ### What's planned to do: - - Support saving and restoring trees from Json and SQL - Implement a user interface + - Implement a controller -## Build tool - -The Gradle build tool is used to manage the project -The files with the source code must be located in the directory `src/main/kotlin` \ -The test files must be located in the directory `src/test/kotlin` +## Build tool -Build code without running tests with command +The Gradle build tool is used to manage the project. +You only need to write one line to build a project ```bash -./gradlew assemble + gradle build ``` +## Usage + +- `insert` - Inserts a node in tree. It accepts the `Key` and `Value` and uses them to insert +- `remove` - Removes a node from the tree. It accepts the `Key` and uses it to delete the node +- `get` - Retrieves a given node. Use the `Key` to get the `Value`. If there is no such key in the tree the program will return null. + +Implemented saving to databases, such as Json, Neo4j, SQLite.\ + Usage guide will be soon. -Build the code and run the tests with command -```bash -./gradlew test -``` ## Neo4j -About neo4j read in [here](https://neo4j.com/). +About neo4j read in [here](https://github.com/neo4j/neo4j). From 9b0fd37ece7ef3fad6e5065e99af14bf0d4a1f60 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Thu, 20 Apr 2023 05:01:13 +0300 Subject: [PATCH 097/164] fix: first day patch fix SQLite save (node -> nodes) fix Neo4j save ($treeName -> :$treeName) refactor: rename dir name delete trash in Json save --- .../StoredCoordAVLTree.kt | 3 +-- .../StoredCoordBSTree.kt | 4 ++-- .../StoredCoordRBTree.kt | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) rename src/main/kotlin/{coordinateTreeWithStored => storedTreeWithCoordinate}/StoredCoordAVLTree.kt (90%) rename src/main/kotlin/{coordinateTreeWithStored => storedTreeWithCoordinate}/StoredCoordBSTree.kt (92%) rename src/main/kotlin/{coordinateTreeWithStored => storedTreeWithCoordinate}/StoredCoordRBTree.kt (96%) diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt b/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt similarity index 90% rename from src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt rename to src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt index 53546c6..76b6f39 100644 --- a/src/main/kotlin/coordinateTreeWithStored/StoredCoordAVLTree.kt +++ b/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt @@ -1,4 +1,4 @@ -package coordinateTreeWithStored +package storedTreeWithCoordinate import AVLTree import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper @@ -18,7 +18,6 @@ class StoredCoordAVLTree: AVLTree>> { override fun writeTree() { removeTree() jsonFile.createNewFile() -// val nodeList = mutableListOf>>>() val nodeList = mutableListOf>>>() breadthFirstSearch({ node -> if (node != null) nodeList.add(Pair(node.key, node.value))}) jsonFile.appendText(mapper.writeValueAsString(nodeList)) diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt b/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt similarity index 92% rename from src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt rename to src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt index 6578b35..78f4d54 100644 --- a/src/main/kotlin/coordinateTreeWithStored/StoredCoordBSTree.kt +++ b/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt @@ -1,4 +1,4 @@ -package coordinateTreeWithStored +package storedTreeWithCoordinate import BSTree import interfaces.DataBase @@ -38,7 +38,7 @@ class StoredCoordBSTree : BSTree>> { override fun readTree() { session.executeRead { tx -> val result = tx.run( - "OPTIONAL MATCH ($treeName)-[n:next*]->(node)" + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" + "OPTIONAL MATCH (:$treeName)-[n:next*]->(node)" + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" ) result.stream().forEach { try { diff --git a/src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt b/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt similarity index 96% rename from src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt rename to src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt index 2a9b970..c2d4d39 100644 --- a/src/main/kotlin/coordinateTreeWithStored/StoredCoordRBTree.kt +++ b/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt @@ -1,4 +1,4 @@ -package coordinateTreeWithStored +package storedTreeWithCoordinate import RBTree import abstractTree.BinTree @@ -16,7 +16,7 @@ class StoredCoordRBTree: RBTree>> { private val connection = DriverManager.getConnection("$DB_DRIVER:$dbPath") ?: throw SQLException("Cannot connect to database") private val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } - private val getAllNodesStatement by lazy { connection.prepareStatement("SELECT node.key as key, node.value as value, node.x as x, node.y as y FROM nodes;") } + private val getAllNodesStatement by lazy { connection.prepareStatement("SELECT nodes.key as key, nodes.value as value, nodes.x as x, nodes.y as y FROM nodes;") } fun createDb() { connection.createStatement() From aa30b5ca750f19a17c7dde5fe3a616474d8523a5 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 23 Apr 2023 00:27:12 +0300 Subject: [PATCH 098/164] refactor(gradle)!: change gradle project struct now trees in lib, save to db and app in app change dependencies add gradle conventions --- build.gradle.kts => app/build.gradle.kts | 23 +++++-------------- .../src/main/kotlin/dataBase}/DataBase.kt | 2 +- .../main/kotlin/dataBase/neo4j functions.kt | 0 .../StoredCoordAVLTree.kt | 2 +- .../StoredCoordBSTree.kt | 2 +- .../StoredCoordRBTree.kt | 3 +-- build-logic/build.gradle.kts | 11 +++++++++ build-logic/settings.gradle.kts | 1 + ....kotlin-application-conventions.gradle.kts | 5 ++++ ...trees.kotlin-common-conventions.gradle.kts | 17 ++++++++++++++ ...rees.kotlin-library-conventions.gradle.kts | 5 ++++ lib/build.gradle.kts | 3 +++ {src => lib/src}/main/kotlin/AVLTree.kt | 4 +--- {src => lib/src}/main/kotlin/BSTree.kt | 2 -- .../src/main/kotlin/BalanceTree.kt | 2 -- .../src/main/kotlin/BinTree.kt | 4 ---- {src => lib/src}/main/kotlin/RBTree.kt | 12 +++++----- .../src/main/kotlin}/Tree.kt | 4 +--- {src => lib/src}/test/kotlin/AVLTreeTest.kt | 0 {src => lib/src}/test/kotlin/BSTreeTest.kt | 0 {src => lib/src}/test/kotlin/RBTreeTest.kt | 0 settings.gradle.kts | 6 ++++- 22 files changed, 65 insertions(+), 43 deletions(-) rename build.gradle.kts => app/build.gradle.kts (51%) rename {src/main/kotlin/interfaces => app/src/main/kotlin/dataBase}/DataBase.kt (81%) rename {src => app/src}/main/kotlin/dataBase/neo4j functions.kt (100%) rename {src => app/src}/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt (97%) rename {src => app/src}/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt (98%) rename {src => app/src}/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt (98%) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/trees.kotlin-application-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/trees.kotlin-common-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/trees.kotlin-library-conventions.gradle.kts create mode 100644 lib/build.gradle.kts rename {src => lib/src}/main/kotlin/AVLTree.kt (99%) rename {src => lib/src}/main/kotlin/BSTree.kt (93%) rename src/main/kotlin/abstractTree/abstractBalanceTree.kt => lib/src/main/kotlin/BalanceTree.kt (98%) rename src/main/kotlin/abstractTree/abstractBinTree.kt => lib/src/main/kotlin/BinTree.kt (99%) rename {src => lib/src}/main/kotlin/RBTree.kt (98%) rename {src/main/kotlin/interfaces => lib/src/main/kotlin}/Tree.kt (92%) rename {src => lib/src}/test/kotlin/AVLTreeTest.kt (100%) rename {src => lib/src}/test/kotlin/BSTreeTest.kt (100%) rename {src => lib/src}/test/kotlin/RBTreeTest.kt (100%) diff --git a/build.gradle.kts b/app/build.gradle.kts similarity index 51% rename from build.gradle.kts rename to app/build.gradle.kts index 7944c24..8b1297a 100644 --- a/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,33 +1,22 @@ plugins { - kotlin("jvm") version "1.8.0" - application + id("trees.kotlin-application-conventions") } group = "org.example" version = "1.0-SNAPSHOT" -repositories { - mavenCentral() -} - val sqliteJdbcVersion: String by project dependencies { - testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.10") - testImplementation(platform("org.junit:junit-bom:5.9.2")) - testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") + implementation(project(":lib")) implementation("org.neo4j.driver:neo4j-java-driver:5.7.0") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2") implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion) } - -tasks.test { - useJUnitPlatform() -} - -kotlin { - jvmToolchain(8) -} +// +//kotlin { +// jvmToolchain(8) +//} application { mainClass.set("MainKt") diff --git a/src/main/kotlin/interfaces/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt similarity index 81% rename from src/main/kotlin/interfaces/DataBase.kt rename to app/src/main/kotlin/dataBase/DataBase.kt index 36bc1f2..bbbf8af 100644 --- a/src/main/kotlin/interfaces/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -1,4 +1,4 @@ -package interfaces +package dataBase interface DataBase { fun writeTree() diff --git a/src/main/kotlin/dataBase/neo4j functions.kt b/app/src/main/kotlin/dataBase/neo4j functions.kt similarity index 100% rename from src/main/kotlin/dataBase/neo4j functions.kt rename to app/src/main/kotlin/dataBase/neo4j functions.kt diff --git a/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt similarity index 97% rename from src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt rename to app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt index 76b6f39..13448a8 100644 --- a/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt +++ b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt @@ -3,7 +3,7 @@ package storedTreeWithCoordinate import AVLTree import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import interfaces.DataBase +import dataBase.DataBase import java.io.File class StoredCoordAVLTree: AVLTree>> { diff --git a/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt similarity index 98% rename from src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt rename to app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt index 78f4d54..ba96541 100644 --- a/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt +++ b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt @@ -1,7 +1,7 @@ package storedTreeWithCoordinate import BSTree -import interfaces.DataBase +import dataBase.DataBase import org.neo4j.driver.Session import org.neo4j.driver.exceptions.value.Uncoercible import java.io.IOException diff --git a/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt similarity index 98% rename from src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt rename to app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt index c2d4d39..cb48dd3 100644 --- a/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt +++ b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt @@ -1,8 +1,7 @@ package storedTreeWithCoordinate import RBTree -import abstractTree.BinTree -import interfaces.DataBase +import dataBase.DataBase import java.io.IOException import java.sql.DriverManager import java.sql.SQLException diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..8bfe7be --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..d1a9718 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "trees-build-logic" diff --git a/build-logic/src/main/kotlin/trees.kotlin-application-conventions.gradle.kts b/build-logic/src/main/kotlin/trees.kotlin-application-conventions.gradle.kts new file mode 100644 index 0000000..d0520c8 --- /dev/null +++ b/build-logic/src/main/kotlin/trees.kotlin-application-conventions.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("trees.kotlin-common-conventions") + + application +} diff --git a/build-logic/src/main/kotlin/trees.kotlin-common-conventions.gradle.kts b/build-logic/src/main/kotlin/trees.kotlin-common-conventions.gradle.kts new file mode 100644 index 0000000..06c11bd --- /dev/null +++ b/build-logic/src/main/kotlin/trees.kotlin-common-conventions.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("org.jetbrains.kotlin.jvm") +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.10") + testImplementation(platform("org.junit:junit-bom:5.9.2")) + testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") +} + +tasks.test { + useJUnitPlatform() +} diff --git a/build-logic/src/main/kotlin/trees.kotlin-library-conventions.gradle.kts b/build-logic/src/main/kotlin/trees.kotlin-library-conventions.gradle.kts new file mode 100644 index 0000000..a29f85b --- /dev/null +++ b/build-logic/src/main/kotlin/trees.kotlin-library-conventions.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("trees.kotlin-common-conventions") + + `java-library` +} diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts new file mode 100644 index 0000000..b583248 --- /dev/null +++ b/lib/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("trees.kotlin-library-conventions") +} diff --git a/src/main/kotlin/AVLTree.kt b/lib/src/main/kotlin/AVLTree.kt similarity index 99% rename from src/main/kotlin/AVLTree.kt rename to lib/src/main/kotlin/AVLTree.kt index 7852f89..a03871a 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/lib/src/main/kotlin/AVLTree.kt @@ -1,5 +1,3 @@ -import abstractTree.BalanceTree - open class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, @@ -92,4 +90,4 @@ open class AVLTree, Value> : BalanceTree { val right = node.right?.let { (it as AVLNode).height.toInt() } ?: 0 return (right - left) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/BSTree.kt b/lib/src/main/kotlin/BSTree.kt similarity index 93% rename from src/main/kotlin/BSTree.kt rename to lib/src/main/kotlin/BSTree.kt index a559fb7..84b19aa 100644 --- a/src/main/kotlin/BSTree.kt +++ b/lib/src/main/kotlin/BSTree.kt @@ -1,5 +1,3 @@ -import abstractTree.BinTree - open class BSTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) diff --git a/src/main/kotlin/abstractTree/abstractBalanceTree.kt b/lib/src/main/kotlin/BalanceTree.kt similarity index 98% rename from src/main/kotlin/abstractTree/abstractBalanceTree.kt rename to lib/src/main/kotlin/BalanceTree.kt index fc20d64..28ef0a0 100644 --- a/src/main/kotlin/abstractTree/abstractBalanceTree.kt +++ b/lib/src/main/kotlin/BalanceTree.kt @@ -1,5 +1,3 @@ -package abstractTree - abstract class BalanceTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) diff --git a/src/main/kotlin/abstractTree/abstractBinTree.kt b/lib/src/main/kotlin/BinTree.kt similarity index 99% rename from src/main/kotlin/abstractTree/abstractBinTree.kt rename to lib/src/main/kotlin/BinTree.kt index 9a81c78..23f68c6 100644 --- a/src/main/kotlin/abstractTree/abstractBinTree.kt +++ b/lib/src/main/kotlin/BinTree.kt @@ -1,6 +1,3 @@ -package abstractTree - -import interfaces.Tree import java.util.LinkedList import java.util.Queue import kotlin.math.abs @@ -12,7 +9,6 @@ abstract class BinTree, Value> : Tree { var parent: BinNode? = null, var left: BinNode? = null, var right: BinNode? = null - ) : Comparable { override fun compareTo(other: Key): Int { return key.compareTo(other) diff --git a/src/main/kotlin/RBTree.kt b/lib/src/main/kotlin/RBTree.kt similarity index 98% rename from src/main/kotlin/RBTree.kt rename to lib/src/main/kotlin/RBTree.kt index 025467d..3cd736c 100644 --- a/src/main/kotlin/RBTree.kt +++ b/lib/src/main/kotlin/RBTree.kt @@ -1,9 +1,9 @@ -import abstractTree.BalanceTree - -const val RED = false -const val BLACK = true - open class RBTree, Value> : BalanceTree { + companion object { + const val RED = false + const val BLACK = true + } + protected class RBNode, Value>( key: Key, value: Value, @@ -170,4 +170,4 @@ open class RBTree, Value> : BalanceTree { node.color = BLACK } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/interfaces/Tree.kt b/lib/src/main/kotlin/Tree.kt similarity index 92% rename from src/main/kotlin/interfaces/Tree.kt rename to lib/src/main/kotlin/Tree.kt index bf41c2e..6d04116 100644 --- a/src/main/kotlin/interfaces/Tree.kt +++ b/lib/src/main/kotlin/Tree.kt @@ -1,5 +1,3 @@ -package interfaces - interface Tree { fun insert(key: Key, value: Value) fun insert(vararg array: Pair) @@ -7,4 +5,4 @@ interface Tree { fun remove(vararg keys: Key) fun get(key: Key): Value? fun get(vararg keys: Key): List -} \ No newline at end of file +} diff --git a/src/test/kotlin/AVLTreeTest.kt b/lib/src/test/kotlin/AVLTreeTest.kt similarity index 100% rename from src/test/kotlin/AVLTreeTest.kt rename to lib/src/test/kotlin/AVLTreeTest.kt diff --git a/src/test/kotlin/BSTreeTest.kt b/lib/src/test/kotlin/BSTreeTest.kt similarity index 100% rename from src/test/kotlin/BSTreeTest.kt rename to lib/src/test/kotlin/BSTreeTest.kt diff --git a/src/test/kotlin/RBTreeTest.kt b/lib/src/test/kotlin/RBTreeTest.kt similarity index 100% rename from src/test/kotlin/RBTreeTest.kt rename to lib/src/test/kotlin/RBTreeTest.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index c18280d..248e7e9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,6 @@ -rootProject.name = "trees" +pluginManagement { + includeBuild("build-logic") +} +rootProject.name = "trees" +include("lib", "app") From 345679f07094bb1e5b82d3623bfd287cf8e00870 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 23 Apr 2023 00:37:04 +0300 Subject: [PATCH 099/164] refactor(gitignore): update and clean gitignore --- .gitignore | 105 +++++------------------------------------------------ 1 file changed, 9 insertions(+), 96 deletions(-) diff --git a/.gitignore b/.gitignore index 9e7d0d6..fd4e6bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,119 +1,24 @@ -# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij,windows,linux,kotlin -# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,intellij,windows,linux,kotlin - -### 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 - ### Kotlin ### # Compiled class file *.class @@ -200,6 +105,14 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk -# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij,windows,linux,kotlin /.idea/ + /.gradle/ + +build-logic/build/ + +build-logic/.gradle/ + +lib/build/ + +app/build/ From 7cd47ece740405cf2f1a3d77e993f60edbfbfc8f Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 24 Apr 2023 03:15:34 +0300 Subject: [PATCH 100/164] fix: some tests in RBTree --- src/test/kotlin/RBTreeTest.kt | 50 +++++++++++------------------------ 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/src/test/kotlin/RBTreeTest.kt b/src/test/kotlin/RBTreeTest.kt index d4d2dbc..aeabda5 100644 --- a/src/test/kotlin/RBTreeTest.kt +++ b/src/test/kotlin/RBTreeTest.kt @@ -22,11 +22,7 @@ class RBTreeTest { return Stream.of( Arguments.of(arrayOf(4), null, "insert one node test"), Arguments.of(arrayOf(4), Pair(4, "5k"), "two inserts with eq. keys of the first node"), - Arguments.of( - arrayOf(4, 1, 5, 6), - Pair(4, "5k"), - "two inserts with eq. keys of the first node in non-degenerate tree" - ), + Arguments.of(arrayOf(4, 1, 5, 6), Pair(4, "5k"), "two inserts with eq. keys of the first node in non-degenerate tree"), Arguments.of(arrayOf(5, 6, 4), Pair(4, "5k"), "two inserts with eq. keys of node"), Arguments.of(Array(1000) { Random.nextInt() }, Pair(Random.nextInt(), "random"), "random insert") ) @@ -50,43 +46,29 @@ class RBTreeTest { fun debugTestsFactory(): Stream { return Stream.of( Arguments.of(arrayOf(4), "4 \n", "insert root"), - Arguments.of( - arrayOf(5, 6, 3, 4, 1, 2), "5 \n3 6 \n1 4 - - \n- 2 ", "grandfather isn't root, uncle red" - ), + Arguments.of(arrayOf(5, 6, 3, 4, 1, 2), "5 \n3 6 \n1 4 - - \n- 2 ", "grandfather isn't root, uncle red"), Arguments.of(arrayOf(6, 3, 8, 4), "6 \n3 8 \n- 4 ", "grandfather root, red uncle)"), Arguments.of(arrayOf(6, 4, 5), "5 \n4 6 \n", "zigzag, null uncle"), Arguments.of(arrayOf(5, 4, 3), "4 \n3 5 \n", "straight line, null uncle"), Arguments.of(arrayOf(8, 9, 5, 6, 3, 2, 4, 1), "5 \n3 8 \n2 4 6 9 \n1 ", "change color, right rotation"), Arguments.of(arrayOf(8, 9, 5, 6, 3, 1, 2), "8 \n5 9 \n2 6 - - \n1 3 ", "two rotation"), - ) + ) } @JvmStatic fun debugRemoveTestsFactory(): Stream { return Stream.of( - Arguments.of(arrayOf(4, 2, 5, 3), 3, "4 \n2 5 \n", "remove red leaf"), - Arguments.of(arrayOf(4, 2, 5, 3), 2, "4 \n3 5 \n", "remove black with red child"), - Arguments.of( - arrayOf(5, 2, 8, 7, 9, 10, 6), 2, "8 \n6 9 \n5 7 - 10 \n", "remove left black with red brother 1" - ), - Arguments.of( - arrayOf(5, 2, 8, 10, 6, 7, 9), 2, "8 \n6 10 \n5 7 9 ", "remove left black with red brother 2" - ), - Arguments.of( - arrayOf(5, 2, 8, 1, 3, 0, 4), 8, "2 \n1 4 \n0 - 3 5 \n", "remove right black with red brother 1" - ), - Arguments.of( - arrayOf(5, 2, 8, 0, 4, 1, 3), 8, "2 \n0 4 \n- 1 3 5 \n", "remove right black with red brother 2" - ), - Arguments.of(arrayOf(5, 2, 8, 7, 9), 2, "8 \n5 9 \n- 7 ", "remove black with black brother right"), - Arguments.of(arrayOf(5, 2, 8, 1, 3), 8, "2 \n1 5 \n- - 3 ", "remove black with black brother left"), - Arguments.of( - arrayOf(3, 1, 9, 7, 11, 5, 8), - 7, - "3 \n1 9 \n- - 8 11 \n- - - - 5 ", - "remove node with two red children" - ), - ) +// Arguments.of(arrayOf(4, 2, 5, 3), arrayOf(3), "4 \n2 5 \n", "remove red leaf"), +// Arguments.of(arrayOf(4, 2, 5, 3), arrayOf(2), "4 \n3 5 \n", "remove black with red child"), + Arguments.of(arrayOf(5, 2, 8, 7, 9, 10, 6), arrayOf(2), "6 \n5 8 \n- - 7 9 \n- - - - - - - 10 \n", "remove left black with red brother 1"), + Arguments.of(arrayOf(5, 2, 8, 10, 6, 7, 9), arrayOf(2), "6 \n5 8 \n- - 7 10 \n- - - - - - 9 ", "remove left black with red brother 2"), + Arguments.of(arrayOf(5, 2, 8, 1, 3, 0, 4), arrayOf(8), "4 \n2 5 \n1 3 - - \n0 ", "remove right black with red brother 1"), + Arguments.of(arrayOf(5, 2, 8, 0, 4, 1, 3), arrayOf(8), "4 \n2 5 \n0 3 - - \n- 1 ", "remove right black with red brother 2"), + Arguments.of(arrayOf(5, 2, 8, 7, 9), arrayOf(2), "8 \n5 9 \n- 7 ", "remove black with black brother right"), + Arguments.of(arrayOf(5, 2, 8, 1, 3), arrayOf(8), "2 \n1 5 \n- - 3 ", "remove black with black brother left"), + Arguments.of(arrayOf(3, 1, 9, 7, 11, 5, 8), arrayOf(7), "3 \n1 9 \n- - 8 11 \n- - - - 5 ", "remove node with two red children"), + Arguments.of(arrayOf(96, 69, 3, 49, 89, 61, 61, 16, 33, 21), arrayOf(69, 49), "61 \n16 89 \n3 33 - 96 \n- - 21 ", "add something"), + ) } } @@ -123,9 +105,9 @@ class RBTreeTest { @ParameterizedTest(name = "{3} ({0}, {1}") @MethodSource("debugRemoveTestsFactory") @DisplayName("remove tests using debug") - fun removeTestsWithDebug(keys: Array, remove: Int, treeInString: String, name: String) { + fun removeTestsWithDebug(keys: Array, remove: Array, treeInString: String, name: String) { val tree = generateTreeWithInsert(*keys.toIntArray()) - tree.remove(remove) + remove.forEach { tree.remove(it)} Assertions.assertEquals(treeInString, tree.Debug().treeKeysInString()) } From 8dd2afa63fa06cac28618f2e7e9c888dd9807e9e Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 24 Apr 2023 03:18:35 +0300 Subject: [PATCH 101/164] fix: RBTree balancer after remove --- src/main/kotlin/RBTree.kt | 172 ++++++++++++++++++++++++++++---------- 1 file changed, 130 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 025467d..7cffbac 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -83,7 +83,6 @@ open class RBTree, Value> : BalanceTree { //root color should always be black if (parent == null) (rootNode as RBNode?)?.color = BLACK - else if (parent.color == BLACK) return else { val uncle = getUncle(node) @@ -114,60 +113,149 @@ open class RBTree, Value> : BalanceTree { var node = removeNode while ((node != rootNode) && (node?.color == BLACK)) { - var brother = getSibling(node) + val parent = node.parent as RBNode? + val brother = getSibling(node) ?: error("remove error: brother must exist") + //balancing when a node is the left child of its parent - if (node == node.parent?.left) { - if (brother?.color == RED) { - (node.parent as RBNode?)?.swapColor() - brother.swapColor() - rotation(node.parent, RotationType.LEFT) - brother = (node.parent as RBNode?)?.right as RBNode? + if (node == parent?.left) { + + //if the parent color is red, the brother's color must be black + if (parent.color == RED) { + + //case when brother has a red child + if (((brother.left as RBNode?)?.color == RED) || + ((brother.right as RBNode?)?.color == RED)) { + parent.color = BLACK + if ((brother.left as RBNode?)?.color == RED) { + rotation(brother, RotationType.RIGHT) + } else { + brother.color = RED + (brother.right as RBNode?)?.color = BLACK + } + node = rotation(parent, RotationType.LEFT) as RBNode? + } else { + brother.swapColor() + parent.swapColor() + } + break } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && - ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { - brother?.color = RED - node = node.parent as RBNode - } else { - if ((brother.right == null) || (brother.right as RBNode?)?.color == BLACK) { - (brother.left as RBNode?)?.color = BLACK - brother.color = RED + + else if (brother.color == RED) { + //brother's left child must exist and his color must be black + var brotherLeftChild = + brother.left as RBNode? ?: error("remove error: brother's left child must exist") + + if (((brotherLeftChild.left as RBNode?)?.color == RED) || + ((brotherLeftChild.right as RBNode?)?.color == RED)) { + if ((brotherLeftChild.left as RBNode?)?.color == RED) { + brotherLeftChild.swapColor() + (brotherLeftChild.left as RBNode?)?.swapColor() + brotherLeftChild = rotation(brotherLeftChild, RotationType.RIGHT) as RBNode + } + (brotherLeftChild.right as RBNode?)?.swapColor() rotation(brother, RotationType.RIGHT) - brother = node.parent?.right as RBNode? } - brother?.color = (node.parent as RBNode).color - (node.parent as RBNode).color = BLACK - (brother?.right as RBNode).color = BLACK - rotation(node.parent as RBNode?, RotationType.LEFT) - node = rootNode as RBNode + + else { + brother.swapColor() + brotherLeftChild.swapColor() + } + rotation(parent, RotationType.LEFT) as RBNode? + break + } + + //if brother's color is black + else { + if (((brother.left == null) || (brother.left as RBNode?)?.color == BLACK) && + ((brother.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + brother.color = RED + node = parent + } + + else { + if ((brother.right == null) || (brother.right as RBNode?)?.color == BLACK) { + (brother.left as RBNode?)?.color = BLACK + rotation(brother, RotationType.RIGHT) + } + else { + (brother.right as RBNode?)?.color = BLACK + } + rotation(parent, RotationType.LEFT) as RBNode? + break + } } } + //balancing when a node is the right child of its parent else { - if (brother?.color == RED) { - (node.parent as RBNode?)?.swapColor() - brother.swapColor() - rotation(node.parent, RotationType.RIGHT) - brother = (node.parent as RBNode?)?.left as RBNode? + + //if the parent color is red, the brother's color must be black + if (parent?.color == RED) { + + //case when brother has a red child + if (((brother.left as RBNode?)?.color == RED) || + ((brother.right as RBNode?)?.color == RED)) { + parent.color = BLACK + if ((brother.right as RBNode?)?.color == RED) { + rotation(brother, RotationType.LEFT) + } else { + brother.color = RED + (brother.left as RBNode?)?.color = BLACK + } + node = rotation(parent, RotationType.RIGHT) as RBNode? + } + + else { + brother.swapColor() + parent.swapColor() + } + break } - if (((brother?.left == null) || (brother.left as RBNode?)?.color == BLACK) && - ((brother?.right == null) || (brother.right as RBNode?)?.color == BLACK)) { - brother?.color = RED - node = node.parent as RBNode - } else { - if ((brother.left == null) || (brother.left as RBNode?)?.color == BLACK) { - (brother.right as RBNode?)?.color = BLACK - brother.color = RED + + else if (brother.color == RED) { + //brother's right child must exist and his color must be black + var brotherRightChild = + brother.right as RBNode? ?: error("remove error: brother's right child must exist") + + if (((brotherRightChild.left as RBNode?)?.color == RED) || + ((brotherRightChild.right as RBNode?)?.color == RED) + ) { + if ((brotherRightChild.right as RBNode?)?.color == RED) { + brotherRightChild.swapColor() + (brotherRightChild.right as RBNode?)?.swapColor() + brotherRightChild = rotation(brotherRightChild, RotationType.LEFT) as RBNode + } + (brotherRightChild.left as RBNode?)?.swapColor() rotation(brother, RotationType.LEFT) - brother = node.parent?.left as RBNode? } - brother?.color = (node.parent as RBNode).color - (node.parent as RBNode).color = BLACK - (brother?.left as RBNode).color = BLACK - rotation(node.parent as RBNode?, RotationType.RIGHT) - node = rootNode as RBNode + + else { + brother.swapColor() + brotherRightChild.swapColor() + } + rotation(parent, RotationType.RIGHT) as RBNode? + break + } + + //if brother's color is black + else { + if (((brother.left == null) || (brother.left as RBNode?)?.color == BLACK) && + ((brother.right == null) || (brother.right as RBNode?)?.color == BLACK)) { + brother.color = RED + node = parent + } else { + if ((brother.left == null) || (brother.left as RBNode?)?.color == BLACK) { + (brother.right as RBNode?)?.color = BLACK + rotation(brother, RotationType.LEFT) + } + else { + (brother.left as RBNode?)?.color = BLACK + } + rotation(parent, RotationType.RIGHT) as RBNode? + break + } } } - node.color = BLACK } } } \ No newline at end of file From d92f88dfaa3fb8990aeda9dc1c0b181f6bf73bf9 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 24 Apr 2023 19:51:29 +0300 Subject: [PATCH 102/164] refactor(database): Completely reworked saving to databases Now all the operations with databases are in the dataBase dir Add function getKeyValueList to the BinTree --- app/src/main/kotlin/dataBase/DataBase.kt | 30 +++- app/src/main/kotlin/dataBase/Json.kt | 70 +++++++++ app/src/main/kotlin/dataBase/Neo4j.kt | 99 ++++++++++++ app/src/main/kotlin/dataBase/SQLite.kt | 145 ++++++++++++++++++ .../main/kotlin/dataBase/neo4j functions.kt | 30 ---- .../StoredCoordAVLTree.kt | 34 ---- .../StoredCoordBSTree.kt | 60 -------- .../StoredCoordRBTree.kt | 99 ------------ lib/src/main/kotlin/BinTree.kt | 12 +- 9 files changed, 349 insertions(+), 230 deletions(-) create mode 100644 app/src/main/kotlin/dataBase/Json.kt create mode 100644 app/src/main/kotlin/dataBase/Neo4j.kt create mode 100644 app/src/main/kotlin/dataBase/SQLite.kt delete mode 100644 app/src/main/kotlin/dataBase/neo4j functions.kt delete mode 100644 app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt delete mode 100644 app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt delete mode 100644 app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index bbbf8af..3e219f5 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -1,7 +1,31 @@ package dataBase +import AVLTree +import BSTree +import BinTree +import RBTree +import java.sql.SQLException + interface DataBase { - fun writeTree() - fun readTree() - fun removeTree() + fun isSupportTreeType(treeType: String): Boolean { + val supportTypes = arrayOf("BSTree", "RBTree", "AVLTree") + return (treeType in supportTypes) + } + + fun typeToTree(type: String): BinTree>> = when (type) { + "BSTree" -> BSTree() + "RBTree" -> RBTree() + "AVLTree" -> AVLTree() + else -> throw SQLException("invalid type of tree") + } + fun saveTree(treeName: String, tree: BinTree>>, treeType: String) + fun readTree(treeName: String): BinTree>> + fun removeTree(treeName: String) + fun getAllTree(): List> + fun clean() + fun close() + fun cleanAndClose() { + clean() + close() + } } \ No newline at end of file diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt new file mode 100644 index 0000000..f17cc9f --- /dev/null +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -0,0 +1,70 @@ +package dataBase + +import BinTree +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.io.path.extension + +class Json(private val dirName: String) : DataBase { + private val mapper = jacksonObjectMapper() + + init { + if (dirName.last() != '\\' && dirName.last() != '/') throw IllegalArgumentException("Please, don't use '/' or '\\' in the end of dir path") + } + + private fun getFile(treeName: String) = try { + File("${dirName}${treeName}/.json") + } catch (ex: Exception) { + throw IOException("cannot get file with name: ${dirName}${treeName}/.json") + } + + override fun saveTree( + treeName: String, tree: BinTree>>, treeType: String + ) { + if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") + if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + removeTree(treeName) + val jsonFile = getFile(treeName) + jsonFile.createNewFile() + jsonFile.appendText(mapper.writeValueAsString(Pair(treeName, treeType))) + jsonFile.appendText(mapper.writeValueAsString(tree.getKeyValueList())) + } + + override fun readTree(treeName: String): BinTree>> { + val jsonFile = getFile(treeName) + + val treeParams = mapper.readValue>(jsonFile) + val tree = typeToTree(treeParams.second) + tree.insert(*mapper.readValue(jsonFile)) + return tree + } + + override fun removeTree(treeName: String) { + getFile(treeName).delete() + } + + private fun forAllJsonFile(function: (File) -> Unit) { + Files.walk(Paths.get(dirName)).use { path -> + path.filter { Files.isRegularFile(it) && Files.isWritable(it) && (it.extension == "json") } + .forEach { function(it.toFile()) } + } + } + + override fun getAllTree(): List> { + val list = mutableListOf>() + forAllJsonFile { list.add(mapper.readValue(it)) } + + return list + } + + override fun clean() { + forAllJsonFile { it.delete() } + } + + override fun close() { + } +} diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt new file mode 100644 index 0000000..626e06f --- /dev/null +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -0,0 +1,99 @@ +package dataBase + +import BinTree +import org.neo4j.driver.AuthTokens +import org.neo4j.driver.Driver +import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.Session +import org.neo4j.driver.exceptions.value.Uncoercible +import java.io.IOException + +class Neo4j(uri: String, user: String, password: String) : DataBase { + private var driver: Driver + private var session: Session + + init { + try { + driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) + session = driver.session() + } catch (e: IllegalArgumentException) { + throw IOException("can't start session, try to change uri, user name or password") + } + } + + override fun saveTree(treeName: String, tree: BinTree>>, treeType: String) { + if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") + if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + removeTree(treeName) + session.run("CREATE (:Tree {name: \$$treeName}, type: \$$treeType)") + var prevKey: String? = null + tree.getKeyValueList().forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } + } + + private fun saveNode(key: String, value: String, coordinate: Pair, prevKey: String?, treeName: String) { + session.executeWrite { tx -> + tx.run( + "OPTIONAL MATCH (prevNode:${if (prevKey == null) "Tree WHERE prevNode.name = $treeName" else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", + mutableMapOf( + "key" to key, + "value" to value, + "x" to coordinate.first, + "y" to coordinate.second + ) as Map + ) + } + } + + override fun readTree(treeName: String): BinTree>> { + var type = "" + session.executeRead { tx -> + type = tx.run("OPTIONAL MATCH (tree:Tree WHERE tree.name = $treeName) RETURN tree.type").toString() + } + + val tree = typeToTree(type.toString()) + + session.executeRead { tx -> + val result = tx.run( + "OPTIONAL MATCH (tree:Tree WHERE tree.name = $treeName)-[n:next*]->(node) RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" + ) + + result.stream().forEach { + try { + tree.insert( + it["key"].asString(), + Pair(it["value"].asString(), + Pair(it["x"].asDouble(), it["y"].asDouble())) + ) + } catch (e: Uncoercible) { + throw IOException("Corrupted data in the database.\n Possible solution: Clear the data.") + } + } + } + return tree + } + + override fun removeTree(treeName: String) { + session.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = $treeName)-[:next*]->(node) DETACH DELETE node, tree") + } + + override fun getAllTree(): List> { + val list = mutableListOf>() + session.executeRead { tx -> + val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type") + result.stream().forEach { + list.add(Pair(it["name"].asString(), it["type"].asString())) + } + } + return list + } + + override fun close() { + session.close() + driver.close() + } + + override fun clean() { + session.run("MATCH (n) DETACH DELETE n") + } +} diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt new file mode 100644 index 0000000..62e2cec --- /dev/null +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -0,0 +1,145 @@ +package dataBase + +import BinTree +import java.io.IOException +import java.sql.DriverManager +import java.sql.SQLException + +class SQLite (dbPath: String): DataBase { + companion object { + private const val MAX_STRING_LEN = 255 //need to take out to properties + private const val DB_DRIVER = "jdbc:sqlite" + } + + private val connection = DriverManager.getConnection("$DB_DRIVER:$dbPath") + ?: throw SQLException("Cannot connect to database") + private val addTreeStatement by lazy { connection.prepareStatement("INSERT INTO trees (name, type) VALUES (?, ?);") } + private val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO ?Nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } + private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type FROM trees;") } + + override fun saveTree(treeName: String, tree: BinTree>>, treeType: String) { + if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") + if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + createTreesTable() + createTableForTree(treeName) + removeTree(treeName) + addTree(treeName, treeType) + tree.getKeyValueList().forEach{ saveNode(it.first, it.second.first, it.second.second, treeName) } + } + + private fun saveNode( + key: String, + value: String, + coordinate: Pair, + treeName: String + ) { + try { + addNodeStatement.setString(1, treeName) + addNodeStatement.setString(2, key) + addNodeStatement.setString(3, value) + addNodeStatement.setDouble(4, coordinate.first) + addNodeStatement.setDouble(5, coordinate.second) + + addNodeStatement.execute() + } catch (ex: Exception) { + throw SQLException("Cannot add node with key: \"${key}\" in tree: $treeName") + } + } + + private fun createTreesTable() { + try { + executeQuery("CREATE TABLE if not exists trees (treeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "name varchar($MAX_STRING_LEN), " + + "type varchar($MAX_STRING_LEN);" + ) + } catch (ex: Exception) { + throw SQLException("Cannot create table in database") + } + } + + private fun createTableForTree(treeName: String) { + try { + executeQuery( + "CREATE TABLE if not exists ${treeName}Nodes (nodeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "key varchar($MAX_STRING_LEN), " + + "value varchar($MAX_STRING_LEN), " + + "x INTEGER, " + + "y INTEGER);" + ) + } catch (ex: Exception) { + throw SQLException("Cannot create table in database") + } + } + + private fun executeQuery(query: String) = + connection.createStatement().also { stmt -> + try { + stmt.execute(query) + } catch (ex: Exception) { + throw SQLException("Cannot execute query: \"$query\"") + } finally { + stmt.close() + } + } + + private fun addTree(treeName: String, treeType: String) { + try { + addTreeStatement.setString(1, treeName) + addTreeStatement.setString(2, treeType) + + addTreeStatement.execute() + } catch(ex: Exception) { + throw SQLException("Cannot add tree: $treeName") + } + } + + private fun getTreeType(treeName: String) = executeQuery("SELECT tree FROM trees WHERE tree.name = $treeName").toString() + + override fun readTree(treeName: String): BinTree>> { + val nodes = "${treeName}Nodes" + val getAllNodesStatement by lazy { connection.prepareStatement("SELECT $nodes.key as key, $nodes.value as value, $nodes.x as x, $nodes.y as y FROM $nodes;") } + + val tree = typeToTree(getTreeType(treeName)) + + try { + val nodesSet = getAllNodesStatement.executeQuery() + while (nodesSet.next()) { + tree.insert( + nodesSet.getString("key"), + Pair(nodesSet.getString("value"), + Pair(nodesSet.getDouble("x"), nodesSet.getDouble("y")) + ) + ) + } + } catch (ex: Exception) { + throw IOException("Cannot get nodes from database") + } + + return tree + } + + override fun removeTree(treeName: String) { + executeQuery("DELETE FROM ${treeName}Nodes; DELETE FROM trees WHERE trees.name = $treeName") + } + + override fun getAllTree(): List> { + val list = mutableListOf>() + val treesSet = getAllTreesStatement.executeQuery() + while (treesSet.next()) { + list.add(Pair(treesSet.getString("name"), treesSet.getString("type"))) + } + + return list + } + + override fun close() { + addTreeStatement.close() + addNodeStatement.close() + getAllTreesStatement.close() + connection.close() + } + + override fun clean() { + executeQuery("DROP DATABASE;") + } +} diff --git a/app/src/main/kotlin/dataBase/neo4j functions.kt b/app/src/main/kotlin/dataBase/neo4j functions.kt deleted file mode 100644 index c34124f..0000000 --- a/app/src/main/kotlin/dataBase/neo4j functions.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dataBase - -import org.neo4j.driver.AuthTokens -import org.neo4j.driver.Driver -import org.neo4j.driver.GraphDatabase -import org.neo4j.driver.Session -import java.io.IOException - -fun startSession(uri: String, user: String, password: String): Session { - try { - val driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) - return driver.session() - } catch (e: IllegalArgumentException) { - throw IOException("can't start session, try to chainge uri, user name or password") - } -} - -fun closeSession(session: Session, driver: Driver) { - session.close() - driver.close() -} - -fun cleanDB(session: Session) { - session.run("MATCH (n) DETACH DELETE n") -} - -fun cleanAndClose(session: Session, driver: Driver) { - cleanDB(session) - closeSession(session, driver) -} \ No newline at end of file diff --git a/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt deleted file mode 100644 index 13448a8..0000000 --- a/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordAVLTree.kt +++ /dev/null @@ -1,34 +0,0 @@ -package storedTreeWithCoordinate - -import AVLTree -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import dataBase.DataBase -import java.io.File - -class StoredCoordAVLTree: AVLTree>> { - constructor() : super() - constructor(key: String, value: Pair>) : super(key, value) - constructor(vararg pairs: Pair>>) : super(*pairs) - - inner class Json(private val fileName: String) : DataBase { - val jsonFile = File(fileName) - val mapper = jacksonObjectMapper() - - override fun writeTree() { - removeTree() - jsonFile.createNewFile() - val nodeList = mutableListOf>>>() - breadthFirstSearch({ node -> if (node != null) nodeList.add(Pair(node.key, node.value))}) - jsonFile.appendText(mapper.writeValueAsString(nodeList)) - } - - override fun readTree() { - insert(*mapper.readValue(jsonFile)) - } - - override fun removeTree() { - jsonFile.delete() - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt deleted file mode 100644 index ba96541..0000000 --- a/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordBSTree.kt +++ /dev/null @@ -1,60 +0,0 @@ -package storedTreeWithCoordinate - -import BSTree -import dataBase.DataBase -import org.neo4j.driver.Session -import org.neo4j.driver.exceptions.value.Uncoercible -import java.io.IOException - -class StoredCoordBSTree : BSTree>> { - constructor() : super() - constructor(key: String, value: Pair>) : super(key, value) - constructor(vararg pairs: Pair>>) : super(*pairs) - - inner class Neo4j(private val session: Session, private val treeName: String) : DataBase { - override fun writeTree() { - removeTree() - session.run("CREATE (:$treeName)") - var prevKey: String? = null - breadthFirstSearch({ node -> saveNode(node, prevKey); prevKey = node?.key ?: prevKey }) - } - - private fun saveNode(node: BinNode>>?, prevKey: String?) { - if (node == null) return - session.executeWrite { tx -> - tx.run( - "OPTIONAL MATCH (prevNode:${if (prevKey == null) treeName else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + - "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", - mutableMapOf( - "key" to node.key, - "value" to node.value.first, - "x" to node.value.second.first, - "y" to node.value.second.second - ) as Map - ) - } - } - - override fun readTree() { - session.executeRead { tx -> - val result = tx.run( - "OPTIONAL MATCH (:$treeName)-[n:next*]->(node)" + "RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" - ) - result.stream().forEach { - try { - insert( - it["key"].asString(), - Pair(it["value"].asString(), Pair(it["x"].asDouble(), it["y"].asDouble())) - ) - } catch (e: Uncoercible) { - throw IOException("Corrupted data in the database.\n Possible solution: Clear the data.") - } - } - } - } - - override fun removeTree() { - session.run("OPTIONAL MATCH (tree:$treeName)-[:next*]->(node) DETACH DELETE node, tree") - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt b/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt deleted file mode 100644 index cb48dd3..0000000 --- a/app/src/main/kotlin/storedTreeWithCoordinate/StoredCoordRBTree.kt +++ /dev/null @@ -1,99 +0,0 @@ -package storedTreeWithCoordinate - -import RBTree -import dataBase.DataBase -import java.io.IOException -import java.sql.DriverManager -import java.sql.SQLException - -class StoredCoordRBTree: RBTree>> { - constructor() : super() - constructor(key: String, value: Pair>) : super(key, value) - constructor(vararg pairs: Pair>>) : super(*pairs) - - inner class SQLite(private val dbPath: String) : DataBase { - private val connection = DriverManager.getConnection("$DB_DRIVER:$dbPath") - ?: throw SQLException("Cannot connect to database") - private val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } - private val getAllNodesStatement by lazy { connection.prepareStatement("SELECT nodes.key as key, nodes.value as value, nodes.x as x, nodes.y as y FROM nodes;") } - - fun createDb() { - connection.createStatement() - .also { stmt -> - try { - stmt.execute("CREATE TABLE if not exists nodes (nodeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "key varchar(255), " + - "value varchar(255), " + - "x INTEGER, " + - "y INTEGER);" - ) - } catch (ex: Exception) { - throw IOException("Cannot create table in database") - } finally { - stmt.close() - } - } - } - - fun executeQuery(query: String) { - connection.createStatement().also { stmt -> - try { - stmt.execute(query) - } catch (ex: Exception) { - createDb() - } finally { - stmt.close() - } - } - } - - override fun writeTree() { - removeTree() - breadthFirstSearch({ node -> saveNode(node)}) - } - - private fun saveNode(node: BinTree.BinNode>>?) { - if (node != null) - try { - addNodeStatement.setString(1, node.key) - addNodeStatement.setString(2, node.value.first) - addNodeStatement.setDouble(3, node.value.second.first) - addNodeStatement.setDouble(4, node.value.second.second) - - addNodeStatement.execute() - } catch (ex: Exception) { - throw IOException("Cannot add user: ${node.key}") - } - } - - override fun readTree() { - try { - val resSet = getAllNodesStatement.executeQuery() - while (resSet.next()) { - insert( - resSet.getString("key"), - Pair(resSet.getString("value"), - Pair(resSet.getDouble("x"), resSet.getDouble("y")) - ) - ) - } - } catch (ex: Exception) { - throw IOException("Cannot get nodes from database") - } - } - - override fun removeTree() { - executeQuery("DELETE FROM nodes;") - } - - fun close() { - addNodeStatement.close() - getAllNodesStatement.close() - connection.close() - } - } - - companion object { - private const val DB_DRIVER = "jdbc:sqlite" - } -} \ No newline at end of file diff --git a/lib/src/main/kotlin/BinTree.kt b/lib/src/main/kotlin/BinTree.kt index 23f68c6..bcfe8de 100644 --- a/lib/src/main/kotlin/BinTree.kt +++ b/lib/src/main/kotlin/BinTree.kt @@ -161,7 +161,7 @@ abstract class BinTree, Value> : Tree { newNode?.let { it.parent = parent } } - protected fun breadthFirstSearch(function: (BinNode?) -> Unit, addNullNodes: Boolean = false) { + protected fun breadthFirstSearch(addNullNodes: Boolean = false, function: (BinNode?) -> Unit) { val queue: Queue?> = LinkedList(listOf(rootNode)) fun notNullInQueue(): Boolean { @@ -183,13 +183,19 @@ abstract class BinTree, Value> : Tree { } } + fun getKeyValueList(): List> { + val list = mutableListOf>() + breadthFirstSearch { node -> if (node != null) list.add(Pair(node.key, node.value)) } + return list + } + internal open inner class Debug { fun treeKeysInString(): String { var sizeOfLevel = 1 var elemInTheLevel = 0 var string = "" - fun function(node: BinNode?) { + breadthFirstSearch(true) { node -> string += node?.key ?: "-" string += " " elemInTheLevel += 1 @@ -199,8 +205,6 @@ abstract class BinTree, Value> : Tree { string += "\n" } } - - breadthFirstSearch(::function, true) return string } } From f77a53a871e8aa20e805a691faa5dc0197ec910b Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 24 Apr 2023 20:08:00 +0300 Subject: [PATCH 103/164] ci(refactor): add new os to run and add name for checkout --- .github/workflows/{actions.yml => gradle.yml} | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) rename .github/workflows/{actions.yml => gradle.yml} (77%) diff --git a/.github/workflows/actions.yml b/.github/workflows/gradle.yml similarity index 77% rename from .github/workflows/actions.yml rename to .github/workflows/gradle.yml index 2ad0e51..ebc8de4 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/gradle.yml @@ -7,10 +7,15 @@ on: jobs: build: + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + runs-on: ${{ matrix.os }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: actions checkout + uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v3 with: @@ -23,6 +28,7 @@ jobs: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle + - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle From c3412e5601f7a11821f82f64fcf0a67d09fc5378 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 24 Apr 2023 20:15:55 +0300 Subject: [PATCH 104/164] ci(fix): remove old run system --- .github/workflows/gradle.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ebc8de4..bb0ca29 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -10,9 +10,8 @@ jobs: strategy: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] - runs-on: ${{ matrix.os }} - runs-on: ubuntu-latest - + runs-on: ${{ matrix.os }} + steps: - name: actions checkout uses: actions/checkout@v3 From 9a11f2913e579d8321f86cd3b356ed748b01bf75 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 24 Apr 2023 21:42:32 +0300 Subject: [PATCH 105/164] feat: combine all the trees into the package "trees" and clean code --- app/src/main/kotlin/dataBase/DataBase.kt | 15 ++++++--------- app/src/main/kotlin/dataBase/Json.kt | 2 +- app/src/main/kotlin/dataBase/Neo4j.kt | 16 ++++++++-------- app/src/main/kotlin/dataBase/SQLite.kt | 2 +- lib/src/main/kotlin/{ => trees}/AVLTree.kt | 2 ++ lib/src/main/kotlin/{ => trees}/BSTree.kt | 4 +++- lib/src/main/kotlin/{ => trees}/BalanceTree.kt | 2 ++ lib/src/main/kotlin/{ => trees}/BinTree.kt | 2 ++ lib/src/main/kotlin/{ => trees}/RBTree.kt | 2 ++ lib/src/main/kotlin/{ => trees}/Tree.kt | 2 ++ lib/src/test/kotlin/{ => trees}/AVLTreeTest.kt | 2 ++ lib/src/test/kotlin/{ => trees}/BSTreeTest.kt | 2 ++ lib/src/test/kotlin/{ => trees}/RBTreeTest.kt | 2 ++ 13 files changed, 35 insertions(+), 20 deletions(-) rename lib/src/main/kotlin/{ => trees}/AVLTree.kt (99%) rename lib/src/main/kotlin/{ => trees}/BSTree.kt (85%) rename lib/src/main/kotlin/{ => trees}/BalanceTree.kt (98%) rename lib/src/main/kotlin/{ => trees}/BinTree.kt (99%) rename lib/src/main/kotlin/{ => trees}/RBTree.kt (99%) rename lib/src/main/kotlin/{ => trees}/Tree.kt (94%) rename lib/src/test/kotlin/{ => trees}/AVLTreeTest.kt (99%) rename lib/src/test/kotlin/{ => trees}/BSTreeTest.kt (99%) rename lib/src/test/kotlin/{ => trees}/RBTreeTest.kt (99%) diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index 3e219f5..c27ea5e 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -1,21 +1,18 @@ package dataBase -import AVLTree -import BSTree -import BinTree -import RBTree +import trees.* import java.sql.SQLException interface DataBase { fun isSupportTreeType(treeType: String): Boolean { - val supportTypes = arrayOf("BSTree", "RBTree", "AVLTree") + val supportTypes = arrayOf("trees.BSTree", "trees.RBTree", "trees.AVLTree") return (treeType in supportTypes) } fun typeToTree(type: String): BinTree>> = when (type) { - "BSTree" -> BSTree() - "RBTree" -> RBTree() - "AVLTree" -> AVLTree() + "trees.BSTree" -> BSTree() + "trees.RBTree" -> RBTree() + "trees.AVLTree" -> AVLTree() else -> throw SQLException("invalid type of tree") } fun saveTree(treeName: String, tree: BinTree>>, treeType: String) @@ -28,4 +25,4 @@ interface DataBase { clean() close() } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index f17cc9f..8640b68 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -1,6 +1,6 @@ package dataBase -import BinTree +import trees.BinTree import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import java.io.File diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index 626e06f..f6d3aeb 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -1,6 +1,6 @@ package dataBase -import BinTree +import trees.BinTree import org.neo4j.driver.AuthTokens import org.neo4j.driver.Driver import org.neo4j.driver.GraphDatabase @@ -25,7 +25,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") removeTree(treeName) - session.run("CREATE (:Tree {name: \$$treeName}, type: \$$treeType)") + session.run("CREATE (:trees.Tree {name: \$$treeName}, type: \$$treeType)") var prevKey: String? = null tree.getKeyValueList().forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } } @@ -33,7 +33,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { private fun saveNode(key: String, value: String, coordinate: Pair, prevKey: String?, treeName: String) { session.executeWrite { tx -> tx.run( - "OPTIONAL MATCH (prevNode:${if (prevKey == null) "Tree WHERE prevNode.name = $treeName" else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + + "OPTIONAL MATCH (prevNode:${if (prevKey == null) "trees.Tree WHERE prevNode.name = $treeName" else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", mutableMapOf( "key" to key, @@ -48,14 +48,14 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { override fun readTree(treeName: String): BinTree>> { var type = "" session.executeRead { tx -> - type = tx.run("OPTIONAL MATCH (tree:Tree WHERE tree.name = $treeName) RETURN tree.type").toString() + type = tx.run("OPTIONAL MATCH (tree:trees.Tree WHERE tree.name = $treeName) RETURN tree.type").toString() } - val tree = typeToTree(type.toString()) + val tree = typeToTree(type) session.executeRead { tx -> val result = tx.run( - "OPTIONAL MATCH (tree:Tree WHERE tree.name = $treeName)-[n:next*]->(node) RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" + "OPTIONAL MATCH (tree:trees.Tree WHERE tree.name = $treeName)-[n:next*]->(node) RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" ) result.stream().forEach { @@ -74,13 +74,13 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } override fun removeTree(treeName: String) { - session.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = $treeName)-[:next*]->(node) DETACH DELETE node, tree") + session.run("OPTIONAL MATCH (tree: trees.Tree WHERE tree.name = $treeName)-[:next*]->(node) DETACH DELETE node, tree") } override fun getAllTree(): List> { val list = mutableListOf>() session.executeRead { tx -> - val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type") + val result = tx.run("OPTIONAL MATCH (tree: trees.Tree) RETURN tree.name AS name, tree.type AS type") result.stream().forEach { list.add(Pair(it["name"].asString(), it["type"].asString())) } diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 62e2cec..caa92f8 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -1,6 +1,6 @@ package dataBase -import BinTree +import trees.BinTree import java.io.IOException import java.sql.DriverManager import java.sql.SQLException diff --git a/lib/src/main/kotlin/AVLTree.kt b/lib/src/main/kotlin/trees/AVLTree.kt similarity index 99% rename from lib/src/main/kotlin/AVLTree.kt rename to lib/src/main/kotlin/trees/AVLTree.kt index a03871a..77ef68e 100644 --- a/lib/src/main/kotlin/AVLTree.kt +++ b/lib/src/main/kotlin/trees/AVLTree.kt @@ -1,3 +1,5 @@ +package trees + open class AVLTree, Value> : BalanceTree { protected class AVLNode, Value>( key: Key, diff --git a/lib/src/main/kotlin/BSTree.kt b/lib/src/main/kotlin/trees/BSTree.kt similarity index 85% rename from lib/src/main/kotlin/BSTree.kt rename to lib/src/main/kotlin/trees/BSTree.kt index 84b19aa..6216dfe 100644 --- a/lib/src/main/kotlin/BSTree.kt +++ b/lib/src/main/kotlin/trees/BSTree.kt @@ -1,9 +1,11 @@ +package trees + open class BSTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) constructor(vararg pairs: Pair) : super(pairs) - open override fun insert(key: Key, value: Value) { + override fun insert(key: Key, value: Value) { insertService(BinNode(key, value)) } diff --git a/lib/src/main/kotlin/BalanceTree.kt b/lib/src/main/kotlin/trees/BalanceTree.kt similarity index 98% rename from lib/src/main/kotlin/BalanceTree.kt rename to lib/src/main/kotlin/trees/BalanceTree.kt index 28ef0a0..de09d82 100644 --- a/lib/src/main/kotlin/BalanceTree.kt +++ b/lib/src/main/kotlin/trees/BalanceTree.kt @@ -1,3 +1,5 @@ +package trees + abstract class BalanceTree, Value> : BinTree { constructor() : super() constructor(key: Key, value: Value) : super(key, value) diff --git a/lib/src/main/kotlin/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt similarity index 99% rename from lib/src/main/kotlin/BinTree.kt rename to lib/src/main/kotlin/trees/BinTree.kt index bcfe8de..24ebd4c 100644 --- a/lib/src/main/kotlin/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -1,3 +1,5 @@ +package trees + import java.util.LinkedList import java.util.Queue import kotlin.math.abs diff --git a/lib/src/main/kotlin/RBTree.kt b/lib/src/main/kotlin/trees/RBTree.kt similarity index 99% rename from lib/src/main/kotlin/RBTree.kt rename to lib/src/main/kotlin/trees/RBTree.kt index 3cd736c..6c2b573 100644 --- a/lib/src/main/kotlin/RBTree.kt +++ b/lib/src/main/kotlin/trees/RBTree.kt @@ -1,3 +1,5 @@ +package trees + open class RBTree, Value> : BalanceTree { companion object { const val RED = false diff --git a/lib/src/main/kotlin/Tree.kt b/lib/src/main/kotlin/trees/Tree.kt similarity index 94% rename from lib/src/main/kotlin/Tree.kt rename to lib/src/main/kotlin/trees/Tree.kt index 6d04116..60d3ae8 100644 --- a/lib/src/main/kotlin/Tree.kt +++ b/lib/src/main/kotlin/trees/Tree.kt @@ -1,3 +1,5 @@ +package trees + interface Tree { fun insert(key: Key, value: Value) fun insert(vararg array: Pair) diff --git a/lib/src/test/kotlin/AVLTreeTest.kt b/lib/src/test/kotlin/trees/AVLTreeTest.kt similarity index 99% rename from lib/src/test/kotlin/AVLTreeTest.kt rename to lib/src/test/kotlin/trees/AVLTreeTest.kt index e6945d1..44278e9 100644 --- a/lib/src/test/kotlin/AVLTreeTest.kt +++ b/lib/src/test/kotlin/trees/AVLTreeTest.kt @@ -1,3 +1,5 @@ +package trees + import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested diff --git a/lib/src/test/kotlin/BSTreeTest.kt b/lib/src/test/kotlin/trees/BSTreeTest.kt similarity index 99% rename from lib/src/test/kotlin/BSTreeTest.kt rename to lib/src/test/kotlin/trees/BSTreeTest.kt index ba1db74..c3ef1e9 100644 --- a/lib/src/test/kotlin/BSTreeTest.kt +++ b/lib/src/test/kotlin/trees/BSTreeTest.kt @@ -1,3 +1,5 @@ +package trees + import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested diff --git a/lib/src/test/kotlin/RBTreeTest.kt b/lib/src/test/kotlin/trees/RBTreeTest.kt similarity index 99% rename from lib/src/test/kotlin/RBTreeTest.kt rename to lib/src/test/kotlin/trees/RBTreeTest.kt index d4d2dbc..d2a8ec2 100644 --- a/lib/src/test/kotlin/RBTreeTest.kt +++ b/lib/src/test/kotlin/trees/RBTreeTest.kt @@ -1,3 +1,5 @@ +package trees + import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested From 8b4980f2835fe83fda1fb4ba298c36f634189722 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 26 Apr 2023 22:53:13 +0300 Subject: [PATCH 106/164] fix(db): fix all the database, add properties files, add clean fun to the BinTree --- app/src/main/kotlin/dataBase/DataBase.kt | 32 ++++++--- app/src/main/kotlin/dataBase/Json.kt | 51 ++++++++++---- app/src/main/kotlin/dataBase/Neo4j.kt | 52 ++++++++++---- app/src/main/kotlin/dataBase/SQLite.kt | 86 ++++++++++++++++-------- app/src/main/resources/Json.properties | 1 + app/src/main/resources/Neo4j.properties | 3 + app/src/main/resources/SQLite.properties | 2 + lib/src/main/kotlin/trees/BinTree.kt | 4 ++ 8 files changed, 165 insertions(+), 66 deletions(-) create mode 100644 app/src/main/resources/Json.properties create mode 100644 app/src/main/resources/Neo4j.properties create mode 100644 app/src/main/resources/SQLite.properties diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index c27ea5e..ffebcf7 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -1,21 +1,33 @@ package dataBase import trees.* -import java.sql.SQLException +import java.io.IOException interface DataBase { - fun isSupportTreeType(treeType: String): Boolean { - val supportTypes = arrayOf("trees.BSTree", "trees.RBTree", "trees.AVLTree") - return (treeType in supportTypes) - } + fun isSupportTreeType(tree: BinTree<*, *>) = when (tree) { + is BSTree, + is RBTree, + is AVLTree -> true + else -> false + } fun typeToTree(type: String): BinTree>> = when (type) { - "trees.BSTree" -> BSTree() - "trees.RBTree" -> RBTree() - "trees.AVLTree" -> AVLTree() - else -> throw SQLException("invalid type of tree") + BSTree::class.simpleName -> BSTree() + RBTree::class.simpleName -> RBTree() + AVLTree::class.simpleName -> AVLTree() + else -> throw IOException("invalid type of tree") + } + + fun validateName(name: String) { + for (i in name) + if (i !in 'a'..'z' && i !in 'A'..'Z' && i !in '0'..'9') + throw IllegalArgumentException("Unsupported tree name, please use only ascii letters or digits") + if (name[0] in '0'..'9') + throw IllegalArgumentException("Unsupported tree name, please don't use a digit as the first char") + if (name.isEmpty()) throw IllegalArgumentException("Incorrect tree name") } - fun saveTree(treeName: String, tree: BinTree>>, treeType: String) + + fun saveTree(treeName: String, tree: BinTree>>) fun readTree(treeName: String): BinTree>> fun removeTree(treeName: String) fun getAllTree(): List> diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 8640b68..25b0c9d 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -9,46 +9,63 @@ import java.nio.file.Files import java.nio.file.Paths import kotlin.io.path.extension -class Json(private val dirName: String) : DataBase { +class Json(private val saveDirPath: String) : DataBase { private val mapper = jacksonObjectMapper() init { - if (dirName.last() != '\\' && dirName.last() != '/') throw IllegalArgumentException("Please, don't use '/' or '\\' in the end of dir path") + if (saveDirPath.last() == '\\' || saveDirPath.last() == '/') throw IllegalArgumentException("Please, don't use '/' or '\\' in the end of dir path") } private fun getFile(treeName: String) = try { - File("${dirName}${treeName}/.json") + File("${saveDirPath}/${treeName}.json") } catch (ex: Exception) { - throw IOException("cannot get file with name: ${dirName}${treeName}/.json") + throw IOException("cannot get file with name: ${saveDirPath}/${treeName}.json") } override fun saveTree( - treeName: String, tree: BinTree>>, treeType: String + treeName: String, tree: BinTree>> ) { - if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") - if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") + validateName(treeName) + removeTree(treeName) + val jsonFile = getFile(treeName) + File(saveDirPath).mkdirs() jsonFile.createNewFile() - jsonFile.appendText(mapper.writeValueAsString(Pair(treeName, treeType))) - jsonFile.appendText(mapper.writeValueAsString(tree.getKeyValueList())) + + jsonFile.appendText( + mapper.writeValueAsString( + Pair( + Pair(treeName, tree::class.simpleName), + tree.getKeyValueList() + ) + ) + ) } override fun readTree(treeName: String): BinTree>> { + validateName(treeName) + val jsonFile = getFile(treeName) - val treeParams = mapper.readValue>(jsonFile) - val tree = typeToTree(treeParams.second) - tree.insert(*mapper.readValue(jsonFile)) + val readTree = + mapper.readValue, Array>>>>>( + jsonFile + ) + val tree = typeToTree(readTree.first.second) + tree.insert(*readTree.second) return tree } override fun removeTree(treeName: String) { + validateName(treeName) + getFile(treeName).delete() } private fun forAllJsonFile(function: (File) -> Unit) { - Files.walk(Paths.get(dirName)).use { path -> + Files.walk(Paths.get(saveDirPath)).use { path -> path.filter { Files.isRegularFile(it) && Files.isWritable(it) && (it.extension == "json") } .forEach { function(it.toFile()) } } @@ -56,7 +73,13 @@ class Json(private val dirName: String) : DataBase { override fun getAllTree(): List> { val list = mutableListOf>() - forAllJsonFile { list.add(mapper.readValue(it)) } + forAllJsonFile { + list.add( + mapper.readValue, Array>>>>>( + it + ).first + ) + } return list } diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index f6d3aeb..cc7020a 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -5,6 +5,7 @@ import org.neo4j.driver.AuthTokens import org.neo4j.driver.Driver import org.neo4j.driver.GraphDatabase import org.neo4j.driver.Session +import org.neo4j.driver.exceptions.ServiceUnavailableException import org.neo4j.driver.exceptions.value.Uncoercible import java.io.IOException @@ -21,19 +22,36 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } } - override fun saveTree(treeName: String, tree: BinTree>>, treeType: String) { - if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") - if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + private fun executeQuery(query: String) { + try { + session.run(query) + } + catch (ex: ServiceUnavailableException) { + throw IOException("Cannot connect to Neo4j database\nCheck that Neo4j is running and that all the data in the app/src/main/resources/Neo4j.properties file is correct") + } + } + + override fun saveTree(treeName: String, tree: BinTree>>) { + if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") + validateName(treeName) + removeTree(treeName) - session.run("CREATE (:trees.Tree {name: \$$treeName}, type: \$$treeType)") + executeQuery("CREATE (:Tree {name: '$treeName', type: '${tree::class.simpleName}'})") var prevKey: String? = null - tree.getKeyValueList().forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } + tree.getKeyValueList() + .forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } } - private fun saveNode(key: String, value: String, coordinate: Pair, prevKey: String?, treeName: String) { + private fun saveNode( + key: String, + value: String, + coordinate: Pair, + prevKey: String?, + treeName: String + ) { session.executeWrite { tx -> tx.run( - "OPTIONAL MATCH (prevNode:${if (prevKey == null) "trees.Tree WHERE prevNode.name = $treeName" else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + + "OPTIONAL MATCH (prevNode:${if (prevKey == null) "Tree WHERE prevNode.name = '$treeName'" else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", mutableMapOf( "key" to key, @@ -46,24 +64,28 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } override fun readTree(treeName: String): BinTree>> { + validateName(treeName) + var type = "" session.executeRead { tx -> - type = tx.run("OPTIONAL MATCH (tree:trees.Tree WHERE tree.name = $treeName) RETURN tree.type").toString() + type = tx.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName') RETURN tree.type AS type").single()["type"].asString() } val tree = typeToTree(type) session.executeRead { tx -> val result = tx.run( - "OPTIONAL MATCH (tree:trees.Tree WHERE tree.name = $treeName)-[n:next*]->(node) RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" + "OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName')-[n:next*]->(node) RETURN node.key AS key, node.value AS value, node.x AS x, node.y AS y ORDER BY n" ) result.stream().forEach { try { tree.insert( it["key"].asString(), - Pair(it["value"].asString(), - Pair(it["x"].asDouble(), it["y"].asDouble())) + Pair( + it["value"].asString(), + Pair(it["x"].asDouble(), it["y"].asDouble()) + ) ) } catch (e: Uncoercible) { throw IOException("Corrupted data in the database.\n Possible solution: Clear the data.") @@ -74,13 +96,15 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } override fun removeTree(treeName: String) { - session.run("OPTIONAL MATCH (tree: trees.Tree WHERE tree.name = $treeName)-[:next*]->(node) DETACH DELETE node, tree") + validateName(treeName) + + executeQuery("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName')-[:next*]->(node) DETACH DELETE node, tree") } override fun getAllTree(): List> { val list = mutableListOf>() session.executeRead { tx -> - val result = tx.run("OPTIONAL MATCH (tree: trees.Tree) RETURN tree.name AS name, tree.type AS type") + val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type") result.stream().forEach { list.add(Pair(it["name"].asString(), it["type"].asString())) } @@ -94,6 +118,6 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } override fun clean() { - session.run("MATCH (n) DETACH DELETE n") + executeQuery("MATCH (n) DETACH DELETE n") } } diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index caa92f8..2bcfa2c 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -3,42 +3,48 @@ package dataBase import trees.BinTree import java.io.IOException import java.sql.DriverManager +import java.sql.PreparedStatement import java.sql.SQLException -class SQLite (dbPath: String): DataBase { +class SQLite(dbPath: String, maxStringLen: UInt) : DataBase { companion object { - private const val MAX_STRING_LEN = 255 //need to take out to properties + private const val maxStringLen = 255 private const val DB_DRIVER = "jdbc:sqlite" } private val connection = DriverManager.getConnection("$DB_DRIVER:$dbPath") ?: throw SQLException("Cannot connect to database") private val addTreeStatement by lazy { connection.prepareStatement("INSERT INTO trees (name, type) VALUES (?, ?);") } - private val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO ?Nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type FROM trees;") } - override fun saveTree(treeName: String, tree: BinTree>>, treeType: String) { - if (isSupportTreeType(treeName)) throw IllegalArgumentException("Unsupported tree type") - if (treeName.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + + override fun saveTree(treeName: String, tree: BinTree>>) { + if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") + validateName(treeName) + + removeTree(treeName) createTreesTable() createTableForTree(treeName) - removeTree(treeName) - addTree(treeName, treeType) - tree.getKeyValueList().forEach{ saveNode(it.first, it.second.first, it.second.second, treeName) } + addTree(treeName, tree::class.simpleName ?: throw IllegalArgumentException("Cannot get tree type")) + + val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO ${treeName}Nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } + tree.getKeyValueList() + .forEach { saveNode(it.first, it.second.first, it.second.second, treeName, addNodeStatement) } + addNodeStatement.close() } private fun saveNode( key: String, value: String, coordinate: Pair, - treeName: String + treeName: String, + addNodeStatement: PreparedStatement ) { try { - addNodeStatement.setString(1, treeName) - addNodeStatement.setString(2, key) - addNodeStatement.setString(3, value) - addNodeStatement.setDouble(4, coordinate.first) - addNodeStatement.setDouble(5, coordinate.second) + addNodeStatement.setString(1, key) + addNodeStatement.setString(2, value) + addNodeStatement.setDouble(3, coordinate.first) + addNodeStatement.setDouble(4, coordinate.second) addNodeStatement.execute() } catch (ex: Exception) { @@ -48,9 +54,10 @@ class SQLite (dbPath: String): DataBase { private fun createTreesTable() { try { - executeQuery("CREATE TABLE if not exists trees (treeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "name varchar($MAX_STRING_LEN), " + - "type varchar($MAX_STRING_LEN);" + executeQuery( + "CREATE TABLE if not exists trees (treeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "name varchar($maxStringLen), " + + "type varchar($maxStringLen));" ) } catch (ex: Exception) { throw SQLException("Cannot create table in database") @@ -58,11 +65,13 @@ class SQLite (dbPath: String): DataBase { } private fun createTableForTree(treeName: String) { + validateName(treeName) + try { executeQuery( "CREATE TABLE if not exists ${treeName}Nodes (nodeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "key varchar($MAX_STRING_LEN), " + - "value varchar($MAX_STRING_LEN), " + + "key varchar($maxStringLen), " + + "value varchar($maxStringLen), " + "x INTEGER, " + "y INTEGER);" ) @@ -71,7 +80,7 @@ class SQLite (dbPath: String): DataBase { } } - private fun executeQuery(query: String) = + private fun executeQuery(query: String) { connection.createStatement().also { stmt -> try { stmt.execute(query) @@ -81,6 +90,7 @@ class SQLite (dbPath: String): DataBase { stmt.close() } } + } private fun addTree(treeName: String, treeType: String) { try { @@ -88,14 +98,27 @@ class SQLite (dbPath: String): DataBase { addTreeStatement.setString(2, treeType) addTreeStatement.execute() - } catch(ex: Exception) { + } catch (ex: Exception) { throw SQLException("Cannot add tree: $treeName") } } - private fun getTreeType(treeName: String) = executeQuery("SELECT tree FROM trees WHERE tree.name = $treeName").toString() + private fun getTreeType(treeName: String): String { + val getTreeTypeStatement by lazy { connection.prepareStatement("SELECT trees.type FROM trees WHERE name = ?") } + getTreeTypeStatement.setString(1, treeName) + try { + return getTreeTypeStatement.executeQuery().getString(1) + } catch (ex: Exception) { + throw SQLException("Cannot get tree type from database") + } finally { + getTreeTypeStatement.close() + } + + } override fun readTree(treeName: String): BinTree>> { + validateName(treeName) + val nodes = "${treeName}Nodes" val getAllNodesStatement by lazy { connection.prepareStatement("SELECT $nodes.key as key, $nodes.value as value, $nodes.x as x, $nodes.y as y FROM $nodes;") } @@ -106,20 +129,27 @@ class SQLite (dbPath: String): DataBase { while (nodesSet.next()) { tree.insert( nodesSet.getString("key"), - Pair(nodesSet.getString("value"), + Pair( + nodesSet.getString("value"), Pair(nodesSet.getDouble("x"), nodesSet.getDouble("y")) ) ) } } catch (ex: Exception) { - throw IOException("Cannot get nodes from database") + throw IOException("Cannot get nodes from database") + } finally { + getAllNodesStatement.close() } return tree } override fun removeTree(treeName: String) { - executeQuery("DELETE FROM ${treeName}Nodes; DELETE FROM trees WHERE trees.name = $treeName") + validateName(treeName) + + executeQuery("DROP TABLE IF EXISTS ${treeName}Nodes;") + + executeQuery("DELETE FROM trees WHERE name = '$treeName';") } override fun getAllTree(): List> { @@ -134,12 +164,12 @@ class SQLite (dbPath: String): DataBase { override fun close() { addTreeStatement.close() - addNodeStatement.close() getAllTreesStatement.close() connection.close() } override fun clean() { - executeQuery("DROP DATABASE;") + for (i in getAllTree()) + removeTree(i.first) } } diff --git a/app/src/main/resources/Json.properties b/app/src/main/resources/Json.properties new file mode 100644 index 0000000..d8bf1e6 --- /dev/null +++ b/app/src/main/resources/Json.properties @@ -0,0 +1 @@ +save_dir = JsonSave diff --git a/app/src/main/resources/Neo4j.properties b/app/src/main/resources/Neo4j.properties new file mode 100644 index 0000000..ff78ecf --- /dev/null +++ b/app/src/main/resources/Neo4j.properties @@ -0,0 +1,3 @@ +uri = bolt://localhost:7687 +user = neo4j +password = 12345678 diff --git a/app/src/main/resources/SQLite.properties b/app/src/main/resources/SQLite.properties new file mode 100644 index 0000000..bdec215 --- /dev/null +++ b/app/src/main/resources/SQLite.properties @@ -0,0 +1,2 @@ +max_string_len = 255 +sqlite_path = SQLDB/sqliteTreeStorage.db diff --git a/lib/src/main/kotlin/trees/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt index 24ebd4c..bc92a73 100644 --- a/lib/src/main/kotlin/trees/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -163,6 +163,10 @@ abstract class BinTree, Value> : Tree { newNode?.let { it.parent = parent } } + fun clean() { + rootNode = null + } + protected fun breadthFirstSearch(addNullNodes: Boolean = false, function: (BinNode?) -> Unit) { val queue: Queue?> = LinkedList(listOf(rootNode)) From 0d3bbcc8ee2a6dec255e2fb2a1547fbdc6d588b4 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 28 Apr 2023 01:05:39 +0300 Subject: [PATCH 107/164] feat: new dependencies (Compose, MaterialDesign) --- app/build.gradle.kts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8b1297a..9e1cd01 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,10 +1,18 @@ plugins { id("trees.kotlin-application-conventions") + + id("org.jetbrains.compose") version "1.4.0" } group = "org.example" version = "1.0-SNAPSHOT" +repositories { + google() + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") +} + val sqliteJdbcVersion: String by project dependencies { @@ -12,11 +20,10 @@ dependencies { implementation("org.neo4j.driver:neo4j-java-driver:5.7.0") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2") implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion) + + implementation(compose.desktop.currentOs) + implementation ("org.jetbrains.compose.material3:material3-desktop:1.2.1") } -// -//kotlin { -// jvmToolchain(8) -//} application { mainClass.set("MainKt") From a31542c3f1cb0cd545ee49c96aea2e53b246abf1 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 28 Apr 2023 01:06:45 +0300 Subject: [PATCH 108/164] feat: resources for MaterialDesign --- app/src/main/kotlin/app/Color.kt | 67 +++++++++++++++++++++++++ app/src/main/kotlin/app/Theme.kt | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 app/src/main/kotlin/app/Color.kt create mode 100644 app/src/main/kotlin/app/Theme.kt diff --git a/app/src/main/kotlin/app/Color.kt b/app/src/main/kotlin/app/Color.kt new file mode 100644 index 0000000..ce0278d --- /dev/null +++ b/app/src/main/kotlin/app/Color.kt @@ -0,0 +1,67 @@ +package UIT +import androidx.compose.ui.graphics.Color + +val md_theme_light_primary = Color(0xFF00687C) +val md_theme_light_onPrimary = Color(0xFFFFFFFF) +val md_theme_light_primaryContainer = Color(0xFFB0ECFF) +val md_theme_light_onPrimaryContainer = Color(0xFF001F27) +val md_theme_light_secondary = Color(0xFF4B6269) +val md_theme_light_onSecondary = Color(0xFFFFFFFF) +val md_theme_light_secondaryContainer = Color(0xFFCEE7EF) +val md_theme_light_onSecondaryContainer = Color(0xFF061F25) +val md_theme_light_tertiary = Color(0xFF585C7E) +val md_theme_light_onTertiary = Color(0xFFFFFFFF) +val md_theme_light_tertiaryContainer = Color(0xFFDEE0FF) +val md_theme_light_onTertiaryContainer = Color(0xFF141937) +val md_theme_light_error = Color(0xFFBA1A1A) +val md_theme_light_errorContainer = Color(0xFFFFDAD6) +val md_theme_light_onError = Color(0xFFFFFFFF) +val md_theme_light_onErrorContainer = Color(0xFF410002) +val md_theme_light_background = Color(0xFFFBFCFE) +val md_theme_light_onBackground = Color(0xFF191C1D) +val md_theme_light_surface = Color(0xFFFBFCFE) +val md_theme_light_onSurface = Color(0xFF191C1D) +val md_theme_light_surfaceVariant = Color(0xFFDBE4E7) +val md_theme_light_onSurfaceVariant = Color(0xFF40484B) +val md_theme_light_outline = Color(0xFF70787C) +val md_theme_light_inverseOnSurface = Color(0xFFEFF1F2) +val md_theme_light_inverseSurface = Color(0xFF2E3132) +val md_theme_light_inversePrimary = Color(0xFF57D6F6) +val md_theme_light_shadow = Color(0xFF000000) +val md_theme_light_surfaceTint = Color(0xFF00687C) +val md_theme_light_outlineVariant = Color(0xFFBFC8CB) +val md_theme_light_scrim = Color(0xFF000000) + +val md_theme_dark_primary = Color(0xFF57D6F6) +val md_theme_dark_onPrimary = Color(0xFF003641) +val md_theme_dark_primaryContainer = Color(0xFF004E5E) +val md_theme_dark_onPrimaryContainer = Color(0xFFB0ECFF) +val md_theme_dark_secondary = Color(0xFFB2CBD3) +val md_theme_dark_onSecondary = Color(0xFF1D343A) +val md_theme_dark_secondaryContainer = Color(0xFF344A51) +val md_theme_dark_onSecondaryContainer = Color(0xFFCEE7EF) +val md_theme_dark_tertiary = Color(0xFFC0C4EB) +val md_theme_dark_onTertiary = Color(0xFF292E4D) +val md_theme_dark_tertiaryContainer = Color(0xFF404565) +val md_theme_dark_onTertiaryContainer = Color(0xFFDEE0FF) +val md_theme_dark_error = Color(0xFFFFB4AB) +val md_theme_dark_errorContainer = Color(0xFF93000A) +val md_theme_dark_onError = Color(0xFF690005) +val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +val md_theme_dark_background = Color(0xFF191C1D) +val md_theme_dark_onBackground = Color(0xFFE1E3E4) +val md_theme_dark_surface = Color(0xFF191C1D) +val md_theme_dark_onSurface = Color(0xFFE1E3E4) +val md_theme_dark_surfaceVariant = Color(0xFF40484B) +val md_theme_dark_onSurfaceVariant = Color(0xFFBFC8CB) +val md_theme_dark_outline = Color(0xFF899295) +val md_theme_dark_inverseOnSurface = Color(0xFF191C1D) +val md_theme_dark_inverseSurface = Color(0xFFE1E3E4) +val md_theme_dark_inversePrimary = Color(0xFF00687C) +val md_theme_dark_shadow = Color(0xFF000000) +val md_theme_dark_surfaceTint = Color(0xFF57D6F6) +val md_theme_dark_outlineVariant = Color(0xFF40484B) +val md_theme_dark_scrim = Color(0xFF000000) + + +val seed = Color(0xFF004452) diff --git a/app/src/main/kotlin/app/Theme.kt b/app/src/main/kotlin/app/Theme.kt new file mode 100644 index 0000000..d414ac5 --- /dev/null +++ b/app/src/main/kotlin/app/Theme.kt @@ -0,0 +1,86 @@ +package UIT + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable + + +private val LightColors = lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, +) + + +private val DarkColors = darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, +) + +@Composable +fun AppTheme( + useDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable() () -> Unit +) { + val colors = if (!useDarkTheme) { + LightColors + } else { + DarkColors + } + + MaterialTheme( + colorScheme = colors, + content = content + ) +} \ No newline at end of file From b3b067c2c4e203a631235f15147e4cf55bd5c996 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 28 Apr 2023 01:07:59 +0300 Subject: [PATCH 109/164] feat: Window layout --- app/src/main/kotlin/app/app.kt | 145 +++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 app/src/main/kotlin/app/app.kt diff --git a/app/src/main/kotlin/app/app.kt b/app/src/main/kotlin/app/app.kt new file mode 100644 index 0000000..d84d41e --- /dev/null +++ b/app/src/main/kotlin/app/app.kt @@ -0,0 +1,145 @@ +package UIT + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Compose for Desktop", + state = rememberWindowState(width = 800.dp, height = 600.dp) + ) { + MaterialTheme { + val modifierColumn = Modifier.fillMaxSize().background(Color.White).padding(6.dp) + Row(modifierColumn) { + Column( + modifier = Modifier.padding(start = 32.dp, top = 16.dp) + ) { + Insert() + Remove() + Find() + } + Box( + modifier = Modifier + .fillMaxSize(), + ) { + + } + } + } + } + } + +@Preview +@Composable +fun Insert(/*keyValue: (keyString: String, value: String) -> Unit*/) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Insert:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) + + Row { + var textKey by remember { mutableStateOf("") } + var textValue by remember { mutableStateOf("") } + + + OutlinedTextField( value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key")}, + singleLine = true, + modifier = Modifier.weight(0.30f), + ) + + Spacer(modifier = Modifier.width(16.dp)) + + OutlinedTextField( value = textValue, + onValueChange = { textValue = it }, + label = { Text(text = "value")}, + singleLine = true, + modifier = Modifier.weight(0.30f), + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Button(onClick = { /*keyValue(textKey, textValue)*/ + textKey = "" + textValue = "" }, + shape = MaterialTheme.shapes.small, + modifier = Modifier.weight(0.30f), ){ + Text("go!") + } + } + } +} + +@Composable +fun Remove() { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Remove:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) + + Row { + var textKey by remember { mutableStateOf("") } + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + ) + + Spacer(modifier = Modifier.width(16.dp)) + + + Button( + onClick = {textKey = "" }, + shape = MaterialTheme.shapes.small, + modifier = Modifier.weight(0.30f) + ) { + Text("go!") + } + } + } +} + +@Composable +fun Find() { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Find:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) + + Row { + var textKey by remember { mutableStateOf("") } + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { textKey = "" }, + shape = MaterialTheme.shapes.small, + modifier = Modifier.weight(0.30f), + ) { + Text("go!") + } + } + } +} From a859ec634cc7c8d56ebaa859b115e7097cd0bfb7 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 28 Apr 2023 01:08:52 +0300 Subject: [PATCH 110/164] feat: fun draw Tree --- app/src/main/kotlin/app/ViewTree.kt | 112 ++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 app/src/main/kotlin/app/ViewTree.kt diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ViewTree.kt new file mode 100644 index 0000000..4118d9b --- /dev/null +++ b/app/src/main/kotlin/app/ViewTree.kt @@ -0,0 +1,112 @@ +package app + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.boundsInParent +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import kotlin.math.roundToInt + +open class ViewTree, Value> { + + protected class ViewNode, String>( + var key: Int, + var value: String, + var x: MutableState, + var y: MutableState, + var left: ViewNode? = null, + var right: ViewNode? = null, + var color: Boolean, + ) + + @Composable + protected fun drawTree(node: ViewNode){ + + node.left?.let { + drawLine(node.x, node.y, it.x, it.y) + drawTree(it) + + } + drawNode(node.key.toString(), node.x, node.y) + node.right?.let { + drawLine(node.x, node.y, it.x, it.y) + drawTree(it) + } + } + + @Composable + fun drawNode(key: String, + centerX: MutableState, + centerY: MutableState) { + + val offsetX = remember { mutableStateOf(0f) } + val offsetY = remember { mutableStateOf(0f) } + + Box( + modifier = Modifier + .offset { + IntOffset( + x = offsetX.value.roundToInt(), + y = offsetY.value.roundToInt() + ) + } + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + offsetX.value += dragAmount.x + offsetY.value += dragAmount.y + centerX.value = offsetX.value + centerY.value = offsetY.value + } + } + .size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primary) + .onGloballyPositioned { layoutCoordinates -> + val rect = layoutCoordinates.boundsInParent() + centerX.value = rect.center.x + centerY.value = rect.center.y + }, + contentAlignment = Alignment.Center + ) { + Text( + text = key, + fontSize = 30.sp, + color = Color.White, + fontWeight = FontWeight.Bold + ) + } + } + + @Composable + fun drawLine(x1: MutableState, y1: MutableState, x2: MutableState, y2: MutableState) { + Canvas(modifier = Modifier.fillMaxSize().zIndex(-1f)) { + drawLine(color = Color.DarkGray, + start = Offset(x1.value, y1.value), + end = Offset(x2.value, y2.value), + strokeWidth = 5f ) + } + } +} + From 2f53898b332faccd5dc2447bfa06f19589657376 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Fri, 28 Apr 2023 01:10:50 +0300 Subject: [PATCH 111/164] feat: Controller layout --- app/src/main/kotlin/app/Controller.kt | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/src/main/kotlin/app/Controller.kt diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt new file mode 100644 index 0000000..d0f10f7 --- /dev/null +++ b/app/src/main/kotlin/app/Controller.kt @@ -0,0 +1,33 @@ +package app + +import androidx.compose.runtime.MutableState + +enum class treeType { + RBTree, + AVLTree, + BSTree +} + +open class DrawNode( + var key: String, + var value: String, + var x: MutableState, + var y: MutableState, + var prevX: MutableState, + var prevY: MutableState, +) +fun getAllNodes( name: String, treeType: treeType ): Array { + TODO() +} + +fun drawInsert(key: String, value: String) { + TODO() +} + +fun drawRemove(key: String) { + TODO() +} + +fun drawFind(key: String) { + TODO() +} \ No newline at end of file From cd7ebff4a41c09680e37e49e4b5047df26bec0a4 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 29 Apr 2023 13:00:53 +0300 Subject: [PATCH 112/164] refactor(db): Change all double to float --- app/src/main/kotlin/dataBase/DataBase.kt | 8 ++++---- app/src/main/kotlin/dataBase/Json.kt | 8 ++++---- app/src/main/kotlin/dataBase/Neo4j.kt | 8 ++++---- app/src/main/kotlin/dataBase/SQLite.kt | 14 +++++++------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index ffebcf7..5a6a43f 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -9,9 +9,9 @@ interface DataBase { is RBTree, is AVLTree -> true else -> false - } + } - fun typeToTree(type: String): BinTree>> = when (type) { + fun typeToTree(type: String): BinTree>> = when (type) { BSTree::class.simpleName -> BSTree() RBTree::class.simpleName -> RBTree() AVLTree::class.simpleName -> AVLTree() @@ -27,8 +27,8 @@ interface DataBase { if (name.isEmpty()) throw IllegalArgumentException("Incorrect tree name") } - fun saveTree(treeName: String, tree: BinTree>>) - fun readTree(treeName: String): BinTree>> + fun saveTree(treeName: String, tree: BinTree>>) + fun readTree(treeName: String): BinTree>> fun removeTree(treeName: String) fun getAllTree(): List> fun clean() diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 25b0c9d..9f05e04 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -23,7 +23,7 @@ class Json(private val saveDirPath: String) : DataBase { } override fun saveTree( - treeName: String, tree: BinTree>> + treeName: String, tree: BinTree>> ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) @@ -44,13 +44,13 @@ class Json(private val saveDirPath: String) : DataBase { ) } - override fun readTree(treeName: String): BinTree>> { + override fun readTree(treeName: String): BinTree>> { validateName(treeName) val jsonFile = getFile(treeName) val readTree = - mapper.readValue, Array>>>>>( + mapper.readValue, Array>>>>>( jsonFile ) val tree = typeToTree(readTree.first.second) @@ -75,7 +75,7 @@ class Json(private val saveDirPath: String) : DataBase { val list = mutableListOf>() forAllJsonFile { list.add( - mapper.readValue, Array>>>>>( + mapper.readValue, Array>>>>>( it ).first ) diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index cc7020a..0b1cc02 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -31,7 +31,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } } - override fun saveTree(treeName: String, tree: BinTree>>) { + override fun saveTree(treeName: String, tree: BinTree>>) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) @@ -45,7 +45,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { private fun saveNode( key: String, value: String, - coordinate: Pair, + coordinate: Pair, prevKey: String?, treeName: String ) { @@ -63,7 +63,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } } - override fun readTree(treeName: String): BinTree>> { + override fun readTree(treeName: String): BinTree>> { validateName(treeName) var type = "" @@ -84,7 +84,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { it["key"].asString(), Pair( it["value"].asString(), - Pair(it["x"].asDouble(), it["y"].asDouble()) + Pair(it["x"].asFloat(), it["y"].asFloat()) ) ) } catch (e: Uncoercible) { diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 2bcfa2c..76abd4f 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -18,7 +18,7 @@ class SQLite(dbPath: String, maxStringLen: UInt) : DataBase { private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type FROM trees;") } - override fun saveTree(treeName: String, tree: BinTree>>) { + override fun saveTree(treeName: String, tree: BinTree>>) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) @@ -32,19 +32,19 @@ class SQLite(dbPath: String, maxStringLen: UInt) : DataBase { .forEach { saveNode(it.first, it.second.first, it.second.second, treeName, addNodeStatement) } addNodeStatement.close() } - + private fun saveNode( key: String, value: String, - coordinate: Pair, + coordinate: Pair, treeName: String, addNodeStatement: PreparedStatement ) { try { addNodeStatement.setString(1, key) addNodeStatement.setString(2, value) - addNodeStatement.setDouble(3, coordinate.first) - addNodeStatement.setDouble(4, coordinate.second) + addNodeStatement.setFloat(3, coordinate.first) + addNodeStatement.setFloat(4, coordinate.second) addNodeStatement.execute() } catch (ex: Exception) { @@ -116,7 +116,7 @@ class SQLite(dbPath: String, maxStringLen: UInt) : DataBase { } - override fun readTree(treeName: String): BinTree>> { + override fun readTree(treeName: String): BinTree>> { validateName(treeName) val nodes = "${treeName}Nodes" @@ -131,7 +131,7 @@ class SQLite(dbPath: String, maxStringLen: UInt) : DataBase { nodesSet.getString("key"), Pair( nodesSet.getString("value"), - Pair(nodesSet.getDouble("x"), nodesSet.getDouble("y")) + Pair(nodesSet.getFloat("x"), nodesSet.getFloat("y")) ) ) } From 4a70b1b70613feab32fba51664d14bc622349b77 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 29 Apr 2023 13:03:16 +0300 Subject: [PATCH 113/164] refactor(db): now the maximum length of the string is taken as a parameter --- app/src/main/kotlin/dataBase/SQLite.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 76abd4f..23c673c 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -6,9 +6,8 @@ import java.sql.DriverManager import java.sql.PreparedStatement import java.sql.SQLException -class SQLite(dbPath: String, maxStringLen: UInt) : DataBase { +class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { companion object { - private const val maxStringLen = 255 private const val DB_DRIVER = "jdbc:sqlite" } From 73a47213bf11071f86930d0469b096bfcae7d35c Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 29 Apr 2023 13:04:01 +0300 Subject: [PATCH 114/164] refactor(gradle): clean gradle file --- app/build.gradle.kts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9e1cd01..04c088c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,18 +1,11 @@ plugins { id("trees.kotlin-application-conventions") - id("org.jetbrains.compose") version "1.4.0" } group = "org.example" version = "1.0-SNAPSHOT" -repositories { - google() - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") -} - val sqliteJdbcVersion: String by project dependencies { @@ -22,9 +15,14 @@ dependencies { implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion) implementation(compose.desktop.currentOs) - implementation ("org.jetbrains.compose.material3:material3-desktop:1.2.1") + implementation("org.jetbrains.compose.material3:material3-desktop:1.2.1") } application { mainClass.set("MainKt") -} \ No newline at end of file +} + +repositories { + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() +} From 630a5193a0a5190789dbd23f53f3acb83279d8ad Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sat, 29 Apr 2023 17:22:22 +0300 Subject: [PATCH 115/164] feat: make functions in controller and BinTree controller: add load tree add getting list of all the tree in database add insert tree add get all draw nodes from tree and other service functions tree: add getParentData add getNodesDataWithParentValue --- app/src/main/kotlin/app/Controller.kt | 113 ++++++++++++++++++++------ lib/src/main/kotlin/trees/BinTree.kt | 14 ++++ 2 files changed, 102 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index d0f10f7..133441f 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -1,33 +1,96 @@ package app import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import dataBase.* +import trees.* -enum class treeType { - RBTree, - AVLTree, - BSTree -} +class Controller { + init { + System.getProperties().load(ClassLoader.getSystemResourceAsStream("Json.properties")) + System.getProperties().load(ClassLoader.getSystemResourceAsStream("Neo4j.properties")) + System.getProperties().load(ClassLoader.getSystemResourceAsStream("SQLite.properties")) + } -open class DrawNode( - var key: String, - var value: String, - var x: MutableState, - var y: MutableState, - var prevX: MutableState, - var prevY: MutableState, -) -fun getAllNodes( name: String, treeType: treeType ): Array { - TODO() -} -fun drawInsert(key: String, value: String) { - TODO() -} + enum class TreeType { + RBTree, + AVLTree, + BSTree + } -fun drawRemove(key: String) { - TODO() -} + enum class DatabaseType { + Json, + Neo4j, + SQLite + } + + fun getTree(treeType: TreeType) = when (treeType) { + TreeType.BSTree -> BSTree>>() + TreeType.AVLTree -> AVLTree>>() + TreeType.RBTree -> RBTree>>() + } + + fun getDatabase(databaseType: DatabaseType) = when (databaseType) { + DatabaseType.Json -> Json(System.getProperty("save_dir")) + DatabaseType.Neo4j -> Neo4j( + System.getProperty("uri"), + System.getProperty("user"), + System.getProperty("password") + ) + DatabaseType.SQLite -> SQLite(System.getProperty("sqlite_path"), System.getProperty("max_string_len").toUInt()) + } + + open class DrawNode( + var key: String, + var value: String, + var coordinates: MutableState>, + var prevCoordinates: MutableState?> + ) + + fun getAllTrees(databaseType: DatabaseType) = getDatabase(databaseType).getAllTree() + + inner class DrawTree { + var tree: BinTree>> + var treeName: String -fun drawFind(key: String) { - TODO() -} \ No newline at end of file + constructor(treeName: String, databaseType: DatabaseType) { + this.treeName = treeName + tree = getDatabase(databaseType).readTree(treeName) + } + + constructor(treeName: String, treeType: TreeType) { + this.treeName = treeName + tree = getTree(treeType) + } + + fun getAllDrawNodes(name: String, treeType: TreeType) = + tree.getNodesDataWithParentValue().map { data -> + DrawNode( + data.first, + data.second.first, + mutableStateOf(data.second.second), + mutableStateOf(data.third?.second) + ) + } + + fun drawInsert(key: String, value: String) { + val parentData = tree.getParentData(key) + var coordinate = Pair(0F, 0F) + + if (parentData != null) { + coordinate = Pair(parentData.second.second.first + if (key < parentData.first) -4F else 4F, parentData.second.second.second + 4F) + } + tree.insert(key, Pair(value, coordinate)) + } + } + + + fun drawRemove(key: String) { + TODO() + } + + fun drawFind(key: String) { + TODO() + } +} diff --git a/lib/src/main/kotlin/trees/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt index bc92a73..e2cc75f 100644 --- a/lib/src/main/kotlin/trees/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -195,6 +195,20 @@ abstract class BinTree, Value> : Tree { return list } + fun getParentData(key: Key): Pair? { + val parent = getParent(key) + return if (parent != null) + Pair(parent.key, parent.value) + else null + + } + + fun getNodesDataWithParentValue(): List> { + val list = mutableListOf>() + breadthFirstSearch { node -> if (node != null) list.add(Triple(node.key, node.value, node.parent?.value)) } + return list + } + internal open inner class Debug { fun treeKeysInString(): String { var sizeOfLevel = 1 From 260349ff638816e296bdb9f5a7ef438eacf19eee Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Sun, 30 Apr 2023 23:58:44 +0300 Subject: [PATCH 116/164] feat: rework the coordinate calculation With each insertion, deletion the coordinates of all nodes in the tree are recalculated Make some function in controller --- app/src/main/kotlin/app/Controller.kt | 49 ++++++++++++++++++++------- lib/src/main/kotlin/trees/BinTree.kt | 29 +++++++++++++++- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 133441f..ec9dbae 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import dataBase.* import trees.* +import kotlin.math.pow class Controller { init { @@ -38,6 +39,7 @@ class Controller { System.getProperty("user"), System.getProperty("password") ) + DatabaseType.SQLite -> SQLite(System.getProperty("sqlite_path"), System.getProperty("max_string_len").toUInt()) } @@ -64,7 +66,7 @@ class Controller { tree = getTree(treeType) } - fun getAllDrawNodes(name: String, treeType: TreeType) = + fun getAllDrawNodes() = tree.getNodesDataWithParentValue().map { data -> DrawNode( data.first, @@ -74,23 +76,44 @@ class Controller { ) } + fun rewriteAllCoordinates() { + val startCoord = Pair(0F, 0F) //coordinates of the root node + + val xMinInterval = 4F //interval between nodes + val yInterval = 4F + + fun offsetOnLevel(level: Int, height: Int) = + ((height - 2) * xMinInterval * (0.5.pow(level) - 1) * (-2)).toFloat() //the sum of the terms of the geometric progression + + var lastLevel = -1 + var curX = startCoord.first + var curY = startCoord.second + yInterval + var levelInterval = 0F + tree.rewriteAllValue(true) { value, level, height -> + if (level != lastLevel) { + curY -= yInterval + curX = -offsetOnLevel(level, height) + levelInterval = xMinInterval * 2F.pow(height - level - 1) + } else curX += levelInterval + lastLevel = level + if (value != null) + Pair(value.first, Pair(curX, curY)) + else null + } + } + fun drawInsert(key: String, value: String) { - val parentData = tree.getParentData(key) - var coordinate = Pair(0F, 0F) + tree.insert(key, Pair(value, Pair(0F, 0F))) - if (parentData != null) { - coordinate = Pair(parentData.second.second.first + if (key < parentData.first) -4F else 4F, parentData.second.second.second + 4F) - } - tree.insert(key, Pair(value, coordinate)) + rewriteAllCoordinates() } - } + fun drawRemove(key: String) { + tree.remove(key) - fun drawRemove(key: String) { - TODO() - } + rewriteAllCoordinates() + } - fun drawFind(key: String) { - TODO() + fun drawFind(key: String) = tree.get(key)?.first } } diff --git a/lib/src/main/kotlin/trees/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt index e2cc75f..7f5b5ef 100644 --- a/lib/src/main/kotlin/trees/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -200,7 +200,6 @@ abstract class BinTree, Value> : Tree { return if (parent != null) Pair(parent.key, parent.value) else null - } fun getNodesDataWithParentValue(): List> { @@ -209,6 +208,34 @@ abstract class BinTree, Value> : Tree { return list } + fun rewriteAllValue(addNullNodes: Boolean = false, function: (Value?, Int, Int) -> Value?) { + val listOfAllNodes = mutableListOf?>>() + var listOfLevel = mutableListOf?>() + var sizeOfLevel = 1 + var elemInTheLevel = 0 + breadthFirstSearch(addNullNodes) { node -> + listOfLevel.add(node) + elemInTheLevel += 1 + + if (elemInTheLevel == sizeOfLevel) { + listOfAllNodes.add(listOfLevel) + sizeOfLevel *= 2 + elemInTheLevel = 0 + listOfLevel = mutableListOf() + } + } + + var curLevel = 0 + val height = listOfAllNodes.size + listOfAllNodes.forEach { + it.forEach { node -> + val value: Value? = function(node?.value, curLevel, height) + if (value != null) node?.value = value + } + curLevel++ + } + } + internal open inner class Debug { fun treeKeysInString(): String { var sizeOfLevel = 1 From bf1d3ff321359885c3db2865f6d7f0076997d5bc Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 1 May 2023 00:45:50 +0300 Subject: [PATCH 117/164] feat: creat new windows --- app/src/main/kotlin/app/CreatNewWindow.kt | 125 ++++++++++++++++++++++ app/src/main/kotlin/app/MainWindow.kt | 77 +++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 app/src/main/kotlin/app/CreatNewWindow.kt create mode 100644 app/src/main/kotlin/app/MainWindow.kt diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt new file mode 100644 index 0000000..b251ce8 --- /dev/null +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -0,0 +1,125 @@ +package UIT + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Compose for Desktop", + state = rememberWindowState(width = 800.dp, height = 600.dp) + ) { + MaterialTheme { + Column( + modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + var name by remember { mutableStateOf("") } + + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text(text = "name") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, + ) + } + Spacer(modifier = Modifier.height(15.dp)) + + Text(text = "Add your first key and value!", style = MaterialTheme.typography.titleMedium) + + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + var textValue by remember { mutableStateOf("") } + + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, + ) + + Spacer(modifier = Modifier.width(16.dp)) + + OutlinedTextField( + value = textValue, + onValueChange = { textValue = it }, + label = { Text(text = "value") }, + singleLine = true, + modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Json", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "SQLite", + ) } + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Neo4j", + ) + } + } + + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/MainWindow.kt b/app/src/main/kotlin/app/MainWindow.kt new file mode 100644 index 0000000..88844b2 --- /dev/null +++ b/app/src/main/kotlin/app/MainWindow.kt @@ -0,0 +1,77 @@ +package UIT + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + var isOpen by remember { mutableStateOf(true) } + if (isOpen) { + Window( + onCloseRequest = ::exitApplication, + title = "Compose for Desktop", + state = rememberWindowState(width = 800.dp, height = 600.dp) + ) { + MaterialTheme { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "New", + style = MaterialTheme.typography.headlineSmall + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Open", + style = MaterialTheme.typography.headlineSmall + ) + } + Spacer(modifier = Modifier.height(15.dp)) + + Button( + onClick = { isOpen = false}, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Exit", + style = MaterialTheme.typography.headlineSmall + ) + } + } + + } + } + } +} \ No newline at end of file From 3b1faf17069e635a5d2d024394379410df1d283e Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 1 May 2023 00:47:56 +0300 Subject: [PATCH 118/164] fix: treeWindow: add shape and color for components --- .../main/kotlin/app/{app.kt => TreeWindow.kt} | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) rename app/src/main/kotlin/app/{app.kt => TreeWindow.kt} (73%) diff --git a/app/src/main/kotlin/app/app.kt b/app/src/main/kotlin/app/TreeWindow.kt similarity index 73% rename from app/src/main/kotlin/app/app.kt rename to app/src/main/kotlin/app/TreeWindow.kt index d84d41e..71343c2 100644 --- a/app/src/main/kotlin/app/app.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -1,13 +1,10 @@ package UIT -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material.Button -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @@ -42,14 +39,13 @@ fun main() = application { } } -@Preview @Composable fun Insert(/*keyValue: (keyString: String, value: String) -> Unit*/) { Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { Text(text = "Insert:", style = MaterialTheme.typography.headlineSmall) Spacer(modifier = Modifier.height(7.dp)) - Row { + Row(verticalAlignment = Alignment.CenterVertically) { var textKey by remember { mutableStateOf("") } var textValue by remember { mutableStateOf("") } @@ -59,6 +55,7 @@ fun Insert(/*keyValue: (keyString: String, value: String) -> Unit*/) { label = { Text(text = "key")}, singleLine = true, modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, ) Spacer(modifier = Modifier.width(16.dp)) @@ -68,15 +65,19 @@ fun Insert(/*keyValue: (keyString: String, value: String) -> Unit*/) { label = { Text(text = "value")}, singleLine = true, modifier = Modifier.weight(0.30f), - ) + shape = MaterialTheme.shapes.extraLarge, + ) Spacer(modifier = Modifier.width(16.dp)) Button(onClick = { /*keyValue(textKey, textValue)*/ textKey = "" textValue = "" }, - shape = MaterialTheme.shapes.small, - modifier = Modifier.weight(0.30f), ){ + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary) + ){ Text("go!") } } @@ -89,7 +90,7 @@ fun Remove() { Text(text = "Remove:", style = MaterialTheme.typography.headlineSmall) Spacer(modifier = Modifier.height(7.dp)) - Row { + Row(verticalAlignment = Alignment.CenterVertically) { var textKey by remember { mutableStateOf("") } OutlinedTextField( @@ -98,15 +99,18 @@ fun Remove() { label = { Text(text = "key") }, singleLine = true, modifier = Modifier.weight(0.70f), - ) + shape = MaterialTheme.shapes.extraLarge, + ) Spacer(modifier = Modifier.width(16.dp)) Button( onClick = {textKey = "" }, - shape = MaterialTheme.shapes.small, - modifier = Modifier.weight(0.30f) + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary) ) { Text("go!") } @@ -120,7 +124,7 @@ fun Find() { Text(text = "Find:", style = MaterialTheme.typography.headlineSmall) Spacer(modifier = Modifier.height(7.dp)) - Row { + Row(verticalAlignment = Alignment.CenterVertically) { var textKey by remember { mutableStateOf("") } OutlinedTextField( @@ -129,17 +133,22 @@ fun Find() { label = { Text(text = "key") }, singleLine = true, modifier = Modifier.weight(0.70f), - ) + shape = MaterialTheme.shapes.extraLarge, + ) Spacer(modifier = Modifier.width(16.dp)) Button( onClick = { textKey = "" }, - shape = MaterialTheme.shapes.small, - modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary) ) { Text("go!") } } + Spacer(modifier = Modifier.height(15.dp)) + Text(text = "Result: ", style = MaterialTheme.typography.headlineSmall) } } From 9788d66b20b9173e66f1de135f30c7a80f4f2153 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 15:55:50 +0300 Subject: [PATCH 119/164] docs: add docs for public methods --- lib/src/main/kotlin/trees/BinTree.kt | 36 ++++++++++++++++++++++++++-- lib/src/main/kotlin/trees/Tree.kt | 29 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/lib/src/main/kotlin/trees/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt index 7f5b5ef..9ba9c5c 100644 --- a/lib/src/main/kotlin/trees/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -25,15 +25,27 @@ abstract class BinTree, Value> : Tree { protected open var rootNode: BinNode? = null constructor() + + /** + * creates tree with one node, where node have these key, value + */ constructor(key: Key, value: Value) { insert(key, value) } + /** + * creates tree with nodes + * + * @param sort if this param = true, then insert the nodes starting from the average value of the keys + */ constructor(array: Array>, sort: Boolean = false) { if (sort) sortInsert(*array) else insert(*array) } + /** + * inserts the nodes starting from the average value of the keys + */ fun sortInsert(vararg array: Pair) { val serArray = array.sortedBy { it.first }.toTypedArray() var indices = serArray.indices.toList() @@ -51,8 +63,11 @@ abstract class BinTree, Value> : Tree { for (i in keys) remove(i) } - //return the inserted node if the node with the same key wasn't in the tree and null in otherwise - //doesn't balance the tree + /** + * @return the inserted node if the node with the same key wasn't in the tree and null in otherwise + * + * doesn't balance the tree + */ protected fun insertService(node: BinNode): BinNode? { if (rootNode == null) { rootNode = node @@ -163,6 +178,9 @@ abstract class BinTree, Value> : Tree { newNode?.let { it.parent = parent } } + /** + * removes all nodes from the tree + */ fun clean() { rootNode = null } @@ -189,6 +207,10 @@ abstract class BinTree, Value> : Tree { } } + /** + * @return all key, value of all nodes in the tree. + * In order from left to right, by level. + */ fun getKeyValueList(): List> { val list = mutableListOf>() breadthFirstSearch { node -> if (node != null) list.add(Pair(node.key, node.value)) } @@ -202,12 +224,22 @@ abstract class BinTree, Value> : Tree { else null } + /** + * @return all key, value of all nodes in the tree with value of its parent (null if parent doesn't exist). + * In order from left to right, by level. + */ fun getNodesDataWithParentValue(): List> { val list = mutableListOf>() breadthFirstSearch { node -> if (node != null) list.add(Triple(node.key, node.value, node.parent?.value)) } return list } + /** + * changes value of all nodes + * in order from left to right, by level. + * + * @param addNullNodes adds null nodes to all places where nodes do not exist, so that each node has two children + */ fun rewriteAllValue(addNullNodes: Boolean = false, function: (Value?, Int, Int) -> Value?) { val listOfAllNodes = mutableListOf?>>() var listOfLevel = mutableListOf?>() diff --git a/lib/src/main/kotlin/trees/Tree.kt b/lib/src/main/kotlin/trees/Tree.kt index 60d3ae8..0ef2c9c 100644 --- a/lib/src/main/kotlin/trees/Tree.kt +++ b/lib/src/main/kotlin/trees/Tree.kt @@ -1,10 +1,39 @@ package trees interface Tree { + /** + * inserts node in tree + * + * @param key which will have a node, affects its position in the tree, must be a Comparable type + * + * If a node with this key already exists in the tree, its value will be overwritten + * @param value which will have a node, can be any type + */ fun insert(key: Key, value: Value) + /** + * Calls an [insert] for each pair in the order of pairs' order. + */ fun insert(vararg array: Pair) + + /** + * deletes node with this key + * + * if node with this key doesn't exist, do nothing + */ fun remove(key: Key) + /** + * Calls an [remove] for each key in the order of keys' order. + */ fun remove(vararg keys: Key) + + /** + * @return value of node with this key or null if node doesn't exist + */ fun get(key: Key): Value? + /** + * Calls an [get] for each key in the order of keys' order. + * + * @return List of values + */ fun get(vararg keys: Key): List } From 3de704ef4da3e3ffbd7d2d9779aaaacad31e2fa8 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 15:59:51 +0300 Subject: [PATCH 120/164] refactor: rename getAllNode -> getAllNodes --- app/src/main/kotlin/dataBase/DataBase.kt | 2 +- app/src/main/kotlin/dataBase/Json.kt | 2 +- app/src/main/kotlin/dataBase/Neo4j.kt | 8 ++++---- app/src/main/kotlin/dataBase/SQLite.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index 5a6a43f..54a77fd 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -30,7 +30,7 @@ interface DataBase { fun saveTree(treeName: String, tree: BinTree>>) fun readTree(treeName: String): BinTree>> fun removeTree(treeName: String) - fun getAllTree(): List> + fun getAllTrees(): List> fun clean() fun close() fun cleanAndClose() { diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 9f05e04..5c328d3 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -71,7 +71,7 @@ class Json(private val saveDirPath: String) : DataBase { } } - override fun getAllTree(): List> { + override fun getAllTrees(): List> { val list = mutableListOf>() forAllJsonFile { list.add( diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index 0b1cc02..fe42e7d 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -25,8 +25,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { private fun executeQuery(query: String) { try { session.run(query) - } - catch (ex: ServiceUnavailableException) { + } catch (ex: ServiceUnavailableException) { throw IOException("Cannot connect to Neo4j database\nCheck that Neo4j is running and that all the data in the app/src/main/resources/Neo4j.properties file is correct") } } @@ -68,7 +67,8 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { var type = "" session.executeRead { tx -> - type = tx.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName') RETURN tree.type AS type").single()["type"].asString() + type = tx.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName') RETURN tree.type AS type") + .single()["type"].asString() } val tree = typeToTree(type) @@ -101,7 +101,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { executeQuery("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName')-[:next*]->(node) DETACH DELETE node, tree") } - override fun getAllTree(): List> { + override fun getAllTrees(): List> { val list = mutableListOf>() session.executeRead { tx -> val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type") diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 23c673c..238df85 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -151,7 +151,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { executeQuery("DELETE FROM trees WHERE name = '$treeName';") } - override fun getAllTree(): List> { + override fun getAllTrees(): List> { val list = mutableListOf>() val treesSet = getAllTreesStatement.executeQuery() while (treesSet.next()) { @@ -168,7 +168,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { } override fun clean() { - for (i in getAllTree()) + for (i in getAllTrees()) removeTree(i.first) } } From 12d1575188956f24b2570082405047b8e2fefb28 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 16:08:21 +0300 Subject: [PATCH 121/164] feat(controller): add Database class and small refactor add Database inner class rename startCoord -> startCoordinate in DrawTree add validateName add try, catch into init --- app/src/main/kotlin/app/Controller.kt | 53 +++++++++++++++++++++------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index ec9dbae..7ccd194 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -4,16 +4,21 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import dataBase.* import trees.* +import java.io.IOException import kotlin.math.pow class Controller { init { - System.getProperties().load(ClassLoader.getSystemResourceAsStream("Json.properties")) - System.getProperties().load(ClassLoader.getSystemResourceAsStream("Neo4j.properties")) - System.getProperties().load(ClassLoader.getSystemResourceAsStream("SQLite.properties")) + try { + System.getProperties().load(ClassLoader.getSystemResourceAsStream("Json.properties")) + System.getProperties().load(ClassLoader.getSystemResourceAsStream("Neo4j.properties")) + System.getProperties().load(ClassLoader.getSystemResourceAsStream("SQLite.properties")) + } + catch (ex: Exception) { + throw IOException("Cannot get properties file\nCheck that all properties file exist in the src/main/kotlin/app/resources\n$ex") + } } - enum class TreeType { RBTree, AVLTree, @@ -26,6 +31,15 @@ class Controller { SQLite } + fun validateName(name: String) { + for (i in name) + if (i !in 'a'..'z' && i !in 'A'..'Z' && i !in '0'..'9') + throw IllegalArgumentException("Unsupported tree name, please use only ascii letters or digits") + if (name[0] in '0'..'9') + throw IllegalArgumentException("Unsupported tree name, please don't use a digit as the first char") + if (name.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + } + fun getTree(treeType: TreeType) = when (treeType) { TreeType.BSTree -> BSTree>>() TreeType.AVLTree -> AVLTree>>() @@ -43,6 +57,15 @@ class Controller { DatabaseType.SQLite -> SQLite(System.getProperty("sqlite_path"), System.getProperty("max_string_len").toUInt()) } + inner class Database(private val databaseType: DatabaseType) { + private val database = getDatabase(databaseType) + + fun getAllTrees() = database.getAllTrees() + fun removeTree(treeName: String) = database.removeTree(treeName) + fun clean() = database.clean() + fun close() = database.close() + } + open class DrawNode( var key: String, var value: String, @@ -50,11 +73,9 @@ class Controller { var prevCoordinates: MutableState?> ) - fun getAllTrees(databaseType: DatabaseType) = getDatabase(databaseType).getAllTree() - inner class DrawTree { - var tree: BinTree>> - var treeName: String + private var tree: BinTree>> + private var treeName: String constructor(treeName: String, databaseType: DatabaseType) { this.treeName = treeName @@ -76,8 +97,8 @@ class Controller { ) } - fun rewriteAllCoordinates() { - val startCoord = Pair(0F, 0F) //coordinates of the root node + private fun rewriteAllCoordinates() { + val startCoordinate = Pair(0F, 0F) //coordinates of the root node val xMinInterval = 4F //interval between nodes val yInterval = 4F @@ -86,8 +107,8 @@ class Controller { ((height - 2) * xMinInterval * (0.5.pow(level) - 1) * (-2)).toFloat() //the sum of the terms of the geometric progression var lastLevel = -1 - var curX = startCoord.first - var curY = startCoord.second + yInterval + var curX = startCoordinate.first + var curY = startCoordinate.second + yInterval var levelInterval = 0F tree.rewriteAllValue(true) { value, level, height -> if (level != lastLevel) { @@ -115,5 +136,13 @@ class Controller { } fun drawFind(key: String) = tree.get(key)?.first + + fun saveToDB(databaseType: DatabaseType) { + getDatabase(databaseType).saveTree(treeName, tree) + } + + fun clean() { + tree.clean() + } } } From 9ce852363a55e5ff739be50e70020abe933227f3 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 16:21:44 +0300 Subject: [PATCH 122/164] refactor(database): Improve error messages --- app/src/main/kotlin/dataBase/Json.kt | 2 +- app/src/main/kotlin/dataBase/Neo4j.kt | 14 +++++++++----- app/src/main/kotlin/dataBase/SQLite.kt | 10 +++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 5c328d3..1ed310b 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -19,7 +19,7 @@ class Json(private val saveDirPath: String) : DataBase { private fun getFile(treeName: String) = try { File("${saveDirPath}/${treeName}.json") } catch (ex: Exception) { - throw IOException("cannot get file with name: ${saveDirPath}/${treeName}.json") + throw IOException("cannot get file with name: ${saveDirPath}/${treeName}.json\n$ex") } override fun saveTree( diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index fe42e7d..bb21a9d 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -17,8 +17,8 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { try { driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) session = driver.session() - } catch (e: IllegalArgumentException) { - throw IOException("can't start session, try to change uri, user name or password") + } catch (ex: Exception) { + throw IOException("can't start session, try to change uri, user name or password\n$ex") } } @@ -26,7 +26,9 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { try { session.run(query) } catch (ex: ServiceUnavailableException) { - throw IOException("Cannot connect to Neo4j database\nCheck that Neo4j is running and that all the data in the app/src/main/resources/Neo4j.properties file is correct") + throw IOException("Cannot connect to Neo4j database\n" + + "Check that Neo4j is running and that all the data in the app/src/main/resources/Neo4j.properties file is correct\n" + + "$ex") } } @@ -87,8 +89,10 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { Pair(it["x"].asFloat(), it["y"].asFloat()) ) ) - } catch (e: Uncoercible) { - throw IOException("Corrupted data in the database.\n Possible solution: Clear the data.") + } catch (ex: Uncoercible) { + throw IOException("Corrupted data in the database.\nPossible solution: Clear the data.") + } catch (ex: Exception) { + throw IOException("Cannot get or recognise data\n$ex") } } } diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 238df85..b390b2f 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -59,7 +59,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { "type varchar($maxStringLen));" ) } catch (ex: Exception) { - throw SQLException("Cannot create table in database") + throw SQLException("Cannot create table in database\n$ex") } } @@ -75,7 +75,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { "y INTEGER);" ) } catch (ex: Exception) { - throw SQLException("Cannot create table in database") + throw SQLException("Cannot create table in database\n$ex") } } @@ -98,7 +98,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { addTreeStatement.execute() } catch (ex: Exception) { - throw SQLException("Cannot add tree: $treeName") + throw SQLException("Cannot add tree: $treeName\n$ex") } } @@ -108,7 +108,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { try { return getTreeTypeStatement.executeQuery().getString(1) } catch (ex: Exception) { - throw SQLException("Cannot get tree type from database") + throw SQLException("Cannot get tree type from database\n$ex") } finally { getTreeTypeStatement.close() } @@ -135,7 +135,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { ) } } catch (ex: Exception) { - throw IOException("Cannot get nodes from database") + throw IOException("Cannot get nodes from database\n$ex") } finally { getAllNodesStatement.close() } From 9a1f52f9193edf202a5a6d23a6ef7e6182aa8985 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 19:57:36 +0300 Subject: [PATCH 123/164] feat(database): add save of view coordinate --- app/src/main/kotlin/app/Controller.kt | 11 ++++- app/src/main/kotlin/dataBase/DataBase.kt | 11 +++-- app/src/main/kotlin/dataBase/Json.kt | 10 ++--- app/src/main/kotlin/dataBase/Neo4j.kt | 35 ++++++++++----- app/src/main/kotlin/dataBase/SQLite.kt | 54 ++++++++++++++++-------- 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 7ccd194..06f1fc4 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -76,10 +76,13 @@ class Controller { inner class DrawTree { private var tree: BinTree>> private var treeName: String + var viewCoordinates = Pair(0F, 0F) constructor(treeName: String, databaseType: DatabaseType) { this.treeName = treeName - tree = getDatabase(databaseType).readTree(treeName) + val treeData = getDatabase(databaseType).readTree(treeName) + tree = treeData.first + viewCoordinates = treeData.second } constructor(treeName: String, treeType: TreeType) { @@ -137,8 +140,12 @@ class Controller { fun drawFind(key: String) = tree.get(key)?.first + fun updateCoordinate(node: DrawNode) { + tree.insert(node.key, Pair(node.value, node.coordinates.value)) + } + fun saveToDB(databaseType: DatabaseType) { - getDatabase(databaseType).saveTree(treeName, tree) + getDatabase(databaseType).saveTree(treeName, tree, viewCoordinates) } fun clean() { diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index 54a77fd..7a89f1c 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -9,8 +9,7 @@ interface DataBase { is RBTree, is AVLTree -> true else -> false - } - + } fun typeToTree(type: String): BinTree>> = when (type) { BSTree::class.simpleName -> BSTree() RBTree::class.simpleName -> RBTree() @@ -27,8 +26,12 @@ interface DataBase { if (name.isEmpty()) throw IllegalArgumentException("Incorrect tree name") } - fun saveTree(treeName: String, tree: BinTree>>) - fun readTree(treeName: String): BinTree>> + fun saveTree( + treeName: String, + tree: BinTree>>, + viewCoordinates: Pair + ) + fun readTree(treeName: String): Pair>>, Pair> fun removeTree(treeName: String) fun getAllTrees(): List> fun clean() diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 1ed310b..4521ee2 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -23,7 +23,7 @@ class Json(private val saveDirPath: String) : DataBase { } override fun saveTree( - treeName: String, tree: BinTree>> + treeName: String, tree: BinTree>>, viewCoordinates: Pair ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) @@ -37,25 +37,25 @@ class Json(private val saveDirPath: String) : DataBase { jsonFile.appendText( mapper.writeValueAsString( Pair( - Pair(treeName, tree::class.simpleName), + Triple(treeName, tree::class.simpleName, viewCoordinates), tree.getKeyValueList() ) ) ) } - override fun readTree(treeName: String): BinTree>> { + override fun readTree(treeName: String): Pair>>, Pair> { validateName(treeName) val jsonFile = getFile(treeName) val readTree = - mapper.readValue, Array>>>>>( + mapper.readValue>, Array>>>>>( jsonFile ) val tree = typeToTree(readTree.first.second) tree.insert(*readTree.second) - return tree + return Pair(tree, readTree.first.third) } override fun removeTree(treeName: String) { diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index bb21a9d..61abfba 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -26,18 +26,25 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { try { session.run(query) } catch (ex: ServiceUnavailableException) { - throw IOException("Cannot connect to Neo4j database\n" + - "Check that Neo4j is running and that all the data in the app/src/main/resources/Neo4j.properties file is correct\n" + - "$ex") + throw IOException( + "Cannot connect to Neo4j database\n" + + "Check that Neo4j is running and that all the data in the app/src/main/resources/Neo4j.properties file is correct\n" + + "$ex" + ) } } - override fun saveTree(treeName: String, tree: BinTree>>) { + override fun saveTree( + treeName: String, + tree: BinTree>>, + viewCoordinates: Pair + ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) removeTree(treeName) - executeQuery("CREATE (:Tree {name: '$treeName', type: '${tree::class.simpleName}'})") + executeQuery("CREATE (:Tree {name: '$treeName', type: '${tree::class.simpleName}', " + + "viewX: ${viewCoordinates.first}, viewY: ${viewCoordinates.second}})") var prevKey: String? = null tree.getKeyValueList() .forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } @@ -64,13 +71,21 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } } - override fun readTree(treeName: String): BinTree>> { + override fun readTree(treeName: String): Pair>>, Pair> { validateName(treeName) var type = "" + var viewCoordinates = Pair(0F, 0F) session.executeRead { tx -> - type = tx.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName') RETURN tree.type AS type") - .single()["type"].asString() + val result = tx.run("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName') RETURN tree.type AS type, tree.viewX AS x, tree.viewY AS y").single() + try { + type = result["type"].asString() + viewCoordinates = Pair(result["x"].asFloat(), result["y"].asFloat()) + } catch (ex: Uncoercible) { + throw IOException("Corrupted data in the database.\nPossible solution: Clear the data.\n$ex") + } catch (ex: Exception) { + throw IOException("Cannot get or recognise data\n$ex") + } } val tree = typeToTree(type) @@ -90,13 +105,13 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { ) ) } catch (ex: Uncoercible) { - throw IOException("Corrupted data in the database.\nPossible solution: Clear the data.") + throw IOException("Corrupted data in the database.\nPossible solution: Clear the data.\n$ex") } catch (ex: Exception) { throw IOException("Cannot get or recognise data\n$ex") } } } - return tree + return Pair(tree, viewCoordinates) } override fun removeTree(treeName: String) { diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index b390b2f..859a486 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -11,27 +11,39 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { private const val DB_DRIVER = "jdbc:sqlite" } - private val connection = DriverManager.getConnection("$DB_DRIVER:$dbPath") - ?: throw SQLException("Cannot connect to database") - private val addTreeStatement by lazy { connection.prepareStatement("INSERT INTO trees (name, type) VALUES (?, ?);") } + private val connection = try { + DriverManager.getConnection("$DB_DRIVER:$dbPath") + } catch (ex: Exception) { + throw SQLException("Cannot connect to database\nCheck that it is running and that there is no error in the path to it\n$ex") + } + ?: throw SQLException("Cannot connect to database\nCheck that it is running and that there is no error in the path to it") + private val addTreeStatement by lazy { connection.prepareStatement("INSERT INTO trees (name, type, viewX, viewY) VALUES (?, ?, ?, ?);") } private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type FROM trees;") } - override fun saveTree(treeName: String, tree: BinTree>>) { + override fun saveTree( + treeName: String, + tree: BinTree>>, + viewCoordinates: Pair + ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) - removeTree(treeName) createTreesTable() + removeTree(treeName) createTableForTree(treeName) - addTree(treeName, tree::class.simpleName ?: throw IllegalArgumentException("Cannot get tree type")) + addTree( + treeName, + tree::class.simpleName ?: throw IllegalArgumentException("Cannot get tree type"), + viewCoordinates + ) val addNodeStatement by lazy { connection.prepareStatement("INSERT INTO ${treeName}Nodes (key, value, x, y) VALUES (?, ?, ?, ?);") } tree.getKeyValueList() .forEach { saveNode(it.first, it.second.first, it.second.second, treeName, addNodeStatement) } addNodeStatement.close() } - + private fun saveNode( key: String, value: String, @@ -56,7 +68,9 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { executeQuery( "CREATE TABLE if not exists trees (treeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + "name varchar($maxStringLen), " + - "type varchar($maxStringLen));" + "type varchar($maxStringLen), " + + "viewX FLOAT, " + + "viewY FLOAT);" ) } catch (ex: Exception) { throw SQLException("Cannot create table in database\n$ex") @@ -71,8 +85,8 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { "CREATE TABLE if not exists ${treeName}Nodes (nodeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + "key varchar($maxStringLen), " + "value varchar($maxStringLen), " + - "x INTEGER, " + - "y INTEGER);" + "x FLOAT, " + + "y FLOAT);" ) } catch (ex: Exception) { throw SQLException("Cannot create table in database\n$ex") @@ -84,17 +98,19 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { try { stmt.execute(query) } catch (ex: Exception) { - throw SQLException("Cannot execute query: \"$query\"") + throw SQLException("Cannot execute query: \"$query\"\n$ex") } finally { stmt.close() } } } - private fun addTree(treeName: String, treeType: String) { + private fun addTree(treeName: String, treeType: String, viewCoordinates: Pair) { try { addTreeStatement.setString(1, treeName) addTreeStatement.setString(2, treeType) + addTreeStatement.setFloat(3, viewCoordinates.first) + addTreeStatement.setFloat(4, viewCoordinates.second) addTreeStatement.execute() } catch (ex: Exception) { @@ -102,11 +118,12 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { } } - private fun getTreeType(treeName: String): String { - val getTreeTypeStatement by lazy { connection.prepareStatement("SELECT trees.type FROM trees WHERE name = ?") } + private fun getTreeData(treeName: String): Pair> { + val getTreeTypeStatement by lazy { connection.prepareStatement("SELECT trees.type as type, trees.viewX as x, trees.viewY as y FROM trees WHERE name = ?") } getTreeTypeStatement.setString(1, treeName) try { - return getTreeTypeStatement.executeQuery().getString(1) + val data = getTreeTypeStatement.executeQuery() + return Pair(data.getString("type"), Pair(data.getFloat("x"), data.getFloat("y"))) } catch (ex: Exception) { throw SQLException("Cannot get tree type from database\n$ex") } finally { @@ -115,13 +132,14 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { } - override fun readTree(treeName: String): BinTree>> { + override fun readTree(treeName: String): Pair>>, Pair> { validateName(treeName) val nodes = "${treeName}Nodes" val getAllNodesStatement by lazy { connection.prepareStatement("SELECT $nodes.key as key, $nodes.value as value, $nodes.x as x, $nodes.y as y FROM $nodes;") } - val tree = typeToTree(getTreeType(treeName)) + val treeData = getTreeData(treeName) + val tree = typeToTree(treeData.first) try { val nodesSet = getAllNodesStatement.executeQuery() @@ -140,7 +158,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { getAllNodesStatement.close() } - return tree + return Pair(tree, treeData.second) } override fun removeTree(treeName: String) { From 6f9f6fd8e3586cd1b3a7dcad73be57fed8bbaabe Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 1 May 2023 20:11:09 +0300 Subject: [PATCH 124/164] feat: add navigation in app --- app/src/main/kotlin/app/Main | 16 +++++++++++ app/src/main/kotlin/app/Windows.kt | 45 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 app/src/main/kotlin/app/Main create mode 100644 app/src/main/kotlin/app/Windows.kt diff --git a/app/src/main/kotlin/app/Main b/app/src/main/kotlin/app/Main new file mode 100644 index 0000000..9d77c1b --- /dev/null +++ b/app/src/main/kotlin/app/Main @@ -0,0 +1,16 @@ +package app + +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Trees", + state = rememberWindowState(width = 800.dp, height = 600.dp) + ) { + Main(window) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/Windows.kt b/app/src/main/kotlin/app/Windows.kt new file mode 100644 index 0000000..99a5dad --- /dev/null +++ b/app/src/main/kotlin/app/Windows.kt @@ -0,0 +1,45 @@ +package app + +import androidx.compose.runtime.* +import androidx.compose.ui.awt.ComposeWindow + + +sealed class Screen { + object MainWindow: Screen() + + object CreatNewWindow: Screen() + + object OpenTree: Screen() + + data class TreeWindow(val tree: Controller.DrawTree): Screen() +} +@Composable +fun Main(window: ComposeWindow) { + var screenState by remember { mutableStateOf(Screen.MainWindow) } + + when (val screen = screenState) { + is Screen.MainWindow -> + MainWindow( + onClickNew = { screenState = Screen.CreatNewWindow }, + onClickOpen = { screenState = Screen.OpenTree } + ) + + is Screen.CreatNewWindow -> + CreatNewTree( + onBack = {screenState = Screen.MainWindow}, + onClick = { screenState = Screen.TreeWindow(tree = it) } + ) + + is Screen.OpenTree -> + OpenTree( + onBack = {screenState = Screen.MainWindow}, + onClick = { screenState = Screen.TreeWindow(tree = it) } + ) + + is Screen.TreeWindow -> + Tree( + onBack = {screenState = Screen.MainWindow}, + tree = screen.tree + ) + } +} From 596682d61cd32f5a30272e9c8a7efac499a5658b Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 1 May 2023 20:14:06 +0300 Subject: [PATCH 125/164] feat: add windows for app --- app/src/main/kotlin/app/CreatNewWindow.kt | 188 ++++++-------- app/src/main/kotlin/app/MainWindow.kt | 113 ++++---- app/src/main/kotlin/app/OpenTree.kt | 87 +++++++ app/src/main/kotlin/app/TreeWindow.kt | 300 +++++++++++++--------- 4 files changed, 407 insertions(+), 281 deletions(-) create mode 100644 app/src/main/kotlin/app/OpenTree.kt diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index b251ce8..97e1576 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -1,125 +1,105 @@ -package UIT +package app +import UIT.md_theme_light_primary import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import androidx.compose.ui.window.rememberWindowState -fun main() = application { - Window( - onCloseRequest = ::exitApplication, - title = "Compose for Desktop", - state = rememberWindowState(width = 800.dp, height = 600.dp) - ) { - MaterialTheme { - Column( - modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), - verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - var name by remember { mutableStateOf("") } - OutlinedTextField( - value = name, - onValueChange = { name = it }, - label = { Text(text = "name") }, - singleLine = true, - modifier = Modifier.weight(0.70f), - shape = MaterialTheme.shapes.extraLarge, - ) - } - Spacer(modifier = Modifier.height(15.dp)) - - Text(text = "Add your first key and value!", style = MaterialTheme.typography.titleMedium) - - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - var textValue by remember { mutableStateOf("") } +@Composable +fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { + var name by remember { mutableStateOf("") } + MaterialTheme { + Column( + modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text(text = "name") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, + ) + } + Spacer(modifier = Modifier.height(15.dp)) - OutlinedTextField( - value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key") }, - singleLine = true, - modifier = Modifier.weight(0.30f), - shape = MaterialTheme.shapes.extraLarge, + Spacer(modifier = Modifier.height(15.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary ) - - Spacer(modifier = Modifier.width(16.dp)) - - OutlinedTextField( - value = textValue, - onValueChange = { textValue = it }, - label = { Text(text = "value") }, - singleLine = true, - modifier = Modifier.weight(0.30f), - shape = MaterialTheme.shapes.extraLarge, + ) { + Text( + text = "Exit", ) } - Spacer(modifier = Modifier.height(15.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Button( - onClick = { }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Exit", - ) - } - Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Json", - ) - } - Spacer(modifier = Modifier.width(16.dp)) - - Button( - onClick = { }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "SQLite", - ) } - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + val tree = Controller().DrawTree(name, Controller.TreeType.BSTree) + name = "" + onClick(tree) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "BSTree", + ) + } + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Neo4j", - ) - } + Button( + onClick = { + val tree = Controller().DrawTree(name, Controller.TreeType.AVLTree) + name = "" + onClick(tree) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "AVLTree", + ) } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + val tree = Controller().DrawTree(name, Controller.TreeType.RBTree) + name = "" + onClick(tree) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "RBTree", + ) + } } + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/MainWindow.kt b/app/src/main/kotlin/app/MainWindow.kt index 88844b2..2d43559 100644 --- a/app/src/main/kotlin/app/MainWindow.kt +++ b/app/src/main/kotlin/app/MainWindow.kt @@ -1,5 +1,6 @@ -package UIT +package app +import UIT.md_theme_light_primary import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.Text @@ -9,69 +10,59 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import androidx.compose.ui.window.rememberWindowState +import kotlin.system.exitProcess -fun main() = application { - var isOpen by remember { mutableStateOf(true) } - if (isOpen) { - Window( - onCloseRequest = ::exitApplication, - title = "Compose for Desktop", - state = rememberWindowState(width = 800.dp, height = 600.dp) +@Composable +fun MainWindow(onClickNew: () -> Unit, onClickOpen: () -> Unit) { + MaterialTheme { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally ) { - MaterialTheme { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Button( - onClick = { }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "New", - style = MaterialTheme.typography.headlineSmall - ) - } - Spacer(modifier = Modifier.height(15.dp)) - Button( - onClick = { }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Open", - style = MaterialTheme.typography.headlineSmall - ) - } - Spacer(modifier = Modifier.height(15.dp)) - - Button( - onClick = { isOpen = false}, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Exit", - style = MaterialTheme.typography.headlineSmall - ) - } - } + Button( + onClick = onClickNew, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "New", + style = MaterialTheme.typography.headlineSmall + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = onClickOpen, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Open", + style = MaterialTheme.typography.headlineSmall + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { exitProcess(1) }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Exit", + style = MaterialTheme.typography.headlineSmall + ) } } + } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/OpenTree.kt new file mode 100644 index 0000000..02fffeb --- /dev/null +++ b/app/src/main/kotlin/app/OpenTree.kt @@ -0,0 +1,87 @@ +package app + +import UIT.md_theme_light_primary +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + + +@Composable +fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { + MaterialTheme { + Column( + modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + val files = Controller().Database(Controller.DatabaseType.Json).getAllTrees() + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Json", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + val files = Controller().Database(Controller.DatabaseType.SQLite).getAllTrees() + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "SQLite", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + val files = Controller().Database(Controller.DatabaseType.Neo4j).getAllTrees() + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Neo4j", + ) + } + } + Row(verticalAlignment = Alignment.CenterVertically) { + } + + } + } +} diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 71343c2..6d5fde9 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -1,5 +1,6 @@ -package UIT +package app +import UIT.md_theme_light_primary import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.* @@ -8,147 +9,214 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import androidx.compose.ui.window.rememberWindowState - -fun main() = application { - Window( - onCloseRequest = ::exitApplication, - title = "Compose for Desktop", - state = rememberWindowState(width = 800.dp, height = 600.dp) - ) { - MaterialTheme { - val modifierColumn = Modifier.fillMaxSize().background(Color.White).padding(6.dp) - Row(modifierColumn) { + +@Composable +fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { + var textForUser by remember { mutableStateOf("") } + MaterialTheme { + Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { Column( - modifier = Modifier.padding(start = 32.dp, top = 16.dp) + modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(400.dp) ) { - Insert() - Remove() - Find() + Insert( + onClick = { key, value -> + if ((key != "") && (value != "")) { + tree.drawInsert(key, value) + textForUser = "I insert node with key = $key and value = $value :)" + } + else { + textForUser = "Give me key and value pls :(" + } + } + ) + Remove( + onClick = { key -> + if (key != "") { + tree.drawRemove(key) + textForUser = "I remove node :)" + } + else { + textForUser = "Give me key pls :(" + } + } + ) + Find( + onClick = { key -> + val value = tree.drawFind(key) + if (value != null) { + textForUser = "Result: $value" + } + else { + textForUser = "I can't find node :(" + } + } + ) + Spacer(modifier = Modifier.height(15.dp)) + Text(text = textForUser, + modifier = Modifier.padding(start = 32.dp, top = 16.dp), + style = MaterialTheme.typography.headlineSmall, + ) + Row(modifier = Modifier.height(570.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.aligned(Alignment.End)) { + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Save", + ) + } + } } - Box( - modifier = Modifier - .fillMaxSize(), - ) { - + Box ( + modifier = Modifier + .fillMaxSize().padding(start = 200.dp, end = 30.dp), + ) { + ViewTree().drawTree(tree) + } } } - } } - } -@Composable -fun Insert(/*keyValue: (keyString: String, value: String) -> Unit*/) { - Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { - Text(text = "Insert:", style = MaterialTheme.typography.headlineSmall) - Spacer(modifier = Modifier.height(7.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - var textValue by remember { mutableStateOf("") } - - - OutlinedTextField( value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key")}, - singleLine = true, - modifier = Modifier.weight(0.30f), - shape = MaterialTheme.shapes.extraLarge, + + @Composable + fun Insert(onClick: (key: String, value: String) -> Unit) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Insert:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + var textValue by remember { mutableStateOf("") } + + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, ) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - OutlinedTextField( value = textValue, - onValueChange = { textValue = it }, - label = { Text(text = "value")}, - singleLine = true, - modifier = Modifier.weight(0.30f), - shape = MaterialTheme.shapes.extraLarge, + OutlinedTextField( + value = textValue, + onValueChange = { textValue = it }, + label = { Text(text = "value") }, + singleLine = true, + modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, ) - Spacer(modifier = Modifier.width(16.dp)) - - Button(onClick = { /*keyValue(textKey, textValue)*/ - textKey = "" - textValue = "" }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary) - ){ - Text("go!") + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + onClick(textKey, textValue) + textKey = "" + textValue = "" + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go!") + } } } } -} -@Composable -fun Remove() { - Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { - Text(text = "Remove:", style = MaterialTheme.typography.headlineSmall) - Spacer(modifier = Modifier.height(7.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - - OutlinedTextField( - value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key") }, - singleLine = true, - modifier = Modifier.weight(0.70f), - shape = MaterialTheme.shapes.extraLarge, + @Composable + fun Remove(onClick: (key: String) -> Unit) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Remove:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, ) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = {textKey = "" }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary) - ) { - Text("go!") + Button( + onClick = { + onClick(textKey) + textKey = "" }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go!") + } } } } -} -@Composable -fun Find() { - Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { - Text(text = "Find:", style = MaterialTheme.typography.headlineSmall) - Spacer(modifier = Modifier.height(7.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - - OutlinedTextField( - value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key") }, - singleLine = true, - modifier = Modifier.weight(0.70f), - shape = MaterialTheme.shapes.extraLarge, + @Composable + fun Find(onClick: (key: String) -> Unit) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Find:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) + + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, ) - Spacer(modifier = Modifier.width(16.dp)) - - Button( - onClick = { textKey = "" }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary) - ) { - Text("go!") + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + onClick(textKey) + textKey = "" }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go!") + } } } - Spacer(modifier = Modifier.height(15.dp)) - Text(text = "Result: ", style = MaterialTheme.typography.headlineSmall) } -} From dd8d611e6bcdec97147e8d0223218d40a363d2fe Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 20:16:10 +0300 Subject: [PATCH 126/164] refactor(properties): add App.properties and move max_string_len to App.properties --- app/src/main/kotlin/app/Controller.kt | 8 +++++--- app/src/main/resources/App.properties | 1 + app/src/main/resources/SQLite.properties | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 app/src/main/resources/App.properties diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 06f1fc4..18a6201 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -10,11 +10,11 @@ import kotlin.math.pow class Controller { init { try { + System.getProperties().load(ClassLoader.getSystemResourceAsStream("App.properties")) System.getProperties().load(ClassLoader.getSystemResourceAsStream("Json.properties")) System.getProperties().load(ClassLoader.getSystemResourceAsStream("Neo4j.properties")) System.getProperties().load(ClassLoader.getSystemResourceAsStream("SQLite.properties")) - } - catch (ex: Exception) { + } catch (ex: Exception) { throw IOException("Cannot get properties file\nCheck that all properties file exist in the src/main/kotlin/app/resources\n$ex") } } @@ -37,7 +37,9 @@ class Controller { throw IllegalArgumentException("Unsupported tree name, please use only ascii letters or digits") if (name[0] in '0'..'9') throw IllegalArgumentException("Unsupported tree name, please don't use a digit as the first char") - if (name.isEmpty()) throw IllegalArgumentException("Incorrect tree name") + if (name.length !in 1..System.getProperty("max_string_len") + .toInt() + ) throw IllegalArgumentException("Incorrect tree name\nThe name must be less than ${System.getProperty("max_string_len")} and greater than 0") } fun getTree(treeType: TreeType) = when (treeType) { diff --git a/app/src/main/resources/App.properties b/app/src/main/resources/App.properties new file mode 100644 index 0000000..1e392f5 --- /dev/null +++ b/app/src/main/resources/App.properties @@ -0,0 +1 @@ +max_string_len = 255 diff --git a/app/src/main/resources/SQLite.properties b/app/src/main/resources/SQLite.properties index bdec215..2a938c2 100644 --- a/app/src/main/resources/SQLite.properties +++ b/app/src/main/resources/SQLite.properties @@ -1,2 +1 @@ -max_string_len = 255 sqlite_path = SQLDB/sqliteTreeStorage.db From 6600f2395351e7392e81ceaa6d69e9ac3c29f6a1 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 1 May 2023 21:26:11 +0300 Subject: [PATCH 127/164] feat: change gradle application --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 04c088c..8211d3e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { } application { - mainClass.set("MainKt") + mainClass.set("app.MainKt") } repositories { From 3e670354db1589e4d228c7e838656d780a27928e Mon Sep 17 00:00:00 2001 From: juliakononov Date: Mon, 1 May 2023 21:38:44 +0300 Subject: [PATCH 128/164] feat: add lazy column to open window --- app/src/main/kotlin/app/OpenTree.kt | 51 ++++++++++++++++++++-- app/src/main/kotlin/app/ViewTree.kt | 66 ++++++++++++----------------- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/OpenTree.kt index 02fffeb..08690fd 100644 --- a/app/src/main/kotlin/app/OpenTree.kt +++ b/app/src/main/kotlin/app/OpenTree.kt @@ -2,6 +2,8 @@ package app import UIT.md_theme_light_primary import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -11,6 +13,9 @@ import androidx.compose.ui.unit.dp @Composable fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { + val controller = Controller() + var files = listOf(Pair("", "")) + var dataBaseType: Controller.DatabaseType = Controller.DatabaseType.Json MaterialTheme { Column( modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), @@ -34,7 +39,8 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Button( onClick = { - val files = Controller().Database(Controller.DatabaseType.Json).getAllTrees() + files = controller.Database(Controller.DatabaseType.Json).getAllTrees() + dataBaseType = Controller.DatabaseType.Json }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f).height(57.dp), @@ -50,7 +56,8 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - val files = Controller().Database(Controller.DatabaseType.SQLite).getAllTrees() + files = controller.Database(Controller.DatabaseType.SQLite).getAllTrees() + dataBaseType = Controller.DatabaseType.SQLite }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f).height(57.dp), @@ -66,7 +73,8 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - val files = Controller().Database(Controller.DatabaseType.Neo4j).getAllTrees() + files = controller.Database(Controller.DatabaseType.Neo4j).getAllTrees() + dataBaseType = Controller.DatabaseType.Neo4j }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f).height(57.dp), @@ -79,9 +87,44 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { ) } } + Spacer(modifier = Modifier.height(15.dp)) Row(verticalAlignment = Alignment.CenterVertically) { + LazyColumn { + items(files) { file -> + Row { + Text( + text = "name: \"${file.first}\" type: ${file.second}" + ) + Spacer(modifier = Modifier.width(15.dp)) + Button( + onClick = { + onClick(controller.DrawTree(file.first, dataBaseType)) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("Open") + } + Spacer(modifier = Modifier.width(15.dp)) + Button( + onClick = { + controller.Database(dataBaseType).removeTree(file.first) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("Delete") + } + } + } + } } - } } } diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ViewTree.kt index 4118d9b..08ccdc1 100644 --- a/app/src/main/kotlin/app/ViewTree.kt +++ b/app/src/main/kotlin/app/ViewTree.kt @@ -29,40 +29,26 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import kotlin.math.roundToInt -open class ViewTree, Value> { - - protected class ViewNode, String>( - var key: Int, - var value: String, - var x: MutableState, - var y: MutableState, - var left: ViewNode? = null, - var right: ViewNode? = null, - var color: Boolean, - ) +open class ViewTree { @Composable - protected fun drawTree(node: ViewNode){ - - node.left?.let { - drawLine(node.x, node.y, it.x, it.y) - drawTree(it) - - } - drawNode(node.key.toString(), node.x, node.y) - node.right?.let { - drawLine(node.x, node.y, it.x, it.y) - drawTree(it) - } + open fun drawTree(tree: Controller.DrawTree ) { + tree.getAllDrawNodes().forEach { node -> + drawNode(node.key, node.coordinates)} +// node.prevCoordinates.value?.let { +// drawLine(node.coordinates, node.prevCoordinates) +// } +// } } @Composable - fun drawNode(key: String, - centerX: MutableState, - centerY: MutableState) { + fun drawNode( + key: String, + coordinates: MutableState> + ) { - val offsetX = remember { mutableStateOf(0f) } - val offsetY = remember { mutableStateOf(0f) } + val offsetX = remember { mutableStateOf(coordinates.value.first) } + val offsetY = remember { mutableStateOf(coordinates.value.second) } Box( modifier = Modifier @@ -73,26 +59,24 @@ open class ViewTree, Value> { ) } .pointerInput(Unit) { - detectDragGestures { change, dragAmount -> + detectDragGestures { _, dragAmount -> offsetX.value += dragAmount.x offsetY.value += dragAmount.y - centerX.value = offsetX.value - centerY.value = offsetY.value + //coordinates.value = Pair(offsetX.value, offsetY.value) } } - .size(40.dp) + .size(60.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.primary) .onGloballyPositioned { layoutCoordinates -> val rect = layoutCoordinates.boundsInParent() - centerX.value = rect.center.x - centerY.value = rect.center.y + coordinates.value = Pair(rect.center.x, rect.center.y) }, contentAlignment = Alignment.Center ) { Text( text = key, - fontSize = 30.sp, + fontSize = 20.sp, color = Color.White, fontWeight = FontWeight.Bold ) @@ -100,12 +84,14 @@ open class ViewTree, Value> { } @Composable - fun drawLine(x1: MutableState, y1: MutableState, x2: MutableState, y2: MutableState) { + fun drawLine(coordinates:MutableState>, prevCoordinates: MutableState>) { Canvas(modifier = Modifier.fillMaxSize().zIndex(-1f)) { - drawLine(color = Color.DarkGray, - start = Offset(x1.value, y1.value), - end = Offset(x2.value, y2.value), - strokeWidth = 5f ) + drawLine( + color = Color.DarkGray, + start = Offset(coordinates.value.first,coordinates.value.second), + end = Offset(prevCoordinates.value.first, prevCoordinates.value.second), + strokeWidth = 5f + ) } } } From d3225d458da430e2bf76b26a4a3a2cfc2d51266e Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 21:41:06 +0300 Subject: [PATCH 129/164] fix(database): dirs create during the initialization --- app/src/main/kotlin/dataBase/Json.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 4521ee2..f63ba9f 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -14,6 +14,7 @@ class Json(private val saveDirPath: String) : DataBase { init { if (saveDirPath.last() == '\\' || saveDirPath.last() == '/') throw IllegalArgumentException("Please, don't use '/' or '\\' in the end of dir path") + File(saveDirPath).mkdirs() } private fun getFile(treeName: String) = try { @@ -31,7 +32,6 @@ class Json(private val saveDirPath: String) : DataBase { removeTree(treeName) val jsonFile = getFile(treeName) - File(saveDirPath).mkdirs() jsonFile.createNewFile() jsonFile.appendText( From e06a95e0462c573649f7e2b4c5d16c93939e579b Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Mon, 1 May 2023 21:42:00 +0300 Subject: [PATCH 130/164] refactor(controller): make the controller an object --- app/src/main/kotlin/app/Controller.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 18a6201..368a1b4 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -7,7 +7,7 @@ import trees.* import java.io.IOException import kotlin.math.pow -class Controller { +object Controller { init { try { System.getProperties().load(ClassLoader.getSystemResourceAsStream("App.properties")) @@ -59,7 +59,7 @@ class Controller { DatabaseType.SQLite -> SQLite(System.getProperty("sqlite_path"), System.getProperty("max_string_len").toUInt()) } - inner class Database(private val databaseType: DatabaseType) { + class Database(private val databaseType: DatabaseType) { private val database = getDatabase(databaseType) fun getAllTrees() = database.getAllTrees() @@ -75,7 +75,7 @@ class Controller { var prevCoordinates: MutableState?> ) - inner class DrawTree { + class DrawTree { private var tree: BinTree>> private var treeName: String var viewCoordinates = Pair(0F, 0F) From 59a380d504f04a44d443e2bcf8416d06c14d6ba8 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 00:11:10 +0300 Subject: [PATCH 131/164] fix(ui): change main extension to kt, remove Controller instantiation --- app/src/main/kotlin/app/CreatNewWindow.kt | 6 +++--- app/src/main/kotlin/app/{Main => Main.kt} | 0 app/src/main/kotlin/app/OpenTree.kt | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) rename app/src/main/kotlin/app/{Main => Main.kt} (100%) diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index 97e1576..fa03864 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -48,7 +48,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Button( onClick = { - val tree = Controller().DrawTree(name, Controller.TreeType.BSTree) + val tree = Controller.DrawTree(name, Controller.TreeType.BSTree) name = "" onClick(tree) }, @@ -66,7 +66,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - val tree = Controller().DrawTree(name, Controller.TreeType.AVLTree) + val tree = Controller.DrawTree(name, Controller.TreeType.AVLTree) name = "" onClick(tree) }, @@ -84,7 +84,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - val tree = Controller().DrawTree(name, Controller.TreeType.RBTree) + val tree = Controller.DrawTree(name, Controller.TreeType.RBTree) name = "" onClick(tree) }, diff --git a/app/src/main/kotlin/app/Main b/app/src/main/kotlin/app/Main.kt similarity index 100% rename from app/src/main/kotlin/app/Main rename to app/src/main/kotlin/app/Main.kt diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/OpenTree.kt index 08690fd..213e62b 100644 --- a/app/src/main/kotlin/app/OpenTree.kt +++ b/app/src/main/kotlin/app/OpenTree.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.unit.dp @Composable fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { - val controller = Controller() var files = listOf(Pair("", "")) var dataBaseType: Controller.DatabaseType = Controller.DatabaseType.Json MaterialTheme { @@ -39,7 +38,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Button( onClick = { - files = controller.Database(Controller.DatabaseType.Json).getAllTrees() + files = Controller.Database(Controller.DatabaseType.Json).getAllTrees() dataBaseType = Controller.DatabaseType.Json }, shape = MaterialTheme.shapes.extraLarge, @@ -56,7 +55,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - files = controller.Database(Controller.DatabaseType.SQLite).getAllTrees() + files = Controller.Database(Controller.DatabaseType.SQLite).getAllTrees() dataBaseType = Controller.DatabaseType.SQLite }, shape = MaterialTheme.shapes.extraLarge, @@ -73,7 +72,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - files = controller.Database(Controller.DatabaseType.Neo4j).getAllTrees() + files = Controller.Database(Controller.DatabaseType.Neo4j).getAllTrees() dataBaseType = Controller.DatabaseType.Neo4j }, shape = MaterialTheme.shapes.extraLarge, @@ -98,7 +97,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.width(15.dp)) Button( onClick = { - onClick(controller.DrawTree(file.first, dataBaseType)) + onClick(Controller.DrawTree(file.first, dataBaseType)) }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.30f).height(57.dp), @@ -111,7 +110,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.width(15.dp)) Button( onClick = { - controller.Database(dataBaseType).removeTree(file.first) + Controller.Database(dataBaseType).removeTree(file.first) }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.30f).height(57.dp), From 5d4dd6301839c26cc45ab5d51fb4e32bfcbd07ee Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 12:16:55 +0300 Subject: [PATCH 132/164] refactor(controller, properties): add name of database to properties, took out the intervals between notes from a private function --- app/src/main/kotlin/app/Controller.kt | 18 +++++++++--------- app/src/main/resources/Json.properties | 2 +- app/src/main/resources/Neo4j.properties | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 368a1b4..3a45f7b 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -49,11 +49,11 @@ object Controller { } fun getDatabase(databaseType: DatabaseType) = when (databaseType) { - DatabaseType.Json -> Json(System.getProperty("save_dir")) + DatabaseType.Json -> Json(System.getProperty("json_save_dir")) DatabaseType.Neo4j -> Neo4j( - System.getProperty("uri"), - System.getProperty("user"), - System.getProperty("password") + System.getProperty("neo4j_uri"), + System.getProperty("neo4j_user"), + System.getProperty("neo4j_password") ) DatabaseType.SQLite -> SQLite(System.getProperty("sqlite_path"), System.getProperty("max_string_len").toUInt()) @@ -80,6 +80,11 @@ object Controller { private var treeName: String var viewCoordinates = Pair(0F, 0F) + var startCoordinate = Pair(0F, 0F) //coordinates of the root node + + var xMinInterval = 4F //interval between nodes + var yInterval = 4F //interval between nodes + constructor(treeName: String, databaseType: DatabaseType) { this.treeName = treeName val treeData = getDatabase(databaseType).readTree(treeName) @@ -103,11 +108,6 @@ object Controller { } private fun rewriteAllCoordinates() { - val startCoordinate = Pair(0F, 0F) //coordinates of the root node - - val xMinInterval = 4F //interval between nodes - val yInterval = 4F - fun offsetOnLevel(level: Int, height: Int) = ((height - 2) * xMinInterval * (0.5.pow(level) - 1) * (-2)).toFloat() //the sum of the terms of the geometric progression diff --git a/app/src/main/resources/Json.properties b/app/src/main/resources/Json.properties index d8bf1e6..971fe70 100644 --- a/app/src/main/resources/Json.properties +++ b/app/src/main/resources/Json.properties @@ -1 +1 @@ -save_dir = JsonSave +json_save_dir = JsonSave diff --git a/app/src/main/resources/Neo4j.properties b/app/src/main/resources/Neo4j.properties index ff78ecf..cd28511 100644 --- a/app/src/main/resources/Neo4j.properties +++ b/app/src/main/resources/Neo4j.properties @@ -1,3 +1,3 @@ -uri = bolt://localhost:7687 -user = neo4j -password = 12345678 +neo4j_uri = bolt://localhost:7687 +neo4j_user = neo4j +neo4j_password = 12345678 From 8e7332d641d65c3bbb33c4f1c05b26177da1e1fa Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 13:13:08 +0300 Subject: [PATCH 133/164] refactor(database): change getAllTree return to list of Triple(name, type, viewCoordinate), placed createTreesTable into init in SQLite --- app/src/main/kotlin/dataBase/DataBase.kt | 2 +- app/src/main/kotlin/dataBase/Json.kt | 6 +++--- app/src/main/kotlin/dataBase/Neo4j.kt | 8 ++++---- app/src/main/kotlin/dataBase/SQLite.kt | 18 +++++++++++++----- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index 7a89f1c..63973da 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -33,7 +33,7 @@ interface DataBase { ) fun readTree(treeName: String): Pair>>, Pair> fun removeTree(treeName: String) - fun getAllTrees(): List> + fun getAllTrees(): List>> fun clean() fun close() fun cleanAndClose() { diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index f63ba9f..79fd16c 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -71,11 +71,11 @@ class Json(private val saveDirPath: String) : DataBase { } } - override fun getAllTrees(): List> { - val list = mutableListOf>() + override fun getAllTrees(): MutableList>> { + val list = mutableListOf>>() forAllJsonFile { list.add( - mapper.readValue, Array>>>>>( + mapper.readValue>, Array>>>>>( it ).first ) diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index 61abfba..a78e8ea 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -120,12 +120,12 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { executeQuery("OPTIONAL MATCH (tree: Tree WHERE tree.name = '$treeName')-[:next*]->(node) DETACH DELETE node, tree") } - override fun getAllTrees(): List> { - val list = mutableListOf>() + override fun getAllTrees(): List>> { + val list = mutableListOf>>() session.executeRead { tx -> - val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type") + val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type, tree.viewX AS x, tree.viewY AS y") result.stream().forEach { - list.add(Pair(it["name"].asString(), it["type"].asString())) + list.add(Triple(it["name"].asString(), it["type"].asString(), Pair(it["x"].asFloat(), it["y"].asFloat()))) } } return list diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 859a486..c4c0dba 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -18,8 +18,11 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { } ?: throw SQLException("Cannot connect to database\nCheck that it is running and that there is no error in the path to it") private val addTreeStatement by lazy { connection.prepareStatement("INSERT INTO trees (name, type, viewX, viewY) VALUES (?, ?, ?, ?);") } - private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type FROM trees;") } + private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type, trees.viewX as x, trees.viewY as y FROM trees;") } + init { + createTreesTable() + } override fun saveTree( treeName: String, @@ -29,7 +32,6 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) - createTreesTable() removeTree(treeName) createTableForTree(treeName) addTree( @@ -169,11 +171,17 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { executeQuery("DELETE FROM trees WHERE name = '$treeName';") } - override fun getAllTrees(): List> { - val list = mutableListOf>() + override fun getAllTrees(): List>> { + val list = mutableListOf>>() val treesSet = getAllTreesStatement.executeQuery() while (treesSet.next()) { - list.add(Pair(treesSet.getString("name"), treesSet.getString("type"))) + list.add( + Triple( + treesSet.getString("name"), + treesSet.getString("type"), + Pair(treesSet.getFloat("x"), treesSet.getFloat("y")) + ) + ) } return list From 5c4e39797a7ab39518f79d0e83a3a88ef057f710 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 13:18:08 +0300 Subject: [PATCH 134/164] refactor(controller): change Pair(x, y) to x and y in DrawNode --- app/src/main/kotlin/app/Controller.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 3a45f7b..94ab540 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -71,8 +71,10 @@ object Controller { open class DrawNode( var key: String, var value: String, - var coordinates: MutableState>, - var prevCoordinates: MutableState?> + var x: Float, + var y: Float, + var prevX: Float?, + var prevY: Float? ) class DrawTree { @@ -102,8 +104,10 @@ object Controller { DrawNode( data.first, data.second.first, - mutableStateOf(data.second.second), - mutableStateOf(data.third?.second) + data.second.second.first, + data.second.second.second, + data.third?.second?.first, + data.third?.second?.second ) } @@ -143,7 +147,7 @@ object Controller { fun drawFind(key: String) = tree.get(key)?.first fun updateCoordinate(node: DrawNode) { - tree.insert(node.key, Pair(node.value, node.coordinates.value)) + tree.insert(node.key, Pair(node.value, Pair(node.x, node.y))) } fun saveToDB(databaseType: DatabaseType) { From 3ae58a66783abf3dc97237a4db7727954bfca2b5 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 13:58:35 +0300 Subject: [PATCH 135/164] refactor(controller): make x, y, prevX, prevY mutable state --- app/src/main/kotlin/app/Controller.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 94ab540..e7461f7 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -71,10 +71,10 @@ object Controller { open class DrawNode( var key: String, var value: String, - var x: Float, - var y: Float, - var prevX: Float?, - var prevY: Float? + var x: MutableState, + var y: MutableState, + var prevX: MutableState, + var prevY: MutableState ) class DrawTree { @@ -104,10 +104,10 @@ object Controller { DrawNode( data.first, data.second.first, - data.second.second.first, - data.second.second.second, - data.third?.second?.first, - data.third?.second?.second + mutableStateOf(data.second.second.first), + mutableStateOf(data.second.second.second), + mutableStateOf(data.third?.second?.first), + mutableStateOf(data.third?.second?.second) ) } @@ -147,7 +147,7 @@ object Controller { fun drawFind(key: String) = tree.get(key)?.first fun updateCoordinate(node: DrawNode) { - tree.insert(node.key, Pair(node.value, Pair(node.x, node.y))) + tree.insert(node.key, Pair(node.value, Pair(node.x.value, node.y.value))) } fun saveToDB(databaseType: DatabaseType) { From f0733bf2c69b6b616ea2fb0ceb346f40feec2fa7 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 16:31:24 +0300 Subject: [PATCH 136/164] feat(ui): fix the work and make the loading of trees beautiful --- app/src/main/kotlin/app/Color.kt | 4 ++ app/src/main/kotlin/app/OpenTree.kt | 83 +++++++++++++++++++++------ app/src/main/kotlin/dataBase/Neo4j.kt | 19 ++++-- 3 files changed, 83 insertions(+), 23 deletions(-) diff --git a/app/src/main/kotlin/app/Color.kt b/app/src/main/kotlin/app/Color.kt index ce0278d..0e3e905 100644 --- a/app/src/main/kotlin/app/Color.kt +++ b/app/src/main/kotlin/app/Color.kt @@ -1,8 +1,11 @@ package UIT + import androidx.compose.ui.graphics.Color val md_theme_light_primary = Color(0xFF00687C) val md_theme_light_onPrimary = Color(0xFFFFFFFF) +val md_theme_light_negative_primary = Color(0xFFA44F45) +val md_theme_light_border = Color(0xFFf1f7ff) val md_theme_light_primaryContainer = Color(0xFFB0ECFF) val md_theme_light_onPrimaryContainer = Color(0xFF001F27) val md_theme_light_secondary = Color(0xFF4B6269) @@ -34,6 +37,7 @@ val md_theme_light_scrim = Color(0xFF000000) val md_theme_dark_primary = Color(0xFF57D6F6) val md_theme_dark_onPrimary = Color(0xFF003641) +val md_theme_dark_negative_primary = Color(0xFF8E2417) val md_theme_dark_primaryContainer = Color(0xFF004E5E) val md_theme_dark_onPrimaryContainer = Color(0xFFB0ECFF) val md_theme_dark_secondary = Color(0xFFB2CBD3) diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/OpenTree.kt index 213e62b..3dc1419 100644 --- a/app/src/main/kotlin/app/OpenTree.kt +++ b/app/src/main/kotlin/app/OpenTree.kt @@ -1,20 +1,24 @@ package app -import UIT.md_theme_light_primary +import UIT.* +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex @Composable fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { - var files = listOf(Pair("", "")) - var dataBaseType: Controller.DatabaseType = Controller.DatabaseType.Json + var files = remember { mutableStateOf(mutableStateListOf>>()) } + var dataBaseType = Controller.DatabaseType.Json MaterialTheme { Column( modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), @@ -38,7 +42,8 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Button( onClick = { - files = Controller.Database(Controller.DatabaseType.Json).getAllTrees() + files.value = + Controller.Database(Controller.DatabaseType.Json).getAllTrees().toMutableStateList() dataBaseType = Controller.DatabaseType.Json }, shape = MaterialTheme.shapes.extraLarge, @@ -55,7 +60,8 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - files = Controller.Database(Controller.DatabaseType.SQLite).getAllTrees() + files.value = + Controller.Database(Controller.DatabaseType.SQLite).getAllTrees().toMutableStateList() dataBaseType = Controller.DatabaseType.SQLite }, shape = MaterialTheme.shapes.extraLarge, @@ -72,7 +78,8 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - files = Controller.Database(Controller.DatabaseType.Neo4j).getAllTrees() + files.value = + Controller.Database(Controller.DatabaseType.Neo4j).getAllTrees().toMutableStateList() dataBaseType = Controller.DatabaseType.Neo4j }, shape = MaterialTheme.shapes.extraLarge, @@ -86,36 +93,74 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { ) } } - Spacer(modifier = Modifier.height(15.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - LazyColumn { - items(files) { file -> - Row { - Text( - text = "name: \"${file.first}\" type: ${file.second}" - ) - Spacer(modifier = Modifier.width(15.dp)) + + Spacer(modifier = Modifier.height(20.dp)) + + if (files.value.isNotEmpty()) { + + Button( + onClick = { + Controller.Database(dataBaseType).clean() + files.value = mutableStateListOf() + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.height(50.dp).fillMaxWidth(0.95f), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_negative_primary + ) + ) { + Text("delete all trees saved in $dataBaseType") + } + Spacer(modifier = Modifier.height(10.dp)) + } + + LazyColumn { + items(files.value) { file -> + Spacer(modifier = Modifier.width(20.dp)) + Box( + modifier = Modifier.fillMaxWidth(0.95f) + .zIndex(0f) + .border(4.dp, md_theme_light_border, RoundedCornerShape(20.dp)) + ) { + Row( + modifier = Modifier.fillMaxSize() + .zIndex(1f), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Button( onClick = { onClick(Controller.DrawTree(file.first, dataBaseType)) }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), + modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), colors = ButtonDefaults.buttonColors( containerColor = md_theme_light_primary ) ) { Text("Open") } - Spacer(modifier = Modifier.width(15.dp)) + + Text( + modifier = Modifier.weight(6f), + textAlign = TextAlign.Center, + text = "name: \"${file.first}\"" + ) + Text( + modifier = Modifier.weight(3f), + textAlign = TextAlign.Center, + text = file.second + ) + Button( onClick = { Controller.Database(dataBaseType).removeTree(file.first) + files.value.remove(file) }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), + modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = md_theme_light_negative_primary ) ) { Text("Delete") diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index a78e8ea..f0f3b21 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -122,11 +122,22 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { override fun getAllTrees(): List>> { val list = mutableListOf>>() - session.executeRead { tx -> - val result = tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type, tree.viewX AS x, tree.viewY AS y") - result.stream().forEach { - list.add(Triple(it["name"].asString(), it["type"].asString(), Pair(it["x"].asFloat(), it["y"].asFloat()))) + try { + session.executeRead { tx -> + val result = + tx.run("OPTIONAL MATCH (tree: Tree) RETURN tree.name AS name, tree.type AS type, tree.viewX AS x, tree.viewY AS y") + result.stream().forEach { + list.add( + Triple( + it["name"].asString(), + it["type"].asString(), + Pair(it["x"].asFloat(), it["y"].asFloat()) + ) + ) + } } + } catch (ex: Exception) { + throw IOException("Cannot get trees from Neo4j\nCheck that the database is active and all data is entered correctly\n$ex") } return list } From b93d72b03f8b6e3c5f228c96d3992a44beb68941 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 17:06:11 +0300 Subject: [PATCH 137/164] fix(database): add connection check to init --- app/src/main/kotlin/dataBase/Neo4j.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index f0f3b21..37bee9b 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -16,6 +16,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { init { try { driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)) + driver.verifyConnectivity() session = driver.session() } catch (ex: Exception) { throw IOException("can't start session, try to change uri, user name or password\n$ex") From ef7ef34a01d21eb3330b797ebddb919138d74abd Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 17:58:25 +0300 Subject: [PATCH 138/164] ci(refactor): rename gradle -> gradle build, delete grand execute permission for gradlew --- .github/workflows/{gradle.yml => gradle build.yml} | 2 -- 1 file changed, 2 deletions(-) rename .github/workflows/{gradle.yml => gradle build.yml} (89%) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle build.yml similarity index 89% rename from .github/workflows/gradle.yml rename to .github/workflows/gradle build.yml index bb0ca29..89b75d8 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle build.yml @@ -28,7 +28,5 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: ${{ runner.os }}-gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build From 86a3c2886af20deffedf4ad2834cd5fab6ff3af7 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 18:06:20 +0300 Subject: [PATCH 139/164] ci(refactor): make files executable --- gradlew | 0 gradlew.bat | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew mode change 100644 => 100755 gradlew.bat diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 From 5d2d493156af87f93d5929832f6e6f9bc0f93960 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 18:15:07 +0300 Subject: [PATCH 140/164] refactor(gitignore): clean gitignore --- .gitignore | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index fd4e6bd..9deb9f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -# CMake -cmake-build-*/ +/.idea/ + +/.gradle/ + +build-logic/.gradle/ + +**/build # File-based project format *.iws @@ -16,9 +21,6 @@ crashlytics.properties crashlytics-build.properties fabric.properties -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - ### Kotlin ### # Compiled class file *.class @@ -104,15 +106,3 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk - -/.idea/ - -/.gradle/ - -build-logic/build/ - -build-logic/.gradle/ - -lib/build/ - -app/build/ From f817979e4780ce126d52378cb6a9449878b99bf6 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 20:04:13 +0300 Subject: [PATCH 141/164] feat(ui): add error message for user input --- app/src/main/kotlin/app/Controller.kt | 10 ++++---- app/src/main/kotlin/app/CreatNewWindow.kt | 30 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index e7461f7..3b7498d 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -34,12 +34,12 @@ object Controller { fun validateName(name: String) { for (i in name) if (i !in 'a'..'z' && i !in 'A'..'Z' && i !in '0'..'9') - throw IllegalArgumentException("Unsupported tree name, please use only ascii letters or digits") - if (name[0] in '0'..'9') - throw IllegalArgumentException("Unsupported tree name, please don't use a digit as the first char") + throw IllegalArgumentException("Please use only ascii letters or digits") + if (name.isNotEmpty() && name[0] in '0'..'9') + throw IllegalArgumentException("Please don't use a digit as the first char") if (name.length !in 1..System.getProperty("max_string_len") .toInt() - ) throw IllegalArgumentException("Incorrect tree name\nThe name must be less than ${System.getProperty("max_string_len")} and greater than 0") + ) throw IllegalArgumentException("The name must be less than ${System.getProperty("max_string_len")} and greater than 0") } fun getTree(treeType: TreeType) = when (treeType) { @@ -73,7 +73,7 @@ object Controller { var value: String, var x: MutableState, var y: MutableState, - var prevX: MutableState, + var prevX: MutableState, var prevY: MutableState ) diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index fa03864..ecce3a7 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -8,10 +8,24 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp - @Composable fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { var name by remember { mutableStateOf("") } + val inputError = remember { mutableStateOf(false) } + val error: MutableState = remember { mutableStateOf(null) } + + fun isNameValid() { + try { + Controller.validateName(name) + } catch (ex: Exception) { + inputError.value = true + error.value = ex.message + return + } + inputError.value = false + error.value = null + } + MaterialTheme { Column( modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), @@ -19,11 +33,11 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { horizontalAlignment = Alignment.CenterHorizontally ) { Row(verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( value = name, - onValueChange = { name = it }, + onValueChange = { isNameValid(); name = it }, label = { Text(text = "name") }, + isError = inputError.value, singleLine = true, modifier = Modifier.weight(0.70f), shape = MaterialTheme.shapes.extraLarge, @@ -31,6 +45,12 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { } Spacer(modifier = Modifier.height(15.dp)) + if (error.value != null) + Text( + text = error.value.toString() + ) + + Spacer(modifier = Modifier.height(15.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Button( @@ -46,6 +66,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { ) } Spacer(modifier = Modifier.width(16.dp)) + Button( onClick = { val tree = Controller.DrawTree(name, Controller.TreeType.BSTree) @@ -99,7 +120,6 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { ) } } - } } -} \ No newline at end of file +} From 55d3b556aa6ca019365fcbad9a70cf5406459fd8 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 20:41:13 +0300 Subject: [PATCH 142/164] refactor(controller): Now the node does not have the coordinates of the parent, but a link to it --- app/src/main/kotlin/app/Controller.kt | 30 +++++++++++++++++---------- lib/src/main/kotlin/trees/BinTree.kt | 6 +++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 3b7498d..0daac61 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -73,8 +73,7 @@ object Controller { var value: String, var x: MutableState, var y: MutableState, - var prevX: MutableState, - var prevY: MutableState + var parent: DrawNode? ) class DrawTree { @@ -99,18 +98,27 @@ object Controller { tree = getTree(treeType) } - fun getAllDrawNodes() = - tree.getNodesDataWithParentValue().map { data -> - DrawNode( - data.first, - data.second.first, - mutableStateOf(data.second.second.first), - mutableStateOf(data.second.second.second), - mutableStateOf(data.third?.second?.first), - mutableStateOf(data.third?.second?.second) + fun getAllDrawNodes(): MutableList { + val listOfDrawNodes = mutableListOf() + val mapOfKeysNodes = mutableMapOf>() + for (i in tree.getNodesDataWithParentKeys().reversed()) { + val node = DrawNode( + i.first, + i.second.first, + mutableStateOf(i.second.second.first), + mutableStateOf(i.second.second.second), + parent = null ) + listOfDrawNodes.add(node) + if (mapOfKeysNodes[i.third] == null) + mapOfKeysNodes[i.third] = mutableListOf(node) + else mapOfKeysNodes[i.third]?.add(node) + mapOfKeysNodes[i.first]?.forEach { it.parent = node } } + return listOfDrawNodes + } + private fun rewriteAllCoordinates() { fun offsetOnLevel(level: Int, height: Int) = ((height - 2) * xMinInterval * (0.5.pow(level) - 1) * (-2)).toFloat() //the sum of the terms of the geometric progression diff --git a/lib/src/main/kotlin/trees/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt index 9ba9c5c..0af7ca0 100644 --- a/lib/src/main/kotlin/trees/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -228,9 +228,9 @@ abstract class BinTree, Value> : Tree { * @return all key, value of all nodes in the tree with value of its parent (null if parent doesn't exist). * In order from left to right, by level. */ - fun getNodesDataWithParentValue(): List> { - val list = mutableListOf>() - breadthFirstSearch { node -> if (node != null) list.add(Triple(node.key, node.value, node.parent?.value)) } + fun getNodesDataWithParentKeys(): MutableList> { + val list = mutableListOf>() + breadthFirstSearch { node -> if (node != null) list.add(Triple(node.key, node.value, node.parent?.key)) } return list } From 1f4953a8373a98240350b143f600aca0b1c13ac2 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 20:51:24 +0300 Subject: [PATCH 143/164] feat: add reInitAllDrawNodes --- app/src/main/kotlin/app/Controller.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index 0daac61..c01a5c0 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -83,8 +83,9 @@ object Controller { var startCoordinate = Pair(0F, 0F) //coordinates of the root node - var xMinInterval = 4F //interval between nodes - var yInterval = 4F //interval between nodes + var xMinInterval = 100F //interval between nodes + var yInterval = -100F //interval between nodes + var content = mutableStateOf(listOf()) constructor(treeName: String, databaseType: DatabaseType) { this.treeName = treeName @@ -119,6 +120,10 @@ object Controller { return listOfDrawNodes } + fun reInitAllDrawNodes(){ + content.value = getAllDrawNodes() + } + private fun rewriteAllCoordinates() { fun offsetOnLevel(level: Int, height: Int) = ((height - 2) * xMinInterval * (0.5.pow(level) - 1) * (-2)).toFloat() //the sum of the terms of the geometric progression From 8f323dc08f40c6721e283fad1d97cf37d4f01a9e Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 20:54:10 +0300 Subject: [PATCH 144/164] fix: drawTree --- app/src/main/kotlin/app/ViewTree.kt | 127 ++++++++++++++++------------ 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ViewTree.kt index 08ccdc1..6d891b3 100644 --- a/app/src/main/kotlin/app/ViewTree.kt +++ b/app/src/main/kotlin/app/ViewTree.kt @@ -1,27 +1,20 @@ package app -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background +import androidx.compose.foundation.* import androidx.compose.foundation.gestures.detectDragGestures -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.boundsInParent -import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -32,67 +25,89 @@ import kotlin.math.roundToInt open class ViewTree { @Composable - open fun drawTree(tree: Controller.DrawTree ) { - tree.getAllDrawNodes().forEach { node -> - drawNode(node.key, node.coordinates)} -// node.prevCoordinates.value?.let { -// drawLine(node.coordinates, node.prevCoordinates) -// } -// } + open fun drawTree( + tree: Controller.DrawTree, + ) { + Box(contentAlignment = Alignment.TopCenter, + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize() + .clipToBounds() + .padding(top = 15.dp) + ) { + Box(modifier = Modifier.size(50.dp)) { + tree.content.value.forEach() { node -> + drawNode(node, 50) + node.parent?.let { + drawLine(node, it, 50) + } + } + } + } } + + @OptIn(ExperimentalFoundationApi::class) @Composable fun drawNode( - key: String, - coordinates: MutableState> + node: Controller.DrawNode, + size: Int, ) { - val offsetX = remember { mutableStateOf(coordinates.value.first) } - val offsetY = remember { mutableStateOf(coordinates.value.second) } + Box(modifier = Modifier.fillMaxSize()) { + TooltipArea( + tooltip = { + Surface { + Text( + text = "value: ${node.value} \nx: ${node.x.value} \ny: ${node.y.value}" + ) + } + }, + modifier = Modifier + .offset { + IntOffset( + x = node.x.value.roundToInt(), + y = node.y.value.roundToInt(), + ) + } + .pointerInput(node.x, node.y) { + detectDragGestures { _, dragAmount -> + node.x.value += dragAmount.x + node.y.value += dragAmount.y + } + } - Box( - modifier = Modifier - .offset { - IntOffset( - x = offsetX.value.roundToInt(), - y = offsetY.value.roundToInt() + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(size.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.primary) + ) { + Text( + text = node.key, + fontSize = 10.sp, + color = Color.White, + fontWeight = FontWeight.Bold ) } - .pointerInput(Unit) { - detectDragGestures { _, dragAmount -> - offsetX.value += dragAmount.x - offsetY.value += dragAmount.y - //coordinates.value = Pair(offsetX.value, offsetY.value) - } - } - .size(60.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.primary) - .onGloballyPositioned { layoutCoordinates -> - val rect = layoutCoordinates.boundsInParent() - coordinates.value = Pair(rect.center.x, rect.center.y) - }, - contentAlignment = Alignment.Center - ) { - Text( - text = key, - fontSize = 20.sp, - color = Color.White, - fontWeight = FontWeight.Bold - ) + + } } } @Composable - fun drawLine(coordinates:MutableState>, prevCoordinates: MutableState>) { + fun drawLine( + node: Controller.DrawNode, parent : Controller.DrawNode, size: Int = 50 + ) { Canvas(modifier = Modifier.fillMaxSize().zIndex(-1f)) { drawLine( color = Color.DarkGray, - start = Offset(coordinates.value.first,coordinates.value.second), - end = Offset(prevCoordinates.value.first, prevCoordinates.value.second), - strokeWidth = 5f + start = Offset(node.x.value + size/2, node.y.value + size/2), + end = Offset(parent.x.value + size/2, parent.y.value + size/2), + strokeWidth = 3f ) } } } - From 40361be92019392d8b142048db5c0cbced3e8e7a Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 20:55:07 +0300 Subject: [PATCH 145/164] feat: save dialog box --- app/src/main/kotlin/app/TreeWindow.kt | 396 ++++++++++++++++---------- 1 file changed, 238 insertions(+), 158 deletions(-) diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 6d5fde9..75588d4 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -3,6 +3,8 @@ package app import UIT.md_theme_light_primary import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.material.AlertDialog +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -10,213 +12,291 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +@OptIn(ExperimentalMaterialApi::class) @Composable fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { var textForUser by remember { mutableStateOf("") } + val openDialog = remember { mutableStateOf(false) } + MaterialTheme { - Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { - Column( - modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(400.dp) - ) { - Insert( - onClick = { key, value -> - if ((key != "") && (value != "")) { - tree.drawInsert(key, value) - textForUser = "I insert node with key = $key and value = $value :)" - } - else { - textForUser = "Give me key and value pls :(" - } + Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { + Column( + modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(400.dp) + ) { + Insert( + onClick = { key, value -> + if ((key != "") && (value != "")) { + tree.drawInsert(key, value) + tree.reInitAllDrawNodes() + textForUser = "I insert node with key: $key and value: $value :)" + } else { + textForUser = "Give me key and value pls :(" } - ) - Remove( - onClick = { key -> - if (key != "") { - tree.drawRemove(key) - textForUser = "I remove node :)" - } - else { - textForUser = "Give me key pls :(" - } + } + ) + Remove( + onClick = { key -> + if (key != "") { + tree.drawRemove(key) + tree.reInitAllDrawNodes() + textForUser = "I remove node :)" + } else { + textForUser = "Give me key pls :(" } - ) - Find( - onClick = { key -> - val value = tree.drawFind(key) - if (value != null) { - textForUser = "Result: $value" - } - else { - textForUser = "I can't find node :(" - } + } + ) + Find( + onClick = { key -> + val value = tree.drawFind(key) + if (value != null) { + textForUser = "Result: $value" + } else { + textForUser = "Ooops... I can't find node :(" } - ) - Spacer(modifier = Modifier.height(15.dp)) - Text(text = textForUser, - modifier = Modifier.padding(start = 32.dp, top = 16.dp), - style = MaterialTheme.typography.headlineSmall, + } + ) + Spacer(modifier = Modifier.height(15.dp)) + Text( + text = textForUser, + modifier = Modifier.padding(start = 32.dp, top = 16.dp), + style = MaterialTheme.typography.headlineSmall, + ) + Row( + modifier = Modifier.height(570.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.aligned(Alignment.End) + ) { + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + openDialog.value = true + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = "Save", ) - Row(modifier = Modifier.height(570.dp), + } + } + } + ViewTree().drawTree(tree) + } + if (openDialog.value) { + AlertDialog( + onDismissRequest = { openDialog.value = false }, + title = { + Text(text = "How do you want to save this tree?") + }, + text = { + Text("Select the database to save:") + }, + buttons = { + Row( + modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.aligned(Alignment.End)) { + horizontalArrangement = Arrangement.aligned(Alignment.CenterHorizontally) + ) { Button( - onClick = onBack, + onClick = { openDialog.value = false }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f), + modifier = Modifier.weight(0.3f).height(57.dp), colors = ButtonDefaults.buttonColors( containerColor = md_theme_light_primary ) ) { - Text( - text = "Exit", + Text("Exit") + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + tree.saveToDB(Controller.DatabaseType.Json) + openDialog.value = false + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary ) + ) { + Text("Json") } Spacer(modifier = Modifier.width(16.dp)) + Button( - onClick = { }, + onClick = { + tree.saveToDB(Controller.DatabaseType.SQLite) + openDialog.value = false + }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f), + modifier = Modifier.weight(0.3f).height(57.dp), colors = ButtonDefaults.buttonColors( containerColor = md_theme_light_primary ) ) { - Text( - text = "Save", + Text("SQLite") + } + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + tree.saveToDB(Controller.DatabaseType.Neo4j) + openDialog.value = false + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary ) + ) { + Text("Neo4j") } } } - Box ( - modifier = Modifier - .fillMaxSize().padding(start = 200.dp, end = 30.dp), - ) { - ViewTree().drawTree(tree) - } - } - } + ) } + } +} - @Composable - fun Insert(onClick: (key: String, value: String) -> Unit) { - Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { - Text(text = "Insert:", style = MaterialTheme.typography.headlineSmall) - Spacer(modifier = Modifier.height(7.dp)) +@Composable +fun Insert(onClick: (key: String, value: String) -> Unit) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Insert:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - var textValue by remember { mutableStateOf("") } + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + var textValue by remember { mutableStateOf("") } - OutlinedTextField( - value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key") }, - singleLine = true, - modifier = Modifier.weight(0.30f), - shape = MaterialTheme.shapes.extraLarge, - ) + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, + ) - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - OutlinedTextField( - value = textValue, - onValueChange = { textValue = it }, - label = { Text(text = "value") }, - singleLine = true, - modifier = Modifier.weight(0.30f), - shape = MaterialTheme.shapes.extraLarge, - ) + OutlinedTextField( + value = textValue, + onValueChange = { textValue = it }, + label = { Text(text = "value") }, + singleLine = true, + modifier = Modifier.weight(0.30f), + shape = MaterialTheme.shapes.extraLarge, + ) - Spacer(modifier = Modifier.width(16.dp)) - - Button( - onClick = { - onClick(textKey, textValue) - textKey = "" - textValue = "" - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("go!") - } + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + onClick(textKey, textValue) + textKey = "" + textValue = "" + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go!") } } } +} - @Composable - fun Remove(onClick: (key: String) -> Unit) { - Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { - Text(text = "Remove:", style = MaterialTheme.typography.headlineSmall) - Spacer(modifier = Modifier.height(7.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - - OutlinedTextField( - value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key") }, - singleLine = true, - modifier = Modifier.weight(0.70f), - shape = MaterialTheme.shapes.extraLarge, - ) +@Composable +fun Remove(onClick: (key: String) -> Unit) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Remove:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) - Spacer(modifier = Modifier.width(16.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, + ) - Button( - onClick = { - onClick(textKey) - textKey = "" }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("go!") - } + Spacer(modifier = Modifier.width(16.dp)) + + + Button( + onClick = { + onClick(textKey) + textKey = "" + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go!") } } } +} - @Composable - fun Find(onClick: (key: String) -> Unit) { - Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { - Text(text = "Find:", style = MaterialTheme.typography.headlineSmall) - Spacer(modifier = Modifier.height(7.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - var textKey by remember { mutableStateOf("") } - - OutlinedTextField( - value = textKey, - onValueChange = { textKey = it }, - label = { Text(text = "key") }, - singleLine = true, - modifier = Modifier.weight(0.70f), - shape = MaterialTheme.shapes.extraLarge, - ) +@Composable +fun Find(onClick: (key: String) -> Unit) { + Column(modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(300.dp)) { + Text(text = "Find:", style = MaterialTheme.typography.headlineSmall) + Spacer(modifier = Modifier.height(7.dp)) - Spacer(modifier = Modifier.width(16.dp)) - - Button( - onClick = { - onClick(textKey) - textKey = "" }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.30f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("go!") - } + Row(verticalAlignment = Alignment.CenterVertically) { + var textKey by remember { mutableStateOf("") } + + OutlinedTextField( + value = textKey, + onValueChange = { textKey = it }, + label = { Text(text = "key") }, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Button( + onClick = { + onClick(textKey) + textKey = "" + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.30f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go!") } } } +} + From 2ca16cd083290b56514230aead96f944ca3ecd88 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 21:09:32 +0300 Subject: [PATCH 146/164] feat(app): add drag in app --- app/src/main/kotlin/app/TreeWindow.kt | 4 +++- app/src/main/kotlin/app/ViewTree.kt | 29 ++++++++++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 75588d4..9d3ca70 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -18,6 +18,8 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { var textForUser by remember { mutableStateOf("") } val openDialog = remember { mutableStateOf(false) } + val offSetX = remember { mutableStateOf(0f) } + val offSetY = remember { mutableStateOf(0f) } MaterialTheme { Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { Column( @@ -95,7 +97,7 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { } } } - ViewTree().drawTree(tree) + ViewTree().drawTree(tree, offSetX, offSetY) } if (openDialog.value) { AlertDialog( diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ViewTree.kt index 6d891b3..97f37ef 100644 --- a/app/src/main/kotlin/app/ViewTree.kt +++ b/app/src/main/kotlin/app/ViewTree.kt @@ -27,6 +27,8 @@ open class ViewTree { @Composable open fun drawTree( tree: Controller.DrawTree, + offSetX: MutableState, + offSetY: MutableState ) { Box(contentAlignment = Alignment.TopCenter, modifier = Modifier @@ -34,12 +36,19 @@ open class ViewTree { .fillMaxSize() .clipToBounds() .padding(top = 15.dp) + .pointerInput(offSetX, offSetY) { + detectDragGestures { _, dragAmount -> + offSetX.value += dragAmount.x + offSetY.value += dragAmount.y + } + } + ) { Box(modifier = Modifier.size(50.dp)) { tree.content.value.forEach() { node -> - drawNode(node, 50) + drawNode(node, 50, offSetX, offSetY) node.parent?.let { - drawLine(node, it, 50) + drawLine(node, it, 50, offSetX, offSetY) } } } @@ -52,6 +61,8 @@ open class ViewTree { fun drawNode( node: Controller.DrawNode, size: Int, + offSetX: MutableState, + offSetY: MutableState ) { Box(modifier = Modifier.fillMaxSize()) { @@ -66,8 +77,8 @@ open class ViewTree { modifier = Modifier .offset { IntOffset( - x = node.x.value.roundToInt(), - y = node.y.value.roundToInt(), + x = (node.x.value + offSetX.value).roundToInt(), + y = (node.y.value + offSetY.value).roundToInt(), ) } .pointerInput(node.x, node.y) { @@ -99,13 +110,17 @@ open class ViewTree { @Composable fun drawLine( - node: Controller.DrawNode, parent : Controller.DrawNode, size: Int = 50 + node: Controller.DrawNode, + parent : Controller.DrawNode, + size: Int = 50, + offSetX: MutableState, + offSetY: MutableState ) { Canvas(modifier = Modifier.fillMaxSize().zIndex(-1f)) { drawLine( color = Color.DarkGray, - start = Offset(node.x.value + size/2, node.y.value + size/2), - end = Offset(parent.x.value + size/2, parent.y.value + size/2), + start = Offset(node.x.value + size/2 + offSetX.value, node.y.value + size/2 + offSetY.value), + end = Offset(parent.x.value + size/2 + offSetX.value, parent.y.value + size/2 + offSetY.value), strokeWidth = 3f ) } From 1b3244f6b143dd939d3feea4466396b384f71590 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Tue, 2 May 2023 21:42:56 +0300 Subject: [PATCH 147/164] feat(ui): rework creating new tree window --- app/src/main/kotlin/app/Controller.kt | 6 ++ app/src/main/kotlin/app/CreatNewWindow.kt | 116 +++++++++++++++------- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index c01a5c0..eb2601a 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -25,6 +25,12 @@ object Controller { BSTree } + enum class KeysType { + Int, + Float, + String + } + enum class DatabaseType { Json, Neo4j, diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index ecce3a7..a700f80 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -2,6 +2,8 @@ package app import UIT.md_theme_light_primary import androidx.compose.foundation.layout.* +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -11,18 +13,17 @@ import androidx.compose.ui.unit.dp @Composable fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { var name by remember { mutableStateOf("") } - val inputError = remember { mutableStateOf(false) } val error: MutableState = remember { mutableStateOf(null) } + val treeType = remember { mutableStateOf(Controller.TreeType.BSTree) } + val keysType = remember { mutableStateOf(Controller.KeysType.Int) } fun isNameValid() { try { Controller.validateName(name) } catch (ex: Exception) { - inputError.value = true error.value = ex.message return } - inputError.value = false error.value = null } @@ -35,9 +36,9 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Row(verticalAlignment = Alignment.CenterVertically) { OutlinedTextField( value = name, - onValueChange = { isNameValid(); name = it }, + onValueChange = { name = it; isNameValid(); }, label = { Text(text = "name") }, - isError = inputError.value, + isError = error.value != null, singleLine = true, modifier = Modifier.weight(0.70f), shape = MaterialTheme.shapes.extraLarge, @@ -52,7 +53,9 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Spacer(modifier = Modifier.height(15.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Button( onClick = onBack, shape = MaterialTheme.shapes.extraLarge, @@ -67,43 +70,84 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { } Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - val tree = Controller.DrawTree(name, Controller.TreeType.BSTree) - name = "" - onClick(tree) - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "BSTree", - ) + Box { + val expanded = remember { mutableStateOf(false) } + + Button( + onClick = { + expanded.value = true + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = treeType.value.toString(), + ) + } + + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } + ) { + DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.BSTree; expanded.value = false }) { + Text("binary search tree") + } + Divider() + DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.AVLTree; expanded.value = false }) { + Text("AVL tree") + } + Divider() + DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.RBTree; expanded.value = false }) { + Text("red-black tree") + } + } } + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - val tree = Controller.DrawTree(name, Controller.TreeType.AVLTree) - name = "" - onClick(tree) - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "AVLTree", - ) + Box { + val expanded = remember { mutableStateOf(false) } + + Button( + onClick = { + expanded.value = true + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text( + text = keysType.value.toString(), + ) + } + + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } + ) { + DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.Int; expanded.value = false }) { + Text("Int keys") + } + Divider() + DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.Float; expanded.value = false }) { + Text("Float keys") + } + Divider() + DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.String; expanded.value = false }) { + Text("String keys") + } + } } + Spacer(modifier = Modifier.width(16.dp)) Button( + enabled = error.value == null && name.isNotEmpty(), onClick = { val tree = Controller.DrawTree(name, Controller.TreeType.RBTree) name = "" @@ -116,7 +160,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { ) ) { Text( - text = "RBTree", + text = "Create", ) } } From 2edd2c638dcea2c1059a2be752ff8bbfc680116c Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 22:14:22 +0300 Subject: [PATCH 148/164] feat(app): add button "go to tree Root!" --- app/src/main/kotlin/app/TreeWindow.kt | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 9d3ca70..7fe3f54 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -1,5 +1,6 @@ package app +import UIT.md_theme_light_negative_primary import UIT.md_theme_light_primary import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -20,6 +21,8 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { val offSetX = remember { mutableStateOf(0f) } val offSetY = remember { mutableStateOf(0f) } + + MaterialTheme { Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { Column( @@ -63,8 +66,9 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { modifier = Modifier.padding(start = 32.dp, top = 16.dp), style = MaterialTheme.typography.headlineSmall, ) + Spacer(modifier = Modifier.height(40.dp)) Row( - modifier = Modifier.height(570.dp), + modifier = Modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.aligned(Alignment.End) ) { @@ -73,7 +77,7 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = md_theme_light_negative_primary ) ) { Text( @@ -96,6 +100,20 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { ) } } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { + offSetX.value = 0f + offSetY.value = 0f + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.width(400.dp), + colors = ButtonDefaults.buttonColors( + containerColor = md_theme_light_primary + ) + ) { + Text("go to tree Root!") + } } ViewTree().drawTree(tree, offSetX, offSetY) } From fe7e56bdfdfeb338a7f7c899cf2faeeb3b613dd9 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 22:27:17 +0300 Subject: [PATCH 149/164] fix(app): change buttons size in CreatNewTree window --- app/src/main/kotlin/app/CreatNewWindow.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index a700f80..c13dc52 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -70,7 +70,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { } Spacer(modifier = Modifier.width(16.dp)) - Box { + Box(modifier = Modifier.weight(0.3f)) { val expanded = remember { mutableStateOf(false) } Button( @@ -78,7 +78,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { expanded.value = true }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.height(57.dp), + modifier = Modifier.fillMaxWidth().height(57.dp), colors = ButtonDefaults.buttonColors( containerColor = md_theme_light_primary ) @@ -93,7 +93,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { onDismissRequest = { expanded.value = false } ) { DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.BSTree; expanded.value = false }) { - Text("binary search tree") + Text("Binary search tree") } Divider() DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.AVLTree; expanded.value = false }) { @@ -101,14 +101,14 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { } Divider() DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.RBTree; expanded.value = false }) { - Text("red-black tree") + Text("Red-black tree") } } } Spacer(modifier = Modifier.width(16.dp)) - Box { + Box(modifier = Modifier.weight(0.3f)) { val expanded = remember { mutableStateOf(false) } Button( @@ -116,7 +116,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { expanded.value = true }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.height(57.dp), + modifier = Modifier.fillMaxWidth().height(57.dp), colors = ButtonDefaults.buttonColors( containerColor = md_theme_light_primary ) From 93b11c7678794e3a7e97fb25a5140362578468f8 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Tue, 2 May 2023 22:27:52 +0300 Subject: [PATCH 150/164] fix(app): Open function --- app/src/main/kotlin/app/TreeWindow.kt | 1 + app/src/main/kotlin/app/ViewTree.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 7fe3f54..8b270d8 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -22,6 +22,7 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { val offSetX = remember { mutableStateOf(0f) } val offSetY = remember { mutableStateOf(0f) } + tree.reInitAllDrawNodes() MaterialTheme { Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ViewTree.kt index 97f37ef..303433c 100644 --- a/app/src/main/kotlin/app/ViewTree.kt +++ b/app/src/main/kotlin/app/ViewTree.kt @@ -70,7 +70,7 @@ open class ViewTree { tooltip = { Surface { Text( - text = "value: ${node.value} \nx: ${node.x.value} \ny: ${node.y.value}" + text = "value: ${node.value}" ) } }, From cc27e67dad745744ab248fb3cf88858d0abc7943 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 3 May 2023 00:11:19 +0300 Subject: [PATCH 151/164] refactor(controller): change key type to Int --- app/src/main/kotlin/app/Controller.kt | 49 ++++++++++++++++-------- app/src/main/kotlin/dataBase/DataBase.kt | 7 ++-- app/src/main/kotlin/dataBase/Json.kt | 9 +++-- app/src/main/kotlin/dataBase/Neo4j.kt | 12 +++--- app/src/main/kotlin/dataBase/SQLite.kt | 12 +++--- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/Controller.kt index eb2601a..2f4b054 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/Controller.kt @@ -37,6 +37,16 @@ object Controller { SQLite } + fun validKey(key: String) = run { + try { + key.toInt() + true + } + catch (ex: Exception) { + false + } + } + fun validateName(name: String) { for (i in name) if (i !in 'a'..'z' && i !in 'A'..'Z' && i !in '0'..'9') @@ -48,10 +58,15 @@ object Controller { ) throw IllegalArgumentException("The name must be less than ${System.getProperty("max_string_len")} and greater than 0") } - fun getTree(treeType: TreeType) = when (treeType) { - TreeType.BSTree -> BSTree>>() - TreeType.AVLTree -> AVLTree>>() - TreeType.RBTree -> RBTree>>() + fun getTree(treeType: TreeType, keysType: KeysType) = when(keysType) { + KeysType.Int -> Controller.getTree(treeType) + else -> throw IllegalArgumentException("Only Int support now") + } + + private fun > getTree(treeType: TreeType) = when (treeType) { + TreeType.BSTree -> BSTree>>() + TreeType.AVLTree -> AVLTree>>() + TreeType.RBTree -> RBTree>>() } fun getDatabase(databaseType: DatabaseType) = when (databaseType) { @@ -65,7 +80,7 @@ object Controller { DatabaseType.SQLite -> SQLite(System.getProperty("sqlite_path"), System.getProperty("max_string_len").toUInt()) } - class Database(private val databaseType: DatabaseType) { + class Database(databaseType: DatabaseType) { private val database = getDatabase(databaseType) fun getAllTrees() = database.getAllTrees() @@ -83,8 +98,10 @@ object Controller { ) class DrawTree { - private var tree: BinTree>> + private var tree: BinTree>> private var treeName: String + private var keysType: KeysType + var viewCoordinates = Pair(0F, 0F) var startCoordinate = Pair(0F, 0F) //coordinates of the root node @@ -98,19 +115,21 @@ object Controller { val treeData = getDatabase(databaseType).readTree(treeName) tree = treeData.first viewCoordinates = treeData.second + keysType = KeysType.String } - constructor(treeName: String, treeType: TreeType) { + constructor(treeName: String, treeType: TreeType, keysType: KeysType) { this.treeName = treeName - tree = getTree(treeType) + this.keysType = keysType + tree = getTree(treeType, keysType) } fun getAllDrawNodes(): MutableList { val listOfDrawNodes = mutableListOf() - val mapOfKeysNodes = mutableMapOf>() + val mapOfKeysNodes = mutableMapOf>() for (i in tree.getNodesDataWithParentKeys().reversed()) { val node = DrawNode( - i.first, + i.first.toString(), i.second.first, mutableStateOf(i.second.second.first), mutableStateOf(i.second.second.second), @@ -152,25 +171,25 @@ object Controller { } fun drawInsert(key: String, value: String) { - tree.insert(key, Pair(value, Pair(0F, 0F))) + tree.insert(key.toInt(), Pair(value, Pair(0F, 0F))) rewriteAllCoordinates() } fun drawRemove(key: String) { - tree.remove(key) + tree.remove(key.toInt()) rewriteAllCoordinates() } - fun drawFind(key: String) = tree.get(key)?.first + fun drawFind(key: String) = tree.get(key.toInt())?.first fun updateCoordinate(node: DrawNode) { - tree.insert(node.key, Pair(node.value, Pair(node.x.value, node.y.value))) + tree.insert(node.key.toInt(), Pair(node.value, Pair(node.x.value, node.y.value))) } fun saveToDB(databaseType: DatabaseType) { - getDatabase(databaseType).saveTree(treeName, tree, viewCoordinates) + getDatabase(databaseType).saveTree(treeName, tree, viewCoordinates,) } fun clean() { diff --git a/app/src/main/kotlin/dataBase/DataBase.kt b/app/src/main/kotlin/dataBase/DataBase.kt index 63973da..3c4666f 100644 --- a/app/src/main/kotlin/dataBase/DataBase.kt +++ b/app/src/main/kotlin/dataBase/DataBase.kt @@ -10,7 +10,8 @@ interface DataBase { is AVLTree -> true else -> false } - fun typeToTree(type: String): BinTree>> = when (type) { + + fun typeToTree(type: String): BinTree>> = when (type) { BSTree::class.simpleName -> BSTree() RBTree::class.simpleName -> RBTree() AVLTree::class.simpleName -> AVLTree() @@ -28,10 +29,10 @@ interface DataBase { fun saveTree( treeName: String, - tree: BinTree>>, + tree: BinTree>>, viewCoordinates: Pair ) - fun readTree(treeName: String): Pair>>, Pair> + fun readTree(treeName: String): Pair>>, Pair> fun removeTree(treeName: String) fun getAllTrees(): List>> fun clean() diff --git a/app/src/main/kotlin/dataBase/Json.kt b/app/src/main/kotlin/dataBase/Json.kt index 79fd16c..01a8348 100644 --- a/app/src/main/kotlin/dataBase/Json.kt +++ b/app/src/main/kotlin/dataBase/Json.kt @@ -24,13 +24,16 @@ class Json(private val saveDirPath: String) : DataBase { } override fun saveTree( - treeName: String, tree: BinTree>>, viewCoordinates: Pair + treeName: String, + tree: BinTree>>, + viewCoordinates: Pair ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") validateName(treeName) removeTree(treeName) + val jsonFile = getFile(treeName) jsonFile.createNewFile() @@ -44,13 +47,13 @@ class Json(private val saveDirPath: String) : DataBase { ) } - override fun readTree(treeName: String): Pair>>, Pair> { + override fun readTree(treeName: String): Pair>>, Pair> { validateName(treeName) val jsonFile = getFile(treeName) val readTree = - mapper.readValue>, Array>>>>>( + mapper.readValue>, Array>>>>>( jsonFile ) val tree = typeToTree(readTree.first.second) diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index 37bee9b..7265254 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -37,7 +37,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { override fun saveTree( treeName: String, - tree: BinTree>>, + tree: BinTree>>, viewCoordinates: Pair ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") @@ -46,16 +46,16 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { removeTree(treeName) executeQuery("CREATE (:Tree {name: '$treeName', type: '${tree::class.simpleName}', " + "viewX: ${viewCoordinates.first}, viewY: ${viewCoordinates.second}})") - var prevKey: String? = null + var prevKey: Int? = null tree.getKeyValueList() .forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } } private fun saveNode( - key: String, + key: Int, value: String, coordinate: Pair, - prevKey: String?, + prevKey: Int?, treeName: String ) { session.executeWrite { tx -> @@ -72,7 +72,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { } } - override fun readTree(treeName: String): Pair>>, Pair> { + override fun readTree(treeName: String): Pair>>, Pair> { validateName(treeName) var type = "" @@ -99,7 +99,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { result.stream().forEach { try { tree.insert( - it["key"].asString(), + it["key"].asInt(), Pair( it["value"].asString(), Pair(it["x"].asFloat(), it["y"].asFloat()) diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index c4c0dba..316e631 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -26,7 +26,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { override fun saveTree( treeName: String, - tree: BinTree>>, + tree: BinTree>>, viewCoordinates: Pair ) { if (!isSupportTreeType(tree)) throw IllegalArgumentException("Unsupported tree type") @@ -47,14 +47,14 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { } private fun saveNode( - key: String, + key: Int, value: String, coordinate: Pair, treeName: String, addNodeStatement: PreparedStatement ) { try { - addNodeStatement.setString(1, key) + addNodeStatement.setInt(1, key) addNodeStatement.setString(2, value) addNodeStatement.setFloat(3, coordinate.first) addNodeStatement.setFloat(4, coordinate.second) @@ -85,7 +85,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { try { executeQuery( "CREATE TABLE if not exists ${treeName}Nodes (nodeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "key varchar($maxStringLen), " + + "key INTEGER, " + "value varchar($maxStringLen), " + "x FLOAT, " + "y FLOAT);" @@ -134,7 +134,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { } - override fun readTree(treeName: String): Pair>>, Pair> { + override fun readTree(treeName: String): Pair>>, Pair> { validateName(treeName) val nodes = "${treeName}Nodes" @@ -147,7 +147,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { val nodesSet = getAllNodesStatement.executeQuery() while (nodesSet.next()) { tree.insert( - nodesSet.getString("key"), + nodesSet.getInt("key"), Pair( nodesSet.getString("value"), Pair(nodesSet.getFloat("x"), nodesSet.getFloat("y")) From 534ac25f188100694bce17c260c25d4563a01d87 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 3 May 2023 00:34:11 +0300 Subject: [PATCH 152/164] fix(ui): set keys type to Int everywhere --- app/src/main/kotlin/app/CreatNewWindow.kt | 3 ++- app/src/main/kotlin/app/Main.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index c13dc52..3a87309 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -112,6 +112,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { val expanded = remember { mutableStateOf(false) } Button( + enabled = false, onClick = { expanded.value = true }, @@ -149,7 +150,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( enabled = error.value == null && name.isNotEmpty(), onClick = { - val tree = Controller.DrawTree(name, Controller.TreeType.RBTree) + val tree = Controller.DrawTree(name, Controller.TreeType.RBTree, keysType.value) name = "" onClick(tree) }, diff --git a/app/src/main/kotlin/app/Main.kt b/app/src/main/kotlin/app/Main.kt index 9d77c1b..2b99e6a 100644 --- a/app/src/main/kotlin/app/Main.kt +++ b/app/src/main/kotlin/app/Main.kt @@ -9,7 +9,7 @@ fun main() = application { Window( onCloseRequest = ::exitApplication, title = "Trees", - state = rememberWindowState(width = 800.dp, height = 600.dp) + state = rememberWindowState(width = 900.dp, height = 700.dp) ) { Main(window) } From b59a4358c834754607b5e67da8278af7f55f2b5d Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 3 May 2023 00:38:29 +0300 Subject: [PATCH 153/164] fix(database): add creating dirs for sql database --- app/src/main/kotlin/dataBase/SQLite.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/kotlin/dataBase/SQLite.kt b/app/src/main/kotlin/dataBase/SQLite.kt index 316e631..a496495 100644 --- a/app/src/main/kotlin/dataBase/SQLite.kt +++ b/app/src/main/kotlin/dataBase/SQLite.kt @@ -1,6 +1,7 @@ package dataBase import trees.BinTree +import java.io.File import java.io.IOException import java.sql.DriverManager import java.sql.PreparedStatement @@ -21,6 +22,7 @@ class SQLite(dbPath: String, val maxStringLen: UInt) : DataBase { private val getAllTreesStatement by lazy { connection.prepareStatement("SELECT trees.name as name, trees.type as type, trees.viewX as x, trees.viewY as y FROM trees;") } init { + File(dbPath).mkdirs() createTreesTable() } From 09786e058c341a8a3e6a09ef7f9c232953432421 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Wed, 3 May 2023 00:37:46 +0300 Subject: [PATCH 154/164] fix(app): add update Coordinates --- app/src/main/kotlin/app/ViewTree.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ViewTree.kt index 303433c..e58c5b3 100644 --- a/app/src/main/kotlin/app/ViewTree.kt +++ b/app/src/main/kotlin/app/ViewTree.kt @@ -50,6 +50,7 @@ open class ViewTree { node.parent?.let { drawLine(node, it, 50, offSetX, offSetY) } + tree.updateCoordinate(node) } } } From f4705916443556b1920d40e50f22eccf82734e20 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Wed, 3 May 2023 00:45:06 +0300 Subject: [PATCH 155/164] refactor: change Material Theme --- app/src/main/kotlin/app/CreatNewWindow.kt | 220 +++++++++-------- app/src/main/kotlin/app/Main.kt | 6 +- app/src/main/kotlin/app/MainWindow.kt | 98 ++++---- app/src/main/kotlin/app/OpenTree.kt | 267 ++++++++++---------- app/src/main/kotlin/app/TreeWindow.kt | 288 +++++++++++----------- 5 files changed, 437 insertions(+), 442 deletions(-) diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/CreatNewWindow.kt index 3a87309..a75c561 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/CreatNewWindow.kt @@ -1,6 +1,5 @@ package app -import UIT.md_theme_light_primary import androidx.compose.foundation.layout.* import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem @@ -26,144 +25,149 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { } error.value = null } + Column( + modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + OutlinedTextField( + value = name, + onValueChange = { name = it; isNameValid(); }, + label = { Text(text = "name") }, + isError = error.value != null, + singleLine = true, + modifier = Modifier.weight(0.70f), + shape = MaterialTheme.shapes.extraLarge, + ) + } + Spacer(modifier = Modifier.height(15.dp)) - MaterialTheme { - Column( - modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), - verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - OutlinedTextField( - value = name, - onValueChange = { name = it; isNameValid(); }, - label = { Text(text = "name") }, - isError = error.value != null, - singleLine = true, - modifier = Modifier.weight(0.70f), - shape = MaterialTheme.shapes.extraLarge, - ) - } - Spacer(modifier = Modifier.height(15.dp)) + if (error.value != null) + Text( + text = error.value.toString() + ) - if (error.value != null) - Text( - text = error.value.toString() - ) + Spacer(modifier = Modifier.height(15.dp)) - Spacer(modifier = Modifier.height(15.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.weight(0.3f)) { + val expanded = remember { mutableStateOf(false) } Button( - onClick = onBack, + onClick = { + expanded.value = true + }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), + modifier = Modifier.fillMaxWidth().height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { Text( - text = "Exit", + text = treeType.value.toString(), ) } - Spacer(modifier = Modifier.width(16.dp)) - - Box(modifier = Modifier.weight(0.3f)) { - val expanded = remember { mutableStateOf(false) } - - Button( - onClick = { - expanded.value = true - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth().height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = treeType.value.toString(), - ) - } - DropdownMenu( - expanded = expanded.value, - onDismissRequest = { expanded.value = false } - ) { - DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.BSTree; expanded.value = false }) { - Text("Binary search tree") - } - Divider() - DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.AVLTree; expanded.value = false }) { - Text("AVL tree") - } - Divider() - DropdownMenuItem(onClick = { treeType.value = Controller.TreeType.RBTree; expanded.value = false }) { - Text("Red-black tree") - } + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } + ) { + DropdownMenuItem(onClick = { + treeType.value = Controller.TreeType.BSTree; expanded.value = false + }) { + Text("Binary search tree") } - } - - Spacer(modifier = Modifier.width(16.dp)) - - Box(modifier = Modifier.weight(0.3f)) { - val expanded = remember { mutableStateOf(false) } - - Button( - enabled = false, - onClick = { - expanded.value = true - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth().height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = keysType.value.toString(), - ) + Divider() + DropdownMenuItem(onClick = { + treeType.value = Controller.TreeType.AVLTree; expanded.value = false + }) { + Text("AVL tree") } - - DropdownMenu( - expanded = expanded.value, - onDismissRequest = { expanded.value = false } - ) { - DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.Int; expanded.value = false }) { - Text("Int keys") - } - Divider() - DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.Float; expanded.value = false }) { - Text("Float keys") - } - Divider() - DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.String; expanded.value = false }) { - Text("String keys") - } + Divider() + DropdownMenuItem(onClick = { + treeType.value = Controller.TreeType.RBTree; expanded.value = false + }) { + Text("Red-black tree") } } + } + + Spacer(modifier = Modifier.width(16.dp)) - Spacer(modifier = Modifier.width(16.dp)) + Box(modifier = Modifier.weight(0.3f)) { + val expanded = remember { mutableStateOf(false) } Button( - enabled = error.value == null && name.isNotEmpty(), + enabled = false, onClick = { - val tree = Controller.DrawTree(name, Controller.TreeType.RBTree, keysType.value) - name = "" - onClick(tree) + expanded.value = true }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), + modifier = Modifier.fillMaxWidth().height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { Text( - text = "Create", + text = keysType.value.toString(), ) } + + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } + ) { + DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.Int; expanded.value = false }) { + Text("Int keys") + } + Divider() + DropdownMenuItem(onClick = { keysType.value = Controller.KeysType.Float; expanded.value = false }) { + Text("Float keys") + } + Divider() + DropdownMenuItem(onClick = { + keysType.value = Controller.KeysType.String; expanded.value = false + }) { + Text("String keys") + } + } + } + + Spacer(modifier = Modifier.width(16.dp)) + + Button( + enabled = error.value == null && name.isNotEmpty(), + onClick = { + val tree = Controller.DrawTree(name, Controller.TreeType.RBTree, keysType.value) + name = "" + onClick(tree) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Create", + ) } } } diff --git a/app/src/main/kotlin/app/Main.kt b/app/src/main/kotlin/app/Main.kt index 2b99e6a..bfc86c8 100644 --- a/app/src/main/kotlin/app/Main.kt +++ b/app/src/main/kotlin/app/Main.kt @@ -4,6 +4,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import UIT.AppTheme + fun main() = application { Window( @@ -11,6 +13,8 @@ fun main() = application { title = "Trees", state = rememberWindowState(width = 900.dp, height = 700.dp) ) { - Main(window) + AppTheme { + Main(window) + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/MainWindow.kt b/app/src/main/kotlin/app/MainWindow.kt index 2d43559..1379e81 100644 --- a/app/src/main/kotlin/app/MainWindow.kt +++ b/app/src/main/kotlin/app/MainWindow.kt @@ -1,11 +1,7 @@ package app -import UIT.md_theme_light_primary import androidx.compose.foundation.layout.* -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -14,55 +10,53 @@ import kotlin.system.exitProcess @Composable fun MainWindow(onClickNew: () -> Unit, onClickOpen: () -> Unit) { - MaterialTheme { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + onClick = onClickNew, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) ) { - Button( - onClick = onClickNew, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "New", - style = MaterialTheme.typography.headlineSmall - ) - } - Spacer(modifier = Modifier.height(15.dp)) - Button( - onClick = onClickOpen, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Open", - style = MaterialTheme.typography.headlineSmall - ) - } - Spacer(modifier = Modifier.height(15.dp)) - - Button( - onClick = { exitProcess(1) }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Exit", - style = MaterialTheme.typography.headlineSmall - ) - } + Text( + text = "New", + style = MaterialTheme.typography.headlineSmall + ) + } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = onClickOpen, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Open", + style = MaterialTheme.typography.headlineSmall + ) } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { exitProcess(1) }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.fillMaxWidth(0.6f).height(70.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Exit", + style = MaterialTheme.typography.headlineSmall + ) + } } + } diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/OpenTree.kt index 3dc1419..a1ae9b5 100644 --- a/app/src/main/kotlin/app/OpenTree.kt +++ b/app/src/main/kotlin/app/OpenTree.kt @@ -1,6 +1,5 @@ package app -import UIT.* import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -17,154 +16,152 @@ import androidx.compose.ui.zIndex @Composable fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { - var files = remember { mutableStateOf(mutableStateListOf>>()) } + val files = remember { mutableStateOf(mutableStateListOf>>()) } var dataBaseType = Controller.DatabaseType.Json - MaterialTheme { - Column( - modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), - verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), - horizontalAlignment = Alignment.CenterHorizontally - ) { + Column( + modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), + verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Button( - onClick = onBack, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Exit", - ) - } - Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - files.value = - Controller.Database(Controller.DatabaseType.Json).getAllTrees().toMutableStateList() - dataBaseType = Controller.DatabaseType.Json - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Json", - ) - } - Spacer(modifier = Modifier.width(16.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + files.value = + Controller.Database(Controller.DatabaseType.Json).getAllTrees().toMutableStateList() + dataBaseType = Controller.DatabaseType.Json + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Json", + ) + } + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - files.value = - Controller.Database(Controller.DatabaseType.SQLite).getAllTrees().toMutableStateList() - dataBaseType = Controller.DatabaseType.SQLite - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "SQLite", - ) - } - Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + files.value = + Controller.Database(Controller.DatabaseType.SQLite).getAllTrees().toMutableStateList() + dataBaseType = Controller.DatabaseType.SQLite + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "SQLite", + ) + } + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - files.value = - Controller.Database(Controller.DatabaseType.Neo4j).getAllTrees().toMutableStateList() - dataBaseType = Controller.DatabaseType.Neo4j - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text( - text = "Neo4j", - ) - } + Button( + onClick = { + files.value = + Controller.Database(Controller.DatabaseType.Neo4j).getAllTrees().toMutableStateList() + dataBaseType = Controller.DatabaseType.Neo4j + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Neo4j", + ) } + } - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) - if (files.value.isNotEmpty()) { + if (files.value.isNotEmpty()) { - Button( - onClick = { - Controller.Database(dataBaseType).clean() - files.value = mutableStateListOf() - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.height(50.dp).fillMaxWidth(0.95f), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_negative_primary - ) - ) { - Text("delete all trees saved in $dataBaseType") - } - Spacer(modifier = Modifier.height(10.dp)) + Button( + onClick = { + Controller.Database(dataBaseType).clean() + files.value = mutableStateListOf() + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.height(50.dp).fillMaxWidth(0.95f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text("delete all trees saved in $dataBaseType") } + Spacer(modifier = Modifier.height(10.dp)) + } - LazyColumn { - items(files.value) { file -> - Spacer(modifier = Modifier.width(20.dp)) - Box( - modifier = Modifier.fillMaxWidth(0.95f) - .zIndex(0f) - .border(4.dp, md_theme_light_border, RoundedCornerShape(20.dp)) + LazyColumn { + items(files.value) { file -> + Spacer(modifier = Modifier.width(20.dp)) + Box( + modifier = Modifier.fillMaxWidth(0.95f) + .zIndex(0f) + .border(4.dp, MaterialTheme.colorScheme.background, RoundedCornerShape(20.dp)) + ) { + Row( + modifier = Modifier.fillMaxSize() + .zIndex(1f), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Row( - modifier = Modifier.fillMaxSize() - .zIndex(1f), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + Button( + onClick = { + onClick(Controller.DrawTree(file.first, dataBaseType)) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) ) { - Button( - onClick = { - onClick(Controller.DrawTree(file.first, dataBaseType)) - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("Open") - } + Text("Open") + } - Text( - modifier = Modifier.weight(6f), - textAlign = TextAlign.Center, - text = "name: \"${file.first}\"" - ) - Text( - modifier = Modifier.weight(3f), - textAlign = TextAlign.Center, - text = file.second - ) + Text( + modifier = Modifier.weight(6f), + textAlign = TextAlign.Center, + text = "name: \"${file.first}\"" + ) + Text( + modifier = Modifier.weight(3f), + textAlign = TextAlign.Center, + text = file.second + ) - Button( - onClick = { - Controller.Database(dataBaseType).removeTree(file.first) - files.value.remove(file) - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_negative_primary - ) - ) { - Text("Delete") - } + Button( + onClick = { + Controller.Database(dataBaseType).removeTree(file.first) + files.value.remove(file) + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text("Delete") } } } diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 8b270d8..013ceae 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -1,7 +1,5 @@ package app -import UIT.md_theme_light_negative_primary -import UIT.md_theme_light_primary import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.AlertDialog @@ -24,173 +22,171 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { tree.reInitAllDrawNodes() - MaterialTheme { - Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { - Column( - modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(400.dp) - ) { - Insert( - onClick = { key, value -> - if ((key != "") && (value != "")) { - tree.drawInsert(key, value) - tree.reInitAllDrawNodes() - textForUser = "I insert node with key: $key and value: $value :)" - } else { - textForUser = "Give me key and value pls :(" - } + Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { + Column( + modifier = Modifier.padding(start = 32.dp, top = 16.dp).width(400.dp) + ) { + Insert( + onClick = { key, value -> + if ((key != "") && (value != "")) { + tree.drawInsert(key, value) + tree.reInitAllDrawNodes() + textForUser = "I insert node with key: $key and value: $value :)" + } else { + textForUser = "Give me key and value pls :(" } - ) - Remove( - onClick = { key -> - if (key != "") { - tree.drawRemove(key) - tree.reInitAllDrawNodes() - textForUser = "I remove node :)" - } else { - textForUser = "Give me key pls :(" - } + } + ) + Remove( + onClick = { key -> + if (key != "") { + tree.drawRemove(key) + tree.reInitAllDrawNodes() + textForUser = "I remove node :)" + } else { + textForUser = "Give me key pls :(" } - ) - Find( - onClick = { key -> - val value = tree.drawFind(key) - if (value != null) { - textForUser = "Result: $value" - } else { - textForUser = "Ooops... I can't find node :(" - } + } + ) + Find( + onClick = { key -> + val value = tree.drawFind(key) + if (value != null) { + textForUser = "Result: $value" + } else { + textForUser = "Ooops... I can't find node :(" } + } + ) + Spacer(modifier = Modifier.height(15.dp)) + Text( + text = textForUser, + modifier = Modifier.padding(start = 32.dp, top = 16.dp), + style = MaterialTheme.typography.headlineSmall, + ) + Spacer(modifier = Modifier.height(40.dp)) + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.aligned(Alignment.End) + ) { + Button( + onClick = onBack, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text( + text = "Exit", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + openDialog.value = true + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + text = "Save", + ) + } + } + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { + offSetX.value = 0f + offSetY.value = 0f + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.width(400.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary ) - Spacer(modifier = Modifier.height(15.dp)) - Text( - text = textForUser, - modifier = Modifier.padding(start = 32.dp, top = 16.dp), - style = MaterialTheme.typography.headlineSmall, - ) - Spacer(modifier = Modifier.height(40.dp)) + ) { + Text("go to tree Root!") + } + } + ViewTree().drawTree(tree, offSetX, offSetY) + } + if (openDialog.value) { + AlertDialog( + onDismissRequest = { openDialog.value = false }, + title = { + Text(text = "How do you want to save this tree?") + }, + text = { + Text("Select the database to save:") + }, + buttons = { Row( - modifier = Modifier, + modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.aligned(Alignment.End) + horizontalArrangement = Arrangement.aligned(Alignment.CenterHorizontally) ) { Button( - onClick = onBack, + onClick = { openDialog.value = false }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f), + modifier = Modifier.weight(0.3f).height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_negative_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { - Text( - text = "Exit", - ) + Text("Exit") } Spacer(modifier = Modifier.width(16.dp)) Button( onClick = { - openDialog.value = true + tree.saveToDB(Controller.DatabaseType.Json) + openDialog.value = false }, shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f), + modifier = Modifier.weight(0.3f).height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { - Text( - text = "Save", - ) + Text("Json") } - } - Spacer(modifier = Modifier.height(15.dp)) - Button( - onClick = { - offSetX.value = 0f - offSetY.value = 0f - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.width(400.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("go to tree Root!") - } - } - ViewTree().drawTree(tree, offSetX, offSetY) - } - if (openDialog.value) { - AlertDialog( - onDismissRequest = { openDialog.value = false }, - title = { - Text(text = "How do you want to save this tree?") - }, - text = { - Text("Select the database to save:") - }, - buttons = { - Row( - modifier = Modifier.padding(12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.aligned(Alignment.CenterHorizontally) - ) { - Button( - onClick = { openDialog.value = false }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("Exit") - } - Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - tree.saveToDB(Controller.DatabaseType.Json) - openDialog.value = false - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("Json") - } - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - tree.saveToDB(Controller.DatabaseType.SQLite) - openDialog.value = false - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("SQLite") - } - Spacer(modifier = Modifier.width(16.dp)) + Button( + onClick = { + tree.saveToDB(Controller.DatabaseType.SQLite) + openDialog.value = false + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text("SQLite") + } + Spacer(modifier = Modifier.width(16.dp)) - Button( - onClick = { - tree.saveToDB(Controller.DatabaseType.Neo4j) - openDialog.value = false - }, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier.weight(0.3f).height(57.dp), - colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary - ) - ) { - Text("Neo4j") - } + Button( + onClick = { + tree.saveToDB(Controller.DatabaseType.Neo4j) + openDialog.value = false + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.weight(0.3f).height(57.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary + ) + ) { + Text("Neo4j") } } - ) - } + } + ) } } @@ -237,7 +233,7 @@ fun Insert(onClick: (key: String, value: String) -> Unit) { shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.30f).height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { Text("go!") @@ -275,7 +271,7 @@ fun Remove(onClick: (key: String) -> Unit) { shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.30f).height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { Text("go!") @@ -312,7 +308,7 @@ fun Find(onClick: (key: String) -> Unit) { shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.30f).height(57.dp), colors = ButtonDefaults.buttonColors( - containerColor = md_theme_light_primary + containerColor = MaterialTheme.colorScheme.primary ) ) { Text("go!") From e06951a8d939c91168b71007e88ce7507ff917f3 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Wed, 3 May 2023 01:45:26 +0300 Subject: [PATCH 156/164] feat(app): add icon --- app/src/main/kotlin/app/resources/icon.png | Bin 0 -> 89691 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/src/main/kotlin/app/resources/icon.png diff --git a/app/src/main/kotlin/app/resources/icon.png b/app/src/main/kotlin/app/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6b4147a018cbd365d3253c8b19c6cbcddef4f01c GIT binary patch literal 89691 zcmV*KKxMy)P)EX>4Tx04R}tkv&MmKpe$iQ%glE4wfR~kfC<6AS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RR6NzV;VcNtS#M7I$ z!FiuJ%u2FKd`>)O(glehxvsSMjdRgufoF!zOnRO;Oe_|=Sm|O`GBx5U;;5?WlrLmk zta9GstW|2PeNXR-^Y&AJOTXAz?I(dR~x|0C+YQ$ z7Ciz6wt(g#z$?M&FbJ25*7BwN`KKeVjf38R{x^0~{Oz zu_9%!d%U~1ySIPOwEO!3Pyuq8uK0!o00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=LG^5Cpw`Xa&!Oy02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{03ZNKL_t(|+U&h~ux5oIo56Eju_2Ntw!kQ$-4gP!xnJNC-(82=P$1 z6(q~DEC~(n*3{kV;Z1is^WMMTTKVI*&v5U%ue+rlUf=Gss!pAI&pr1Hd;fmlz1DZF z^VP}2|8$Szxd3h1Edtl?W1a_@S&pZ%jNE*n6N;NANq zz^rU`?*QZQ_=JREX5^g7In!DunUQe}M^%w3qM-nC7MA~iMKa$Y9=;xUo4fyJ;H|)0 z-TlqLevEMgqt1TY;GkltuR(r|SLNrN?|m6FaV%6=_y5b+Uw${BeD=-PEqABDSHK=e z?eRqZa&Y~kd;WsEeBM1Dy9%#HXnh_upwm`iF>aD$8U@fbAJNNr@{O`uO zE}L7=cwq@4S!R)2W^F?=Z)uu_wry!GVNUR+f0q%7-Nigw^lo?^`dQGg2-f=YjsB zi^KAgQ3H+>+{*J&-0d!}`|O3KJ3bvyZ+g_?AQHA4XbaR^d+Dugc-%n*A*@i^+)&Zt zMQxnZh~>%hl>j~V{d z`ZL(-Vo;u6&TAz~2!Xl^^m;vZdVT7urbbts&hn@%Bcv#fib6+|tnk_Mnj%@A#0^i} zmLB>B1+}#;P;c#pdefH9dQq-tH)0p#x))H{WF#{%%?%a;!G$bNN{R7&#_4FnWHF~d z7+_{3%iYT=x$~gSouuKrT=;FkZx!defVZ&qZnE z?t9cP5>Q`s>;OpZO%eG8;O9l;XO-|MVjab0kn;M%sKOvtg&ye-=+}{+L?WdGW?AK9 zW~e*Komf??b+f!6FW1l(sJB49wHM2y_Kgay4bOQKev(6z67$s1G!3)Gf~IMi=Y$Lf zB+I0f$X2QpLg0I1?ET&l_&q75cdg%RT@|sc-mnY3)J;&nHx1wdts3cBD352uOLYkT z!hkZ1UFbdg1vJZD)JgaL;RS4$R_=Z50Mz^(EfGHMp8h$6|HnQ3?DD&ph{uSwq;&+v z*&FWAuOqwtA^i|nukT)}bb5N0HrjI2TcFAbZqP;c!8f^$-+_dG3RQ=-Pu0qAQ>-8(+Ha#QdzDqCW>vmry-CXVu5W zRTFEV{@NCL0czQ3^0}AiyZn6TsgoB1)&)Gz6$PH-+)MrVrl;Fouq5EpH5zSrzfJ>RKcD|)>FQA&}bZbnV_TB!F}m-5xD>*wOOwzdW8 z7aMHv?)bZybo6r`aPoGwDI&5eu<~3sm4>i-gJp?bUU}2P2xr{P8J~`rHw(@r>i3 z8<*~7DQMW-A9ijd=oYNEKz%=f`W}Bc57fS~a@9+XTB(=q0NbovId#FN@i#KTVz;s4oC(*^C8O9fix9=OSp7Q4D9E8b*^DN7FHjrYS0IF+iyBLW}%S zcmEUa{=H}3a=Q_>_AC^XS^61?;qOGzzo+KplprP4i~h;5*Jrpp;M#DHN&}7|9laJM z6p!24kb3IYoUH}9Hn)XuP$OY;-)wCQ)Gw(=?fGIryjLTJT;Psp12;?riG?#>-bQW1 zXgc9&JfcaRCsRd)K(hRxh)}X4%EsZIE&&OEt!`>V07g)mxx`xqRx25&WwF z>Mm?VO84O0jqzf}q-i;wO=)v3f=CjGF%m-f=7j&rob#Vj^XtpaaWBQUt!?euz&dZ% z$E)5F_5a1)|CX73RvmIqNXpb281@J34EyZ#`t(C&x9fadE$uV1xo7pVOU=5U2|D8z zsJB3Ue}Ve^19}FieRE-}bWJKSUNoG{CY;PmiCw~pRS&5mc=)cI^PkQ+f4_)Ss!9~Q z%cEa<*2~SewXNMdBJ5;sgCbHAgKg{{fEh4qC&^r+*Ia8~$^{BT%eSd-4eC<_qg8b)c?7VS5Ya33cV@{?sW~n6y zMMJ0`D34le%_@T(JFAOb;z zK@1G59((-(`&HqvV^{VU%t5utt74FIkt(SB+dAqkP`?Z?eO|bKHT+dNfG+60RJ1dP zS-aqLJZ3bVFwF@ITI3{S_$GJ%q1N&bx|JD8s(MC*Up#C8HCv$G+KU3zORzpaT5QwS zx-1Y}au)xoh33ER@EdN1=1lEEud2DyA2RIM9Q1dHKrj%uHE+Gdn0x49Z*2?IFFH`4 zP2|aHYPTV@X1ErwdXidBCu2slDNWN9jfT1+1-ULFe-`*5GkaYTcV7$QTr9r76b)hv z)LVN|sE-b=@6yM{J!Ib&m>4&2`R& z!wzQ?>|5Ic^-BoU#YV-QJmZ+_6I|* z?d`MI@0ZCl9aZh_XxyUo7N}naJ3monDVWsYm^-7{jOl#Nc(I_hOwwiKC;y(Q{3l+f zr<78<UL(?HPGbd@G&Ci8|x+i*Oc zut+V9L#%43!#i8IA6~TWpUyec>kWw=FpC-DnTX<+$vG2(tQrMOO0s9EpcjB(vJ%nX z+Sa~WkT$VZsq^N`GQscS_AyoBv7AU1eR(jq<6djyp&ueSWgkSx&OsxALk*@hwo_ z#cyPCZi(7W>NW#+G*q;}%wXKi8BHgQr&Aiwm;^M2H)P>I1KzXrTEn@G@IsnZ%3#*q z3ELN7*3PRA-TO)RyKnptYmW!0Hh|etF9}jJ!&z6fsQH<#ux$KQy`WuP1(8_1|CCn8 z)6RWYh1D?Di#+zVV1Hf@7GMk1mkLbI>d*Y7yZ^Vq)8)ZT?XW-Sb8Y{Cz1S-)=&1}Q z2%A+Qoeqw5Gi;VoKZfg;LeATKQw5Z&T7A@oXoY8E;crhpA zXbiZ<{{WT$JmKH5NiV#pKuy4@I@m1BH_+meQPP=9j&;v70(iArxne|{X z7pTwS$*;Nle*^q)g5hmTK-lT^xw?PAmBEl2&>}msq3hJ~->h5EOH3=z4$yafpjQum zZZ-^(wr;q!E9*h07cPk|`5B8xo-^e_C9$JK99ezi zu;E8ZOPMQ@~$U@yFZ^ZxaFG>fSyNUcJU( z1Ax^5KdWW4UMGI;@-ADTepQ0;QcrsIfS%#pM1*Y6SVltOZCjkf(^C$Qk0At-b$qYN zA90O;!G(938Le+{v@0(%bjg}^C)n3+;qGCmSOHu)JDhG=&L z$*@?w=xI(obLTeW>5NcU%Yn1kMdZK8!h5_6+3#h`mfaTzs4qHs=O3^O9gOlB6}3Wb zV5v;x5JyzesHj5IG?@BDd?Q$3SRIg3DJ68@NEFI)x<@V*1l{*MpL|NPJYMSy%f(=Q z7O2aX^es?dV#`9Qic0Axfxlb@xh2`iDKU&SS9W%IVE;;~!-mx~8O&E6HmdjRoa`3u zs}`u^YcD>t4S=?d@LbAzh}ub!W}Z1Y;%Gd^LS(nsXFi|*TY~&3&hJ<*ij~Z0S8{hY z;Lm3~+7}g=#Wo(;pgdNdw36xgX)hP0P=~@n=S=H`$}lO#i5VyK2dL+@#f;7)U9ho(Ulo5btvIB?dS@k4y=s-329g!K_|LgV@IAdgV+7-`dwoiHLw` zg<10+cmK|+>i?w>^`9kXOjAP|B@)g~U2#wk7-%3ahv9D2&VJRC>t8L$@U`PncglBS zt1`B=SKWnF0*%Arc*4`icUYJchJ8$h#bWUnD$ySmk%Q&p%VmDya=g*HYJ$5pJCbj8F-c}k2*RTodcZRd=~r<_hk%okH;ZG%}$&K(`nX%&YM zQ0Z}X?*ZD@7}hh zqV=BM^p^U#OhCQ#9Wx!DyyRBF+-V(d93OE!ozVucP%+Qxn@#vHV;%p53p@q zI+^&wzJZFAyV}A$i zzXm>A$3QchP-)=x*RJxw?m;Qb%T6zpT*WdEqFU;&Etd#zGe6{K>${PKL0gEQ{%;6m-?Sh4Y))9-u+Ju->32(A@qo$B1jK) zB`?~d2@wNzUE^(orA$>TDWz3R-~xEKOLt;xmlbGO)%!0m#A}&V`!(Pvnx^^RcZUNi zgJ!;9uRq}R*B@X=AiI@PJ}>FzrH~oj6q+-@yanpZ?o;QkHdD!#aZyZ!(PYZe=!EfN zfs5cFLJU9JqVH`j?>LGEM_kU9uANH`-d*>5z6#=nHEiVEb_#wqq3)wnFun&cOT&E-~5fd?QP${{_YjjYpOJ)4n3->!ds)6FS_ou zUh#@VFv*?1cS%ZSSHyF2Xd z>@ln=h7Li_b|o%rg3N67^A@Ntv3VCb=Vg1i1X`S<(Fu1>M@$xT>cIdH`gKBhU$XrD ztx4fbC6tanKL@-WP~W>T>l1-V<5)xkrR$=ZqdDZArhLL7mfCsJ|aIM#4prHfiLU~lGrAf{GuE=!@ z)Rz>f=cz?P#5d}rZyX9A)zm<1><2%{u z?J*s-R9Z8v_DPFOE!AoO>naA4_3S<00`=tvYi#AzEOQW>yt0`QV?;!LKIi-wbIw1f z6p4c9EpIp|I@kT!L-5i`j8KW<_+1qt=aZS2`$G2`0_w&bQ(^9jJ155+jVB~Gyo|>B zlda|VBl5Oo#X^+A#flj@=QS^Oj(c7vxz;%HQUG;nBPTdj&sgLUUwG=XeB!eo<>tu~ z^mhfMVV)O7yF-PZ4p9xbMRLxxme6icfqUvIRuYj?M}2-RY<}J>;1&xN(v{YUIM(sp z?wNj7t+Yc#gs6qj2C%I3>wON7Mm%`+jlBC^zn!_ph__u1RqV^H-7=t}!T+%{UH zwm^NUfw~1w=L_zPPB@uQOG#U=qJ{T1ZTkVOtGXNyB_btU%Z7JIdw1vI62#7O)thpq z_X57J`twUB>x|>^O&)vl3w-|Z&oP?cp|_{xxZw85O=2z7y*|CV$09q;G}A1cDh%lN z`Xrm9Arp{}qb_B5QDjxwv|JDFj=Cbt+H6tGYLY0?my)aY_UJxGBCjZ>xK%U0w6<(?GI7*p zwSP*ZsuElXp<svOK8mO#~ps6rw+}8o(lb5pZ&c(cKQR#SOQ-~bej&;7N}n# zziHiE6nQ;X!gS-sM8SbEYc8bp=0 zZOedOjfliTd3b4A*Jw=ah$NYT52>p?WFDBFX5RYxxA9%y{@b~J<&CKIP%lvcECIW8 zJw$OfHBHXwal3BRm$$h1s)c@aT({+Y_Z6t^ob2to*I+p>G!(8H!E~J@bo8f$_Qz}S z8_i-?TF>|PxVC@5F!qSu8D8o}Zh`t0g3c~y1^k7sFh1oqLaX2^%z+6Fqxpi{caFQV zFD0ve8xlSM{Gn%?#Qfq}psxCi#}i@kU*>O>7d{s)U@{^P^J;8Si!o-Nqx z_j%*>2N{M)yI4@iYE^ITxr{Z?E(*4sk@M8$D%rSJ%q%NcF7^s#3)Ei|P}|07Tmf~L z6;7$8jY6w|JI#n2hey~NxPBM#0TKC~FW-j`3lcKmRoLuO<>ntUCnLE&|v?hcaUj&BXfOow0+xX^# zL|umf03ZNKL_t)yeFr;oz_7l?>~w*fV$t!KF`wa*=~aD1BF&<3;ANOWMr8`pc1mp3 z7N}n>US$=3A*J-=r2O;Z5G*sSYOY_o!nMJ$$o5)ETB?)?D|fZ|A;!Cs(U%FRFPGMb ztV3RbyF@h!VWr;^>WZmlZk--+JROs>(>a8HumkCTkw) zc)!>Chu$n0Pp8FEi;?}R#-*sXZ?=$2M~tjX1MdMZ$i=kkD_{v-GLCu;)c5M)Pq|F; zP6~IXBc8c)NXnW1a7dH=`(1b#c@K;K%H`5DQpX)k0+t1$vuIAyx?z@&c;w5U zViaAoHz?JP5> z27K!`zLRf#`)?sl_K3cND8%T5C@dCJvb9C;#JXt9>y_BnYFln;k4tZ=rEE5(M9%pr zMES9Pto}RCMzff++aGYSzsuFZF0~XNI(B}trMzTCH-I`^$Z)J<;7#K4{(|+TDopjw zgXw2)xx?*|Wu87c;&?VCxg%v3+mE{GUloz7=ch)ztXIYEy_Z+^#bQCSmZ}%1hmlEp z!Y4lc|M7(|BeOE|@eE2zpl;;3W=W3!sX0U7w}%b8mN-!_jT}I~7T$v@#~? zm|uV7!#s8PDE)yEsur_EN{;1F8f09_xnb31x$=Q@OMSeGOu7WLh!A7E3jEbs+x+Ol zGdk!q&xym)nA_tM7UmejBBvt78^gsNx6hlnyuXsE;e#Li;H3fSjHB3kvO}4G3R1e} zCOA(Y-QjRDCdWvORcPAuW0w5KtEviyuXSx-X0R3@Xv9oGB4$pLT4Ej0$~c+c;Fmx8 zPq;aLjB1!jOqtBaXw~Cj{{e0s9a7hU%S%abq-$W=*R+yH4IOYS0?tzBl2_O_f~2||n!R6`5jld<3` zzgXrgCsH;+~Yd7Bm1v7Bt_AREV#bQOm z2h-I4M0R_Bsp}2*J@iklkW(jMq=DEA)B`wKJjJKK@L`^~^(dj2S)@5Gk)7RZOeb?D z&6NGUoy))3#8+PLYB+&Oxw$i&F9>zTa4>+XOiDLtndS+%#y9x*r+6p{mjCp`IggrzajuL)ERB=LA zIPdo{d}HLx|+k=zwcPG@|A_b^EhenzOTeom!P1eZ*_pW*GAX4pi714>kRetYf6Oe6X7f3B>!(BoQX1PUc$?xZsz_x(UA zC1od8L$W|?4WS3W`l%0bbNodn?G196kfbG3BenxR=%$EUnLK`}&F*nuE@EeHt9o}u zT)PIIOk0il%I!z_XFvas**mD2r3G~zX|rI-yJOV_tzB2=ItJT<^sB`#ZctOq>_;;G zW3j3ZdV>Lt8%L)n98E^urCUG*Q^i%wywDE(HwQDz#;ROX9{1N3zf`L?NFaACbpwo= zh8w4+9M31T3L>&EA|DiyA3k$f#a4Nj1&RL!B4y+Buh!~M1(9)tig7+5u+=u=trc=i4h=YTj)hM#E z%{qETF8?vx+Os)7<-)$4SRN!|nICrd4@wpHQRQ?t<8UIW(I8rxy(i`*_xoSECdMMqShOQd zW<2txPxC9E_yt-S)7lIw!B~2d@yw|y9d(x-aF&C2b>nwiQxmI-mVAMi-BCyNfVnr! z@(Evj@-uwq(T^}scepcphN_l(FKZ7gt?R8_QcUhNtMfUYS$%!B>lnd|rfL3MHhY*D zhF%3`sp0m?3FD@raqFUT$Zo4_MhKzIUR=rmyRVw+mbYzr-t+ayQrW}_n6wS2xn-u% zs>F%qhfTwKL;^L^-E0K__nYC(l98%q?^7-%vao{YG2dcw>S-m9=GJe=JAJap2D8@EUiXL1$teWgE)y6;T$9Ugh? zlX!26)C*c~*%=h!p+t>%uUjoGE)B?HrN(#pcbazE8+t)a>(b9z-lub6r?OZC_V->- zy*J=!b_4Gn@ySPik*r5ZT&z0}eRF#G(ihs+Q@@%m0I4bJja4^Cw6Xl>6aH`xg^xZR zjW`{R7%yfk+5^D4v2V(vjq)8*6>~n@qHoIU1(iq zv&C-o&pMpGzdp4~X0kZqQ=j<=pL_Hp^ac&5;~Qi#L29P4?9cSci=#;43uOYGOw zb^XJ@2XiiZ4@>Dp-|(KKCAxJUbqligEJGJD1it{>{JBrFiFLQG8m1!}&e8*SQxf66 zg@POJDVbgnn(2hyon2HTISBoO0XL4HvpT($|t*Mik`jl0vfZ5fu_ zjnM->nDHO(_XjM@IhoElnNAqxMd7Tan`$nyR$%h=-YJE^uhFWHRl|e(3KLSY)iFxh zv92|Ik&VOglqZjGbLtJ=i@5M-Jm-h2?6HrcwmB%%N!`86k`D0~s4r~@j>}RFTFLag zTPTm&h`Mfx0a9aBafpNgllcT2$0tK6PG&Agqlw||Xh4k6biFt>!|1+_XcIO^8T zB1|M*;56m?B}dJu)!9)9o>0qFK?x3K&}LujUPvGkQ7xW&t_-R&eSKlhA_*y17!|4Q zTDmO}#i@gX&kz;Sh_neP?_M^<& z6U>?-Jikcf+!CuY7t*X{tMzf&>5WWaU3Tem_O{3iP+LhfNVes^kLvpHtVw@Hdjsgz z+@4LjH5xJXL>6UXxzHRdC#*zbWeBg5Mei&5@rv@OW9XWU9Xyl?;2D_CXPnH&v`VR! z7XC2saBvP*9_HpKdbuF+@Dd#5WtmTz$l?Trwq4-4CFe}jlu60Ey8~_=-{7gEC&`~VP zV(gL3Xj3ZVws6E2w9S<1Y)sQ;hGB<;;Z^#*9V7?px-K4c5X@SdG$(|Ov05)$L}K?? ziWlA1a2})~hN6(?R$*S_+GA!5PN&EG%g_Bfbr0r?5qtZ)xI5$V39*ums*jj(^Q5ete z@X618j5{Z{v1oWz!ZQ(wqJ@sIG6G8^AX*b7mf42MF>hJq1r2kwic|_Y2V#f}s($Hs zY@E3j`BV+yZUmQ3gSW+TYMZlL*Jv0rn@4QyG@%=x1voR>JZ0y~kfzN{CoQ78kSekY zY|=XS!JjIgPLVb>Z$X_J!Lu@*P5IJekMfSM`!@EghY)dMjLez^s#?4(^DUaahE1F| zx%S)_~v>$m6AP2m0{~_M=(AV>Zhu%aO2($KtkA317dFs{|$q8B$OlpD-XcJbHB##{;j`{a9_nqoCi?3%w6y$#=YM zeyy?0WIE^2A?)rk)BqY+$uX7t?@hn5fV%UhHI&@_B0HzE8OM_;i)?5W-|QkE0Qv5F zao#We2h{G;y4m^W9e682hu}`z6akoQbCks6Pd{3ii?D;4vuLNd_913kYf#CcEink@ zEuI7{VqWw?Vyp>OU~UbT4R?qkplYbMXjI}(A*vyb2Pfa&6CH`o`aXfs2TJJJwy_zNrVVkA_k#t z8$3Z3AjH@K|D0y-P(?Il%!_faiO;g|F_ssY)@5vzCTbnfwvJe!r$d@%hPV(-(9p>_ z;$&+H>I}68Qyv;Vz_s1ib1=NlL4S|o;DFuTeRc*rB!_;zOBIKBF75MC1loo)A5-sS z=Iu=;vk9Z)6K)+n!>z-oIhr2QMri`17`M2 z#vhI`Fij0d;|a0@`@I3R0$vIN^gb6--#4IMW@x2LBzo68)-;*PY|3OlBLQ0Xe!zpg zKesKqEVa9Dqo)c_y)iuMEP$ILC@DA0@|Z@Bc>I~q)8AF*ZHDNmm&#=EOtY903y;5yf?zK(0dtGw>u^<3M3BmJ<4T7_x^DKvoA74AZ_C|WH@Gu;l9SOP<})lbv^1D4umFR-3JEon>4e+}buZHE)n%G@e&SGj%q3_6@r#7ir3 zR-!o8v>fyYxGk8T++?R;)6VA@0|@(E*?EvRy!Gw8@%3-#%HA93g^H*R*LL@K=ILXq z{tk5|%$q|Nc0|L7+jnj-Zx)=67mP+@CgT~4`4mfYk|!1fvVzM3(Tqt3HR$S2+vJd- z3cFOU&=x@`7wgEK`^!+KftLz1BO63jF?fIL_P7ZD)PV6~PBlKI+Pz9&;~A=U=^Vb?Rc0?6Pzz9N zp!L$@qoUANOttQa3O5grnKpA=1qu2NQF)(ZZ-$ZxxbKoW8x;@YTIdh4l`@;7B+Gbk zCbJ_(vs;+X$sMmfosAe)LsIKln?NgiVV8b&h02sSJ@^0*UVjr0J@^)eg9lLCM;Q>R zEA%Uc_J})2x9C+p`h&=#o$~QdeT=6MKS!!Y9M5jC(1zAZTR<>H0#sF@T~gj#i9U%c zNGRi9W!Y%ttM15U1QEPT9ANiZE9ETxgxduUcOYBFq-BvO9G~3a+U}c+WU6i3$Y1Mr zzRo%T*#j#1&7kkgZeJD+pKFpap3E3(4;>DurCV>pUAokp8X+&`0D^cQwisz+jJr#z z@MxNbqsb}robVW{Ao4yl-mOwvJM9(4FLn~07c_z7oX~#a@Xk$+PH$o%> ztZR?=fQfRyHAj|_r??VU-o17DdWuM-@kCH(mdAYM=Hp!3e+ZEtIfHD)^IqeYHyo`F zk7V`eyFuUQB7ds&)Sk|#R57Aa*o!?XEmNg7!FZE;eW^X{mldcjXEX$YK%3e!q!d_q z;>O7#O(>zQsJ^##`{OAY#847qyfoc~UmId9n_M=*OA&Qi&(zVmb8?H*`7!JWCKVZD zcW;mBY)qOP@BI3=@onGuZhXGOu-eD6vY5{p?1IFG)6q>HeeAP5b?b3Xjt^<_h^D>G zeA@7ahrWSp`5|H$vU~j+IvtX$39~$*aVJRuR(HjvK$?3?9T#93?G)@x6kRrSyHLq` zIfpZ`P}SuU?q|bGN5f!Lt4scQAXkzFG?Wg}n@3*(`F14qanrjt>(*XlFx{>1O&k&L zJZk|7;g758=d+{_wg{7^CC+AqL13@$vBYTK(6@iN#(2HFor#_ign-hj&{!rboX)14 z&ZhK-L$c*R=IHxMNKteuf(I53fAdexmb7rgWM)JQhvbb6EQ3#w3I9&s6R zIzC}H?s4tlja7IyjvjAkv%6MHsi~We~HJx@;PpvJi)9TgSKVzi+gE`srET- zgjDU2T4Vo#YuFQsg-`IADw+CDti2=`v(eyS=9x;>yw)GxcJ1g=9!MeTd+HK=N z_Vhjv@sUdP?`Gg+IzwEk>zX0!A_hC>UB9Y;I>uNy9TlQRk{gGk6OJbns3Y^7zN2+} zuc`zIS|$i1cs49$w0P+V+jK{}FbX=X(f=W$)a1})uO~?Gp z-}oda$1`Y^*|g#4_$G%(PqD}&vY%oxp|xOQw3f*RLoHPow~vld30Ms5?d@Zhi0Zf& zVk28dQd8I&PngWe(ooffKk#mXT+s#74Yk2ehP30{i~3v{NSn?~VYyG%)vagI?wAYf z6A?;Re+VSWh#2!U|NlcE^3Nl_#<&{BvDGBi;1v%`ObAM7W;IQ+l|DdY${LSajmA(<8+ zf+7r%Ag0F9Q&nZ=ozAfLUVhl;+`O5U)z#gZJr(bcxRG7eSvj10&bQb4_P4&z>>Zmz zyJDg>wP1zWS7R%YH$;2V@-{6Wb#blpr>nxicR~6NGg6DkI*&7YqZ@xTl zXd6UWbq&YM1*_PhRatf^{eF!00f0htLWlLPcSS!9^Hx4+aiBYlb>*4=+pQHK;$U`k z$ZT6bv=Co}>$I)!YR2(~HvG$HR8#eGpt~b0y-E28iFw_;8 zD2uM;XfdaYVKDjreeeCxt13Q=(X)i6_nOwScpFtiS@gZmYwIl$lNxBtqB&&M&Jp#b zpv08$DAUoHU>)irZe*0xj$)@J=rNQty5g893%azVPRlILj~#i~LuuL-UL!Hgn9pyb zNthJ7?Cne$dykqRNf0$09YT#*10iBv>bd3u?Nh#A@p2Bquo6hptzRagPei|0@Mq#O z!$Fgr{nA&bpHVUjL()+UK#QK51_A+3P{Jg6E zzAq|JVcs+xtrjf1b{&YjSyd0!v~9djf#3zXH~+d(cc^Yo70YW5p#O9sSe}j(gxHH) zRw;6OKIdq;!bB+w|6e4te-=W-OYbe6VkYkuI>MO)uD5m#abMY($@QMKV6Nj^H=pCV z+uw#tNwL6t$5g?khGau!muZqVbvOfc1tBJ2ib(bekpwCQ&OyRRVMVE)6gt{;n=UR0 zO~KA|m&s(x?L`N^1W~$_RVE@$=HjdbDG*6S>2Q{MkLTi~#+07cS%cuvJyfVwr2Zf= zNz;FR5BBDICB|7$O&ccu`DrFk#!c3vH;bz9boGkT6{KQ>xx}A6_Z9X>PxIb) z?_)8(eGa6z>rH-UPY(XY`h#cLthVFY=R%|>)jIwe@JHf`KQIzES4*7t__CrfL9EZ^ zG$UHdXGol=le9PCleAG5%bNkzuZr2QwcaoxXg`bqU{0wcELJs(wjriO^7=R_{hpbX zt_S$xp&u%s_%^GAGDsWFGC+&vQSzkFTFc?=kVqto;wkacA!b9Q<{3I=v$@F$xaW#P6KC3kd#A$Xp?x6;#?SU|Fon6;(>+t1Mm^9Eh7cV9S zv785YbaaG!3_@x-IJk;^gFb}wnvS z&K+GpPA-vTk&psyx9Wee#5*u1bi3Z#^$Fnjfd4vS95*Wp=a`lgyc?u?DckMJE8wPG z{7%2rYFb3@U;w2+TE9O95Ebf>Sai9XPG*?XUq|FOlggVab-A3C?odI92q6UKiv`YW ze~R2)iWojh=3S-FcRM`3g-AqVU~lgLrCjs1r&Wx4Noox}Q~B$9iZQo%nTM7hICH@8 zrh4R!et#o&>aZ{oQy^K#YBk5U>MoGi$OJk+@tq4c-=;nfIq? zHsL}(A(#8~^gu0=)dOb_MQ4jS%etY92~ir<>F` znsuW3n=>qvl`JCp{VIaf{EMv#fV#czrvz~uYfTSExrR0|k z;CuC5RD)e-f~#a~;&b-qxq`=YdRroZGk6W|5|8 zD9Y?xePWH6F6}2sl4XLcZpCqZ8|Mt~N_NJ3fIH#g?9JHlhLF+r)C?l7mACbCHF#2|H4PFi zu&S4Muf*7)dU=&}zMgJfrH8m;Hb2A_g5ufVy^bU~?LS~eR%J-8!Y{UAC}v~K#zZQj zRU~{KRmW|}l6qedQ^cxXdtG8ytp{l?e#@R$qPSC%shk9prHWnGY%A*Ry4ce$5E8$N z)4z>(w3axommDtVgxF_xZQz#J3EOcA>by%Xary@(rG&|e_OMNVNzf&YtW=7jKS<4#(s%{-!e;MNVq(VXo7KpK9u{!J>N((0Ay*#wCep}rc zK)d+&ACwbmt;QKP>Ooo3%kTQ>!O-W;tu@dW=E~hYJG!(^a>?|E2$`!+Th+&QT@)<^ zQU8xA%J-p46FQEUE0%4O!C2n7BJ)y#IzJ1FsIqFa_PI8A@$fcLjF`dVA0?HiEf2ZS zSy~^God)N3(sV)=#fiXbwL(PbLRe2P)pbo-mY1>-xT++kge&0I?eEfsB_lUtm#O8JtnIp2OV?=sxAEM?croO@bdK79HuZo49L|sF#Ik)U@{1UK#;4Iwo|TTvF5VUrrq1#9W) zH%pT9AlHZ5!)9e~y3zY~-G56=a-x#z-$mRn1{FGtMF`wp%=6u;lII=z=l*1JQQidf zxo4@D+g-CPkcz#j`zi)*9v>6L5s)iU^mhk;#Op_c9mgJt)(y(?W+4N~Lo=_A8i^T%x99Ism z(6k*d-@MJQCf){ch&^?1QJ~uVE~-kj zmf7MatJQ)OpztHAYJ}^@^flRU%)o}RhaTCSFBx}#|uLQmnCyUz9LdwJ^7?`6Mul;+s6Jc?K&XfP~9EOnQ3?eBIds{QZk`^q~1IT?s}QrZIb zc3rfgixdWGj%fCYPWfH)o`|t%JC2rfI4A27HUe38apF!zq zEqw~_en%o}FX7>#6O)Yr4cFTb#trMuw`(=syU^XiWA&{!CkEp=J-fam#^-faj*<|C z)OK|3in?BLF!ES5cE?k0Em#AzNCsGO**oQ4UCVN{VpNUR{Q9-4*O<*_93CBV<=_Bs zEib+JJXfX<^XxO9%4gLtK2^RCd=k|lGe1gy10Lx zoHydGz3&K^3C<}YNZ)Ga5nmZPiY@!GU9X$=fWicy@}Xg8h9SRWD*v)|^4T&PFCQH; zE-I>`M6g^miQx3&Xm)8++nWKs5ja}RX}XqTGG^I!zf+j*_};+B-C^K!K7=@vL391~ zS!X3?XY|U2A8k~!iiObctp1$Z_%+fVX6WFhB8K|Jd~uAwUSd(%-#^qS#I({SIQE6@}C_95qWuPN~$L>VV(WiuzuU`+&c<<}>Q1K?+Zs z?3YdWg>^Uglr?t3M#vH;HcX#=SjB zHd@V`p(r90MUiEaTlQnSF7o_Y_Ifw6Z;=QDvtKZ?Uv|#@PcA95`J6Gr)#+YF1iPGr z?AHX;wh2jgXE{BvhcC~Lh-Q@4WM|gYzn$e>ZlfAf+&Yvro%4NqFSNHIY3*k~+i};1jnFUm(VacVntz53C?cL)1v& zvLsThIMcHXJAAl<(lImIF7m{~PxIb)Kf`n1{5H?O@HM{YT_56mKlFpdwxn4#G|iGP zeC6{z_nkkbZRVH-oL5vzo_P0DJoUa0GvW#_-#$Y9m=C=FSw8!v?_TI3NkZ>0GF3NX z19sw39Q#M?gtas|&0*R(VU%w@NXNCQemc_~mEH0e z?Qb%gme@EFLSQg{^H-I_ouB8iyk+o)IyjB$YbK56-_&^7;^ zFGd_LR!k;K4#tygTrAe-jk-0Kp6#xO6QsMHB)z7JI+0H_RU{jb^ewVT4h1;wTI$ec z6MdDZ4EZg`FjAQ|K>6&)JHh$wz0x{*Yjf?4ri{ls_*BqEa1PqoltdPUxT0=m?2h&*bwcST#FRG^Q%ArkP1hYz5-8K)VBU8HmU9wAsJZdL4P5Y$ z3YLd3;RZkTLq9{?9Z~I7eCexS=KuQK|IRYp#_vRq+uQ6;_h?h(aCuA!4U-~2Mw=%61sxg3h?!~{OA%Mc@T>`@{qb*Sqo3WtV*s+you^arY27rpIzea~MI zvf)yn?BUmua0>8S`09TNq4`ST3ug6-%H{NONF+1vCZ)a`DxTK^zshaJ2Ju(MXv;yh zsN~_6gm{yT@kVg6yh!4vi8d?8+r!i1lH|&gem`-*9 z#rtYK$~~w*F4p=FJ}L=S1M{R{93S6AT|m{dJK96i#(+x_qNI}vDZ7)*8P@J}m)UHF zI7i*ol$GLK;_&t{&N;sLr+>_MU-}liS4OxJR$)#xQeuoGffsJyB9GD=%lV8|v&8v? z_kvmX2Bny1L#)9dOV@rg26&wFj7B5Q^AF#y?RqWbEKKjZzSI}{z+ejor<^t8{ZX+qzuRcgw zmYE9Ko+WPA8wCZDS%xV5O0@7}QWPW;j+QHyM8cW;p*OCgwmuhc0F2&%X%QM`ZA}|< zd~d*hGg_bW(ra2%O6Yw>5e^ojr`e>G5?veFn;tMO#t<@Hl2W4d7ry{&qajJe7>Rgr z%B;T0syjkWIoP>M)P>6D@v(Y6gD!gOasH7YLgp*RiFC)dXywn!2RN}P>&?BRDa zsV3+arMK&ipxri#kD^f8wk4_jCL$z^E(VTgGuk2Gdw9v6l{IHyub28b`B(Yyum$Qi z&~||?C$#)@#C~owJJA{Ui6KrrbAWZmV$(A9p(b3}y~cEW74Iw5l-RVFat%ZTNA6@I zMl?@ESarv=-2xF|QtdEuqxB@@0Pd&1|Db+QHkIK{3TBHHQV5I1jOV`f71VW%M>{x| zZCbBh-RI?-FS80Ah4<8T%a#2rG;LtIvqucjbt(IdOF@j0uIs3q`VH<$bDB$zMDk!I zDkH95d5E$s-+`H&+x1RcS{5`-gR1^qz~AJ#G(2ck>b>&4tcocXth9! zl7#*}r$zR3h!IgEONUc$3Uoh^RB40qIFm`31m`jxG2J=9NyTav*qKZ&XZkXx3`Q0h zjmI=yz?TK9_BO}!mwIrl7#9=hGVPL52Cr8ORKsK=MWSOtBoNIgMpO3ouCQ7*cvms1 z#2Fy}ySDZHh#U!=ob-(8TQ1FKj5`&iKHe>K+Hbpx4%O zTK=MzAC2413e&Yz?Pq|W){P2tlfSu7-l|ve_YUtS>`kxJHn20^CB}I1idxb-y=1dO zj%oyn)XfZ&MCnSd?q5fuBlVq)^<(Ut)WM*ijvKc?RH3tkQxJ*F7Pp9LNmj5FOh$W{ zDc%)GBp=#|BSl$Tik|K^+=Uvq(@mxlL0Z{hb@^O!iRq6h*N` zn(ev-uzn?YwDohD(4S5!KVt}OO02rTDs-94?h#|)wTEZ=aw_V3<)EWv>$Lq!lADP>^I zoOR3L>=qIOBEt2n50hfqJLu(`C%lTrOI3SYRzV%yy!|{ewImgsj@X}GL84M>K?;iZ zK8ydeB=gQ2ruT{7^dG;_US+TRHA34EVEA^7n~}-RTu7S3wjs_ExGX;w`7nn1pPU zv{q%L*L|zqy#aI^4~L#=MF$CJ*TL1R4{-h9I_)arU5TabWa4(cG3#t}pjo!p(4?pq zzoD87nx<)J+m>*z6FWKGzsJ||^bEX8XNful=Jg6w5AXxPFL;rDdQnd*m;OPD+8Zv( zJAdfF?pl^*YfVcz5(+DL{ISQ`-@S$=*DE$IwxpjhG_&;xdrBRq&@>HAyP|Z;sM=++ zvqvc>S&i{ke-9z0(3@v<%w|XV-v?t_O_+{$v1m9PsXq)6HI%MDFwNC;l6!X{mqZAy zo%7nRCAEoBHRi_kM=0D5U7NDqY2236+jRlLQ%(>P|n02t4y> zcVu2=QLo6=eR`%Sioex$9iqd5VQ+hi+(&AUvzIHbC*Z@r-5ZC|rtIKP=H|dcmdIv%=*IxyfO|m3Maak}PzVR^oJ6BmPv;WDWscBNP#hLB8 z{0@5qU58L_Rb8vz`r_?xbrzoqqAa^W9kOIEkv2Se?5^-D^d3F7@fop85VCLNdPmr( zjc&~rG))UABVYb%jL{W^V$KMXXk6@HLxfiL#SoL;p7Naw(mky9pwof08@lQOM@82~ zs$zr_B`#}5jCk_F_fvFJVkoerjK^cV7@@5xol<(AIW6)iw9uf^oCFh45*ZVuAc;|G zproNpQ0j;-DV-?G&}J`!w&cP6M<@vt+7M#IC{DEBHh`OMoF61gAJMeHM}r8JOgXxF zjA%z2j!-QaRlA54EEgT)a>8g-(u9UqTUt%DqI8sWlvwW(jRcg2i;d7HWOc}xnrT^~ zTC$8ns|CqBnv|Ujr`424_a7$ATPD?n5CV0#LcI3ZbGx?d_1u4u#jG(8@vYTt`L&rk zE-|gCN;Hu)x#aa%R|>kf%ge_z>ZV0eN>PXjrw&V@w|Yp#9%(#6fuQk@;$BWTlVn-t z(F64|CE8%b47mGd>2D70zL}9Eo~;El#F?HleSgQcJyxvw(DlLMhPuJbnC|RhA)r7} zRP65T^Vp5Ysh5eeC=n5s$1`Fy#>JSf%aeOq>UQ$A#EOjr$b3ap-)Lq2*!rsaWG(7B z%eH1z?XtIX1tD|CG1|EUFA)eb4DUqDI-&)N(own+PeIpp9M50ImBxH^gsCz;xWZ^O zrfFL0x<_J;l-Yj2JV0Hq{$MxFv=e!L+fNA ze2r1L2O%P2O!xQt@4TH(+^&m(_1XCldlj>D z`_lu2fR{i@4y;1ln2?mtLN+;)9Oz{>1{;G_{adoYbb%4EJ2t!wMxuem@{lerXuBEv z2NRrwrft|MN2D(Fz@F9#HD@;>H^|sT=u#xm(OJv!@)nc5lKq1zMxbq1Brzfp5r?Yp zRTYYw!<*pybongVi+H;@U)?4{{20*+C{PzkX$hfb>`Hcu8@%hr2Z;3upB!zspy^hK z2yMHyINh$dbUjk4`<2jj;2p<{IZfA{V6JUaA9sSKI?mv-Y(mfp^^q{jN9HHykOhuW6pj-?!LZ_({~YfC)UU(SVX4jGv( z>pr2#K=qVG$61p4^pj>9k-y1M(zRvoszVp|{ zp`l4X$kT6Sl2EVIb={i)c~TcK^Ef6v`L6f!_`~m^trOk~yg0g$@MTG7La^b8uplvH z0QQHA(xCJ~uI5kCNZNZaM{Nl-9L`^6JaLqjQI;iDHKJ}B{Mu$Uo~o+-Fzshb-Q|tI zX;x2YX+$6R%6I;hfA{%+%fJ4A{uSSN;TuR1@m1bj2Kk>Q#hc>I^;FkM(1gH+`Qa$a zXuQX=&K9pR)<|j@i^m7Y1Jeh2=82Cm_ScX!B86N;R8>XOGv@9dh;*rI^kYJY}Zn} z)oMiy%E<4~HUjO4?|IMna&_lM9%+sdb=kiol8EAn6hu5hB1sZCB|P<$jc&b3Kn6uQ z6-kva-}2md{~SL`9L{eOEHN5SIXpU|8WkI0neIyIm=?4l|5T9xKCq~dc<%YH^OdiE ziH@3T$Fu5cR?QOCg2Gkz7?V$DD)`s80tL(1VH&7Lj<#v2kCt4iZt$V^K8tn*p$=5V zh_35sRvjXaE=E#H+Ys{YdP|p?;l=+_iXhIj>Kf)vN7tV{y%LV<9sD$`jYE>MuBPX! zWfpy_JZZu&ZF19RDUhLpb|&lBeGS$9h6?7NzP1Zc{EKcOL8w9vXg_plaL9Q(H@t4nHyQ4X!f$Na?Jh?V4P5JJ-`D%BG)3 zFZ@F-UCIc3!m(&uT$w|Cy$fg(O5Np|cYlm`Klna83f3~JglgpTIJvnTZmV00ZoA%M zoXG9>FNw&LsUM&@njNogqtElHX5;+t3|7mUDp`+zhrp_7>CN_jF<>rWn}Y6F#Z}** zUJJUhS!GY;rfJsV_tmPQYr!)n))gOq|EGBP+Pl}|&TZFFm1Tc{a)q47uscjbPqT~= ztFpw7YLquD|!E#yWK!8$lo|yq7Q#QTS?!47u#^w%M~Sa%Gn(-U%@uz z$emJMW|%|5qg1X$qi5vySRN%l_JL1ub$Wx)7+3eM^72bBQjHYoFlz~6l|kPWrxo5! zs8@!Fp4%Ro8Igo(BDqME7V`;`G8#>2nian1XvOpdNH-4qyk)&+AwuCjA%uJ$p+=V) z)Z5wsD*s(e*JYC^?|i0mHmdfs)~~Nd8rOC$RaFsUo~9g+$J9+t(`JrYTx3xnvpe3U z(kZ+CDj$CD_pwtRP`Mqf3ydZu$<>572kmf9@IKqIVq2biyWYCxMfxe;^f!hn?}+ij z?U(!0qlEOFvu=!{rpfhb$)f?5OZ3-;2W_x(usgUolgPoBpp4&~`8zcvvxtYYtA}{^gHIw!@k3ILD9L3H001BWNkl?~c~TPT zP!lvcwlKY2Z!Jl!^o~CtPbxody+oi%M$;EI5g^@3EBn(ASN3^u&LL!edbV8BMQF{y z%U^#bO5 zz{XSC&hRhJ4l%h?RrD|nG^8)P{tMots_yym!uxgJrkT-oEk%_w4HS0y^hZ9y2cP&T z1V`mOi^UOsRDyX-TB78zI75-7I4k=1aI2@@uD52%8WseDzn)b7H!OGJi>9WiMmPpv z>-@{3Iy#k zP;LHPa+=RR0n@up3~qJ`*^K7&Epr}~afU`a*-l8vt5RLmO^u3BmO1#K?OIYwRAt3T zD&jKn*p;XF*!O&zz0nQkw`;1Zq#F5tGm-3JV#WGr+E&ur^)?_*PpV{7!2P@l(;(1= zK;3l18Q{57#CrB2-Q+;KE>gEGq7Kym9B_4W;_z;q^d?B#eUt2xTk4b1@e^C60m#x> ze`kvb3D;|#LtNl|qAV+B$1RmjdFJtt@yPyDq-DvD+h-&dV_y(rOVg}?u1{RC4A9cy zEW3`VNlsWXzoslUq4&;=x52GIi5ow)>wm@*4ZN;D`VCH7dMkhvOMszb?|(Ho1G`e95z(_ps1c(~D+Bmm=moBJ#7Z znUawKxVcX>tclb99`1ByeJa8>oi@;yj#ab3EFaVE?d;OE%I^3EpMLg7d3672tS)I5 z2`|c|ghZC;nWj9sbZ)gOJpdOc-+TD~=7_kHH~G9k-cw&Qo!>3=y6w{j0cZQVbf!Y@ zGZ{tu3isXziYX<^qQHAk+qI|&qtPf&aUCys`05jU??--sN3K5(t*}42M(MNXNC=U* zc5d{TO)#4vR)Rzv9b#MBW4qoW+GDWM*r-}f{LhNyfx-|1b!bmcwKhGiMFw*7ssv)h8hauIxX=r@!wbfSl zlJcA>T0LD6_S)WPSQ6^4C7EKvUo*QqfiYP9ouxhQ&!r3}VtOVdLGN(6$_Ic0VAXY- z>t7X;b?j6lobxnC9lObW?+6qYHaxhCo=eV2 z_P#1iBs-nRWtM4*=WieJ()@^^j>hTU+&|JU7A*@ zEqkOi)IdPg2zA4a-D}Kl1>X0>hk5#m5A)cKr?6Fa=No*@WogSk@vR$c}F`q5ipB@lWLY**~RNS7;SuB@ad*A_V zu<|s`(jH^I5kLmG*mKt#5wZOa9@p<->JLnl<&cCVN+%Gsuj(y7cw$*TlmS!LeEAT7 zfUusPEKDF8<6=S=g`?#mrX}Tc2h&77Us4pxmC-(nrr_3#HIH0>FF*L~k5bq!^nvb9FqlrJ zESnVx;giq)Wv*Vo&gZ}Q??_F+XnEl3RSu7DQH@KALb!GO4SZGc@WF)H`~}b5Dz44^^ddRP}5PZ0W+NrH_(!0 z444M?u9lb!9Isx$f^g&N6^;*ANK-LN6F&9*KgeU(9;XOnyq4_jmb7)dy?Ne3khTrl zFK*l!t`8Tv10Up0&-2q@?(Qt`&ZVnlP0PLUIh=3F`m4ZypHgDETw>Ebq#rsQTp#u9 z<%p6KtwRX;??iu6Rf%zXV*W-y2%&|N3a1mAnepLgzL(E@>aQT(l%3)l%Vmv7q^V~t zRx?J0M=Y{j%*d9dn>mg{74H%Ay}$M8d6U>n3R_WHk^YsVxq>E=7_A0c4J5Hs9v()P z|Fx<#&S;dLmp=KNA}K}8FBdDCwxk?i#fplXi$iWNZjxA`){*LliR`kt75K{^`_Fjx znIAyI6raXe9cWkewkm#$SM2YNxS{4cIfF?=$*=YK$U14Y>$}Ie0nl#a9vx;8HXR~2 zUPJM5zg)-79wAeRKbf)CST^-Kdojd>xZW#%7;qWfn^q}u^Y$@^t2wQ4K;d6NeYt8| zD!-L|T=YfiQJ; zzcbfhj}XsnI*!_JC5r0ID4kw-YFMx+oKdk4u*Qy21c;UtG@FPF)(|pg%Pw?6Q@PBZ4bj04? z9<#*}^W}oc?wI|ZDRnzr18%l;8J-*M^~FO1=CDg_WV~1oe--OT!zJL8^mBfxr%=i~ z;A6VMQKxl{v_3G>n7v{o$8j|P)?r%FbPei^s+i(Ziw1b;>O(yF&@+7KeV=4`D{$@l zyGY3~zddJff0r)gQF69UrEdeYiT&GhLI_(= z-wRS3aqZQd>4`PS^;JgGL?*j?nR-f*@pwdMftXrwuw1S1W8n*b@>%}y3;%{{QZv2g znbkL$H;0Tz<9@g#SJK8XvFLh^IrLyWRK=%CmWbsUn?{FA4&EbMQ>*Ou7@6GsF_j++mhd*DSZ{gi z3zj1%EuEac*^$U-RMIRLl;a5+0?n#HoDf39mt*!P*QvXj4?Xp1o_OMYeDk~ikuQDi zbF?cs*t<>_>Qg+J+G+LaU_Tl)B9^UK!y2Tu4Mv-=k?=Zk)cYoeeg+XFzD6J4pTiJYNsJ8P#^0-bx!(2;9f# z6hD?yLVUq|wV;cUk@F{$iWU<|p^ky%3jXZ*=ZGk)(0xkP|KFq0h{d9&DoWD&vEHuN zRZ$N{K{Du@J4~#vGGv3l(~aSjK{n@TQ-Ml>FCtMIg3VZlTYUS4FZ1W${9|6e{Y|WB zNn!*uNFLl6r#pGq)&*_5$ipN>$zoo!JKbY8U$D2kM`&^tt$k(f5d`Z2+Y_P?eTg|G zV5~w%mm=O9)yUyfWVM`8mL*OKn$8HKC?isY2ln=P=Di={p{tLxKYoZY*O10j`Vrl# zrtpSohxJipsUL(H^hV14lxF{qs|PX3WBQuxgkBRtRq^8Ky3pS|M^4gC|R;v}GQAv!EHn!{F4beoB+{O9y&Ymodl1_C7XXT6u`?MmTEi`X) z?|bkOO4vwH?aT6IY6&TloRU<2#QGE7wTboiA-RO5cd04{U&9E!FF+JKm5Eq*PfQJM zNTd`oE16ag@t%jKT-kjWfBMzW^PQKz&aK%CjK>wlsAAPN)XQT;jiO9=Q(~7;GY+P? zN}Y}-+`f6pcswSFP^u$|Cs@{RH)3cEj8utnPGbT^3F8tco)9}i2#gK(cdyeNFBp{* zN*opnrjsk|O|SC!Hs1o#dwUznAa;LfOi#L7yEx#a&j(~=MOjqSUUp=8N`)-V>c#J zQfWy$o3S&wiU#P80w4Y0vrH$~c=*95sYbh0)tJJMsOx|v@Iuaeq(O^pJ{2YP41Upr zbk>Atu$C?T_7<*DF(Rf83$X{_oTgp+0#&8!Bl$v-H9&($!w=HE^ovx?>7EMjC zgbI5i`WcI?fqF2r-pZpcLQ!v=j`x-GNtL~c&3ebMK+b|l1TmabR4ckB5?heQh~gO+ z*ICxb98_Z-c;*2fe&`{-^tI1%>+o9?rSj5?ht#u3sY0P+_R9+Al;!b~vVzheAOwSU z;G%mfrl7!)ZB;AD75APwEW#)%V{xRqV*dGWaPx&Djt(1s<;Q=G8xOx1@%ub~^B8nQ z*MYRz3{Xo$H0B3hgwC>bFyXUh&H5m8OL@ECAs5pj45)MabqfY8Q7>T=+S4d)lm ztu^~e1VLD|H7*6zDb8hWv=3PhI#4KPg7%ZRA_9^!l_H54@1KDuXD7#|&V(R_hKG$k*JV(KA2O%TQtI=%g9~xodVQY)h#!6ALCu0dJpsY5#N33dF(w2=Mu+< zxA^uqzD7ad+s}Q2!XjcVRq2sJ@nwlCDxxS&jI?bc#D+>8^?boa?dqJS{>YwkuQ$N-frQ%VtW72_u2JcXUT}uUpqdoQV8T6#fMwth$yo&ez!^Lb5~? zI-(_#3JLqm(x)b+l<-=h$w=G5unShy-39=k2I+ND%UWyNf3ThwF-Xy6DTOK~ij%A! zQD>wS*qb<-u*#dOD9y@PE*h%pfTtdNghl)Rv-f6CmR;AG-*4@`&v5U}sYU>Wi5N(X z6bO=_D7Iv^+AViD9MKL(w4?18zxl87gJ1mO?&t_bv@EOD>L$BIQY1lQ9$6?9rkaPG z?r_f8!}7yE_vXz+0gzY#;mXP#aZrWKtb1>sVePfP^{wyA&BPOfGkot`KOoI^_}~Ax z|CQ~l7ddn47=AXvS}2MV<1A_{ohI6tAT;>dP1*+HH0Qa)=rBGaW5qPEQN!s1?677t zSbp=6`SUzshXLEr_wBtPP|D z$>T#Wd#eUB&$bWwF0HCyLaSEA@aY+gnJL^Wz`}A3Ka1q;Mz(Wsn2% z63iheC{*}m`ryVH#=6vc1B1oB3V&L z#9@Nt^znN+ed<2EkJPTj)~8T!@LghMu!@L7q(H1-)n*QfH5lV){p@goNj;97=F+Dr z5e$EvjF61bwjEw0;BYAT1WigL6;#Zcs{Uv>dl5+li3_8}qxC!PnESG5a|^XG<LHXttJG6?Q^rE9?2H~Z1Q<{ ze)>%OIW9N17LM~uG9kka;u$wL8BaI4zO~8r_9m0rE=_0%p&@C=Rg!iDo&TGNYRK?M zGOP|x@!?zN84R+}7BQkp7*(r$;~S6QQps?*%CXgxtd7@ap;EA=B?ODQf=v^+|Cw)d=Jb73%{Hy%=~RgZi_H!xk_gt54EQ1-DNy@} z2?ZuON~<(3d%dNgR0cL|!S-&)c;^Ne&%V!t&;Ni;-%?qNQ$b>4Sd?fo#4hW2sCLnsbKGwbXU_qu6$T zJt#}2({X=53HyMz$^d?8^_IA|Iy_8@4{R0}zd1E&F(~Hk| z$W4q`fhYxS%I+)oJn$gjc=%D?`{mDYYjZQ=-Y0QMmNJ;k19b*8q=jT8EFZ|)Z`kw(slJ*T#*>hwj57%dx@;&T6DSKj7`|i6V2=3PRQX(_h zavuO>$zCl}+d`O96c%f1e9D2&79|wf{kZ3yzYpEHYrrCt*zEg4+o*9*R7G+f76$Mxw&KDzt?gKCwBAAFRD?|+Pk zPJV-p(FwZX+1=h|ZFG#)m1B&@V?xN;c?uoYm5j;(ksjwwN?nws81`q$lEF5k_R~4K zeg%`*YWbp;Vt)>Q2>RD4Wtw7$0jCPB_xKpV6aYUsdI%0_MrI8mMZ}aC5kePOUtj0R z=f2HrzxWxe^Ue7doOyFgDCfY>IKy-r8I_sb;;iM|JMXZ4e6#P3JvDQDIQVvl&t}l%)kbV|R9m zx6ZxBN0-kLrNg=eWrJ^~Y~7r&x-!gOfBjs={P_Gpy?-;YOJ%=VT-zKd2la@Q5>0Ax zMal7#BQ!ZKUc11BOCRyp$|}Qh$g|)2E)U-GI59Y`ZS7#9qpAz)LQo>J**0Aa)a8)E z4KNAXSw~&Y&()qSomr|}tu8n7Q-8jf*jTr@EOB2goo?}5Ws0KyV1ZeOz^n_nlyb?u zl;ONE$4o374UHe7hg=GW?|hbGGKK6! zmja>cT*^NO7!i}5*n4wiiUKu;wi{#0#L7m+_RbYve(RT9y7B=#vnzP<*echE6^taD|UUFw%NW-%tSN>acW!5WvF>6-9#T-n{EENd?P@*1aB z-{eQ%|L=JCzQ@?!+$CtBo3#uFC8L#!I+UmyV({4HD2tNBY@zUI`Y@UQ8~3p9Tet?@ z%7X99T4^Z}(Oy$YRbMd1q9Kq{q6;23mp1KB^s~UpgUyI|l+hfD?+GX|BRstT^$ zyv}>Czsk4&=m(f$h-VotnQIGzTJhT41kYtv_)2)WR)GS8FLx}8l+X$AjD5z>nlrkp(D?_d}7x~K{{~I2C z@S8mT@V7Xzb{bn68lR9>u(rU6L1IEQQr88ukoNjSk`~zTo-NjXPt(05=gSGudz(^Z zkCMArHHs=m1l4rX81t-%ypb@pF<`nB>0;>l(|q?cPxH}(a)&?U2A>jY1hv`4r*cA3 z6s$dPhT{+3Pf&}fL3)AyB$+#Jmn^sDH(N4$H+Slrd}?9Vbx6@ajL|c&me+sz3(BZC zV^Fc6mM|Y-TPiJOVevJ;h4Vw@O5%9D?lS7kR3IdAa@ia8um}93# zM6=6JU;K~!KmYmP@a_k{WUKj*Yr7v4-ITJnlyylGLlb;9QZ~2Foad0oKRlz(8}Vnc zQdDKJ5mc39b#+L!Qh*8UHe<%VLyMBePnha9sov(T4`1Xz{N&&9^4ovMwXKg(*HH`% zvu;MyPO()Y^=g0I?tY*pWTo(<*v;ZYX`%MD2%`FF z>epTso+s3H$B}wqv-y(mIKDdM{M&DG^@H~*Oh33_q}RjNrH^K@y(H$mk!^Ke8fJ>% zQDrxO$P>}{CC8KOXm3)1Nfm~YxjUxPk=P*si$HY0>K3Do#HG!5`Q@vB%Ny_glu7&q z8BU;Ts769i&&&s=O=8wsk`53zq_88LsrM?Nr7Zej!2W!UHudIjy133(&EjZ$!fVEl zo6xc~*BqrsE~i5c6?70Q(vGTh?z_x_HbzVsisaN{i0o@&Knij37I zBYibu*l+VSpJB*{AuSw@mZ`My_n0CWfS3@G=jT;a8$GT|5uf7xM1SXtO0T2ouse4Z zMZtJHWp$9UHgq$g9u!P`$CKax9)BfS|JKT0Z$c$zEHMgpo>{3S$uee~1A2_g>WZZq zM+%e0reZrIYUDRR{~77viOtt zEtnDw5K_chM_Cti?Jiy0a%}Y^jla%MUi>S5^ZKuO{K02<o-zh>)@YsdJ9PRZ!NPdE_zf`^F=*F)^96tPF>w6cI6mu;^iAO5Q=X zN6Pohuq@c#i4;|qCaubX6g;*IoO|m{{B(lEL^o>~Ry8pt+HUqKd~;a>a-JYWIntVO z&e8Z8ssUFDF}6UWDoTiklq#HAAq9t8OOs}ps)KTiAOHQo<@NXeGuOuNLNOs3PunTR ztWa0S5gp{5SA>`vV#+!JVjN0|m>QX9>H6iw2BYkEe3|PB83WcVaBx*c#ZzjawkeB) zr2=go!jP(7rSUU@YnaJRuFuZ%Utaxde)aB8@x_c#1a|!lQ`Ce6Au85YSW_~n*YVTH zuoxYnAE&;m>;=^1%gCvh;gq~d!~?G@F)=7=QcBMmlQ$W)1`~T1;Us)hg=6LxaK{lo zeMmBD$v1z9UA_TS>ADWYvUcJWr@rwpEf(V}p=(gU7{yj4$z(q6yd+ea-*PE)w384M z42K}Dzz4ER#h^eEy!-m=?B2XVDMDQsyl-)(!&plbJYtSG`d?Jdc7BE{3}v0giIk2Q zJ>GjlQesrF4y>W^Ew)Tt-TaVWz4D*AHu;FojfrMPlF%D7NVaX$64Vh{#}T(91bDGn45$)XUiI3b?pRCJ^wvgXR?xA2uP0$TUU_g8I;=GNHi?CdUO2R zHy*(e(;fzGjG->z%7u?P|J!#cEmQ`QZxB&zVdqKc$Gv%%Gukl}h{hxUA{42O2AP%J_N%rrIGMBYA1a+VVXhEXr zIT{h9Ma={?lP@V?+5P$DTupLmn9Ix!mV4}7<~Cn=1dr*H1L%5}9>l0ukpN5ych*x|lW4(;Tz@3KCVj=<&=qLG~RaT|7&D z#qw+vh?+2=&!|=P>7z&Ca4(HHaL$jSC;%aKk(h*UefN1}ILxJHNR$Rbhy;xUgeayD zrtAx*7R9OJl0j2eS1aq1q{6gmC<;U2EU*6RR}2I+c#I@s=d)9}wM!4|XW)Bx`LYVu zW{9Y5GaI8UMp#>9*-`cE%x*ATGX&Y@)pvfuFMjhAu5VwZT(6i#Pm%((V!sT1>NPpE z|GGmoYenbGHlQ*?rM>Myoj#LexeN&$)Yh0E%QjrvH&PQSz8|78Br3!V=zvGXwp4;PL=$>B);+e#e&XmnY5m%&y{7?M5+>=QqyE(1!>31? z%prWxi3x)v83-CkDArmEb#!gu^ka{)a^@a<%-)HvfEb~R5iw>Fprf!D1BQf+78@<4 ztFTxif+P}Bgm9wav&Bp z7&8&YwlpAzGsSXHSwyrX@-X;`9GUDBTD{MM}26 z-#eUZ2)f0u-~JgNUV9g}9tpOg4LMkQ@3BP>`>5If=OW9w{f45j-(x*4m`rnCNp1=+ zLC;Si`COTV=1!yVM3GSEZ~gE)5xkA zSMDp*=FAO~miW~%cgQsvTAx5H^`OQYhi@A$eewyH&c9D(425;5_Ud59l2ZB<{m$x9 zfIf(miWrq?0Dns&v^Zv{|fbl!5Zw$b_r3CYUS2A@e+R+@u-9) zT)KxUv;uWKW8${Jx25BYGVA{5wP&+vkpsNsL9BUbn+I)(h>2OfFSQgV8{_)6p;E`n zaKL1ClXu^LnGY_!OJK&zdPO8LZYGpv`56h0d?`WtA6ktZVAMRBdMc|~n$dq^8J}Lx zu8-Cs3Dw5r6q$aUT(g5u*}FVb6+HXh?_~Yol)LcOW;{Cfxb*(JVLzsrqL6$diDXR? zYp{hQXu>*6ySu|HFaCbE{4IWRDDOn?NPU~~|`8PepVJ09Gkc@## z8X##6Noxlnli#5tkON152L(w4Nj=YcnO|*jY8Ikgd;i9eaJ{TkmUapzfSr(JlMt@q z%!Gk86b!(x;HQqks^#+L2fY0H&-vu?IaFFyJBrefQaF(NnxzTFW%%VQ?m@6GP(NXe z>A#*o*2AUB${a$quic<7stXU^%gBLpx` zAQ@teIQ?{%4tvd(!eu&MVGKz!RMO5`g7>Vf4cVSeIs4}8Bq!{)EmgHb@(@j=a0X3z ztT!%6oC>-(1%1Z{>rZP&MDj>@E_~yGpIy=~21G;Nw2aB)(N1u6!ESSn-+uT8ul)8! zrr|PfkPxw~j0VKy*xA}Vtz%452mYKFn z+Ly^M8hbjw|1wtvO@!1js7lmANFFB<=PHaT>0%%@EoB8)H$Oqo3RN-W^v1)S*f_(^ z<}P(H*B;SN-Q!=@@A`RK2qLnts#I739tJ)%0LI713UP}vGYE7>>5LML_Tni=^r<@> z;UsFn6hhKK+qM)cRK}tyizKzt@W3OFar&W$=nRMvx=yLYAt5bzuhM5ciXj<8v=D8= zrw(z7XaLRH)y~4GD$uTBHF`dN_YKb7xJX=I$4_8ARvb4qRZ2LG1QQ9pM!NJ_RhHRJ zcYKgmiKt{7tDGHLl9-$!O4ikgGXGiakz8Fc9HJjnmrC1BNk%Cu$IaO$p^m)%!5efk zLuyCF&;`LJp+p!;qzF^`cUoLWqXwe}n+j|y5UutoZ}Y$+nfbw&&`jJ1vmAg!MDwSc zvr+On$~@y>{1PL?^b!LJL&Pn9A44f4g0~nMAZAENiccLaGt_ztQ=)BTWli|_=G(me z@hiwG+?;Gt3~G{OW8G#trm_xaYNoBHss|*!{cS2c8SQ202cf-0Af%2cidaL+r~fgV zH3vb|JhuD~<~U?9;ExjOLlmm_!wLCf@85vekDuV1Pd!bi5Q0(_hQeimpS`i)eY^hN zVfG$GtMjAJOQKLFxN_-3&VBd+$r^^$5aWd~>#{l+X`y`{WiR7fN}&!3)~x%o!+F)3 z0zal+>A75?&X{(>fq31-j5UDRQ;#Hipjhd`_83vlCG zVaT@%u3yFa0{r}z@1K_XX!C$SXRRy;d=Ge3BZFbZ*3LE7j<4{^&5!wifAa5XWWvNx z@Tmh4MxzlS1iWu>&M|G9-|H;N0r>B+EUAz&>PL>I5bpwvbB?qvUFu-=OaU}_s;c7I z?|+}@a=B|2V(_;go(z~0#@kcgf8`B|CSc0~Ul?>TnfPfA>)ffv9-6CQZjov0EpYj{ z@?CDIiCTOp!BqIzf@?6vgjZhwDc5g)OzS6rA!$YGImLU;w|D^ZxI0&%#ixKPhIC!T zhbb4Yp5xWG|DNHhVta(Auw@oTDhyR^@Lh|uUmK$ENUkJupnmutv(Fu^J2+U+;fp>9 zroI#p2BMF6A9?VJZ&9B(hI0<_!mMd2o!oX-m5fk1!?~AVMYqRvT>}LabwL;Wr&u3z zI#`jwro+GhnP;nVpvIYJjB)9CGfN^uhDd=A1{KG&n=o1x&RuwwOV{6K<5WeGkj;^` z>W@8)StaJ{d^g|tl10NN(_PBJh_=aN_$#Z1chCQt>*J3}W=0?}Ze~P*x~{Sg4IX3o zlE%pY(d*%Tfm&6M=un6KUe$<@uxcp!m>YVCD@wwNA*IBL2Or?rgAd|kj}xcfiT<|A zIi(9qRh4}F##_uTUnWRkN|*gORr{vWp1^4?T{98T9fBY&D*LpKO&@}nxeUwALlui+ zh?28vTa0Krr5GA6ZGXToUj7?aP8znxmnf?_Q=!a80k{QAA`58U-Pv>m#0jQwG_y!G z91+5dadVAo)bQdff6KM&A7k8{NFjLN(zY`ORYCO4*A*Lcpzg)LANU8!6dncfL$Qi9 z_rJ^nOP$HnsYO4rSROmhna3YzGV8Ed3TyKe;cc!?GVgl#lgnH?dzL{dcsy-vv;KnI z8mqH&+1)$-{wvsTqc!I*r(3`hOSVudc>^;Ru|gBa{QA}Z$|T;PW0N+FNk&=5n2(zO zpI(!$VX!hHD0uHd0!e4Yw9U1f=Q;PuJ470Wt0SB(nayUz7#R)f+#vsY)&r2)lVIe5 zcr*=m*FYb0Uh;lkYt|WvJ`qaC6W@OU>uTsb&e`JWb~2jWQP-3h`ox<*{}m~A;FPEd zYw|cdOBrXB*gIcj=;IDQ^9$YMg9ZRb^RWkyECuO)GmVmHLrW@@w|@HyA6z=ev11iG z(2I>vr|<(vi1n5wGoUDg}U7xrip>@co5 z^(_a!oZZc{wRxjNB&(?nH%{@$6HhVrfuI5NCFlusK4HXSQ{vi%Pq=pB12pB#CX2-{ z3Gei9z+RRO(B)SwZrNxIaw(fZO&A-Q#iOx3| zDTu)$6d2?BpdYe;lC)X>LIB*p=HlHNsJm9F25ZFV>HLJk+Fs^0pFb%A*LN;({?d6| zT>uu}ff0AxJ(_=XDz)E5=U$_#_kLMI;798Y$>n6;tePiq87pIkubltr!bv|h-HcbfV?_cF@6dmX6O=o{)w z#tF7?xPfKIZ*hJ5GPY1+3=~C0RSv$+4}aPKC+EBOXa0&u$@U=#(rIB38$}mQ#5wiN zrrZg)wnWs@G?C*EJrEuAF_Jolh>JDP(#cIbhkPKsEo& z;m}UV?sb5SIHadY=78eq(u}HxtD6^?bT>g}m=Xj>2ue%_)w$2!GTyx`B9w9KyLn|A zWN)t9`xEAI^gW~7ph`!}V9j#=^1HNYhuzuD{@j|cb2j#*5s?!ja!U+mIf*$6)_49= z`U%9P-my_|nzJ#rhJaG4Lz2a=Z}9LlPt*9WA9%Tqd`5wkvVMq(P$rtq8+`QE+YG8Y zW7KLE_u1Z9NFs>IoMJf)S3t?ew7TROD7_2zo=>2Nf)!$H7_1mBUOrD7wrO;{Fn~?b zVsI2ix81L#pon6@tp}*(Np2%2#-i$a*QDUqox(Od# zeiJb*UK7p@_!=A9$Bn9P9EGpD1^!@eWW2W#(`VG0NIk{ND43*pD?IVsv#cIlp=)pd z{UuwzWg&D6rcM0zl~>rkxrMcMkr>Q3k9l^j{b!DHNCL*E#PKuyp(+TLl)zXegceNT zop;`3d-o>R<(l0XgSD1YEH){sagPk34$-=wP@HdoE%r-tXt|TmyNd6dtQ^pk$=77+ z18N{yPh^*~AHK%fkKW4ul;Xb555HlOvOXUlUl=PMFkL*N>m3f1ZW->#K{58hFw1Ou zO%#I?)4PP5lz8+Tk8t|T!?e;H+AacqKiWK>giB_@&X&*EBy3UA`G6{n297ep#rMy1 zc{9?Z!;9W1O#d3Z%FAtV%d>%G?*cbhqL)MWR{^)GTwg7q8X7-roE6 za-O6t)GuLagW5CF-5b>5D}xyJ@MsqF&t&}KJ)J4h5J3W67}NMoE?xVCYFHzmI={cy zwQd5kYD@u1^FTrGT^1$b%^r`{syzzUcldJ2;F7@-aVg<6<&7Cj#A3>daXY0k9a?m_ zX2zMdlic&zlk8%^70kRRsUW7n2L)456$4_Iu+d;6n3P@dkhsX=6_^o7E@P?PcHqQ{ zW9#Y_-g@H|oCJ&plt4_LHhGHCfRG|J6-B3?f<*3ItTZ=DwO5<&w=xV<#J0UHl%cBD zxV(9Tv20<79c_w~GQt>>acUb-s{|CESjc(?)AUCcx=OvFZR*AhN+_xDu>f0VKgAHi z4iVje?l>9-6t1Qk7^Y^Mt*}dL3X(fclZH$}#13H6ZZqz7SzABGq|LpAAcoKfY}8`a z_TG!0LFGxhlPfAS!pDM039h7#inx+V>xpWJDs(<`>s z6Y!F?qDw1S6hGZzR2Dcf+}ygsw4Kt0mME#OTx?H?bfhGtBumiotz=p4KN>b!rb~;+ zs!7 zOjOolt;LlEVl30PrSsty&FE!ixI9>6tfMF#g$T|-oMpW5t(RWHHxsOYh+@lJX3n~n zNVX>`jJxB{y>P3_bdNG7HW6dM8o~QO6u7#1on#t1+5=hm=j$}@|DHj#pst1lA8ES` zm8h{`4bJ6(4w1y_uwYmurjr|NZ(qZzFs#?9iUHkp#$Yg@v<5!ZM*A~HA%Uw6m)JbIf|aK<4iQo0&drAx{z!2ORt%BeH=Az~?=TM+14V#xPbK(8>F$TIA2q7#eXL-Eb z6Ez*00KGTZV?I^x=Aay~F41;lwzjYF<*+pjT?E8ZmX72izH3RorL;(3R`dkN-3Irs-Oq?s*3G@FoBOD{V^m>-S3KE0a6p~kH2pqyhNJcWgJ2!uQyR~rP+CXZ zMn{ugRlfP`v;6e!*AQ`3r9g~*;E%-UX?#af6j&~7`*rSwagr-$pbofywCEyGmk+Q8;{+CiIQN9kWx!gI(BZ{ zqzf~mBae*VMl6Vuur81!vq~DDx!9wspsErkL@#D$V!`QD;bz=-Mf5GoT)fZWDKkJ!}x!Z!&Bae|N^B zu0&`_sRNVn1h&Rk8T&0(%o=MSm*KcE7Sw29Z)THgrh)xs8?LlcixYk<_3v~ zC8bPXGExvkvCiR*7 zu(f@KB)wpo*xS^YP6Y8Ni4-$eeN^TKT5NY{8;^vV`%gc`H_v>Rdrv*auw11L9-Luy zb(NTe7!1v%;n+&WJ;xv5;fEgQt@nS*EARcBVyz%amt}D=WFB|USd)0p+;^JsRzt-} zo`32)Jo~NZSQ)L-wJj+qMe!$GyYUHUKYEe3KYAI@nByl;(ls@+W=1seHc!0=;u4$4 z?yGE?wxO;HP>Y%ZpE@+oXu~c|yThQYvBqI~_KgCn=8F@E1ugP-{`{2_k%A+9`tHK1 zB_|h)1g{U?Aw=q;!nd9zh9p_(zft9pr=I5csnc|qu22^VRFR~((l84#+i9hMNrH`i zbY5gGxuKXsz*-?B@G(G(97&X_MXXA<2yq3Zhns)>(U7TEKJl z+6`8#(|qrlf6g=C{65i7+1cJ?Wmr)TH`ux9c;M6@acuoD8b9Qn3qN5zy-A|v=4{N` z=or6yLvi_DjEibdbZ<0VgJK2--{~S}l_JqG;pWyQR!^)`$N(|9yqlvP8K?eAn<$0j zF!_cat=kLWF*2+NZ0_!0tOb+VAWe*{pFG7wk3G)&H*e5I#iR%dg*EK75eO*K>|dq zJ^7gPZ@$U(>sJx0Ow%sL2(gWP>l@GV_=7KCbihh|FV8&lBYye%bws@;V-%00Sg;Lc8&Iz+g!WDwC1`#FrDVMH)#F=A{QELR_Af`p=%#g7|Kji}RrH!3i zr>aK8V2p*M)cR1bY`?YS{2_{6xT@r4O4BryMU4{iDI#JJC#)Sm!I?)N<@{?e<9BBq zUmGyqnb9_$)wP1{smB!u#yNEV6r?eR(1omNLC)Bl;3)*0Z+ZC_Kj**y*Z+#?q@(E? zTs^=QHPg`GN(1R|-k+A*eZdtKV!=}ig>U1vI*QrL!3 zWoVifi52&rc!ocC;-BNYHOi`FbNfAh{nn4Uc4qGIW??^!?$^kGXMaRbKlRWa^3v^>c<>37b@9^f^KPIt5J1cnN zp@yfP`X>y{kj{G)ha4C9O8@{M07*naRF@Ks1(K9f4Cc0Q`p-Zn$nyj-MW)jZTMbY= zK1B?cqEIehxj=lZp(wIap*5Bmm8x?1E|N6x6-ri{WlQ9vbyqC2Z^9~y_Jb*V*_}H; zIZ7xA<$FK+W2PZduddQGfzrV0ux9gyXE1R6gOEA2q85|PKJ(UPa{~IbKqX-$VNpK* z?K^BsZUBD^$-5VRLu0QJ>us)XeZ-|3SErh#6FD@9r=do+LJ~Gfg;Fm^KMX33Y~#KKY1K z4~%d{KQWm)q;eP%DP{@V!VRg)6*g8j*xLGlx6b~YY805J9j1QDsgoy|jIZ!FKm9+6 zD6=?YW!=$s9lKYzaMn_}{T1Qwl%$XjKprV83#vtA4{ug1q32DD25bs6O_T3u)rBYJ zGN1ZZ&)WW8#BG6Pce(cAT?2dUDTd9>Rr^8jz6-vL-zm9y(bJhUXS9Q3>pb|)ZxJw9 zR}sAu+r+RGLWp}>Ou_4L@=@xtR?fVZNk`u8P7QP>T5WJuImsHYFG3Uo^ppb z665DTYHCQa4-Ea_k1H}pKOe-nt*IZ0RJC$re37zJ2Bjl;@C46|{6|PUt zvUU=r2#ni~%B|zoVNAuw+HrQar>u@n;mn9*YsXk04!Q5%Q(U`vnaQ{#g^H@!ps;JS z0lF@L)YOAjj;)*^`VQ4GAx+)k|hK-fs zm!tr4&;fSt1Rs|s4>Nvkm}k`xG|@EE{`-5G^qivi6|ZEt8x$mu)*@K%EBF>dIK7bo zEW~P%lnX==BxQ}{dgCN#9{(10G(v6mpc~dDlWEq8vGc(J&6%<=*T2qpn$9-_HONvO zAFW`FGEB<*FTF#EAOTpcB3X1dGDXq~P#IJ3g` z#N)~l^Cx4}Bz)c_GPw5!2t;wjXB6$3T^f@15uRm8%qk8Zm|x5)ySU5kCyU`ZMH4 zoy$0X&_oCw+AHa)3WY7X9fDR8=rmE31AHuLXNJcgeHP~`thJm!`yQd`NM0EfD{R!q zI5t>gU@B;pfhll4Qlx^%Pe08M|KPvjvFRs>|t+! zBWDcT(-}`a{~gvgj^k6LEDPF384QXXQ1vt6*j`HWR(W2;_us3rY+bp+tFOLFSy!Z# zH;uB$`Ve=#dgCnu^gKW;Kl~{rT0bLcAQ5}-mfI}+)sfN==kBGQ3@PO_4?pk(5*_Ld z?L^oZJWLT!Ql&Ngv>~=TXxJr8b~!aV!NbQN<@HPPWbSnmslGr#@9S>@;m&q7ycLSJ^mc28&MT2Z0~G? zcYn>~?)%U4(qH|1 zTIm?AJ2odCwY|WYLNF0QDMG@eDx+6we(=3NXJ;JQSbd29_J{ZJ@%3}O_1;TJ zv~+%kPaRb`z#&YVfF@zIQWL^-A8fHd$Bv7yes(5pmjd%GE{zCTG-2+x)0vY;gk+;~-d)z^n>{kQjE6BEh6AFc^wn-y)iPKUoAxLTe4) zCORx4o-1#@io1T9Vq=3zO4#5iixJ(SL+SeStXV%oL@-uJJ`mduD^OXB?*q13`X~SB zf?q+Rr?Qr2dYR+*pJr5_#4%##FES`gMsAIVPCm(*laFJq&52uRF`|@ZnX%;3GL}Tt7=HJj0bCU7L`yB&1x2RWgK_a6|$#cEt1Z_kY1w2t4=X4_O!q-utVNwG{+95=AB4_~1NO&i$6PKYRg6ij5ZEMJOzHZtpuS5}(+Q zjc4umdGs}lvO`FS1&zG{t}Gi2{I2;l2!fGB>4cTh2qOcOlB?H0WP5szM<05g(daZQ zZVlfBP-Re8q;7}Jtt}y=P`3b9KjT&Xv>@0HS z;Y!JD<_SR=j8=%zBXjIov;Ot1bL(X-?}KOOvz~<|VDP}e(cGQ<_mE*voQRBnPdLlb zy2DphRcvit=b7(3Pca%IJKK!MiQy1plErQAdh51ma_RU*{7>n#0aayXH00{FYrOHs z8~oWH{}64mJGxj!598#l&L@JiQusV8v(}5pZ{ndbM4 z?JRf6rS>Xr#hpcpq(tp4x2D}ai`$Jx!!R1~!2V*xHvA{dz%V?p;qiC_Y+wKz828v? zdptA!K|Ld*TdirewpMGUL{Th~qKd1y6>H1w-g~x)c>N*Hy?L`1ic%F>rB((I$jYqD zJbBNJ^Tr#``@GMi6u}gfE9*#~C0^Wmho8Lq6qhDjtRFm1nU852kBZ;97>?_9i!5rea+deGhBG;HC}w>yF763hq&kN zhZqkJvawTh=b=Zq>%^n{;78x&N6&p5Wwy!90P7TW*HTY*h*qPW!FM|t3vB}VR5lqlMjsUp%DE+6cr~{DYc{He(U)Dcn=SI2xLXh zPQA@XKly3C^@pFMbfBFkgt$0jn*dg1k$}vZctHh>-i6du1f}%is45|n=NX!Y)6YD^ zZ!{f52)aXAa}_OxU)EuEkL7Z;oKNFSmL7f`Q6h+r*WK|Dwa`VuM@>Fh$BRa5VHPST z-6hVv{v^CrplyLsIa;6#6%sb-!h|f#h+3hwMVSJn5+h>43R-M9S26EZP?i~!YKGJ; z>%%cCx#7~z>wM?ww|McVr@8aa$2f8PL40S(eZjAPfv7d^O~vbVc|DiO5Sdy0oKMzoe* z-t>KKN7>OzsjdBtdVln9}a0-lC_p|i)<FmS*A&(Hr8<>dwbefCHX0Ez z%93R{A}TRNB=o0=>j~D8K%0Jtb{L(ZwWaI2#5Mt{dW$B`=wijBo-(Z`)C5`-J8i?( zbcYsARt(9C5nc>+6=@oQ=wOk#a#fgSbuh-+j5>Ixvni8m8|@7FN(m*LpS;B^p5y7~ zzRv&mr~jOnUwW2YYu1Jbxa0V}eDaCU5^6)&1iEI1HJ+l>T--dzcvxa3aq|&PREcsY zdnl3f#+GXZ>Am+$mUX06pe)Nilcx8WR985?yia3O1DEC(y8J;{4$@&iVDArWz!=U? zrrdGg1FRlAigg+7ELGm;c(W4F(e!!=eGO*bp4$iM`C~EXciaA%ur}#a$F|}0lTQ-c z2Cda!!Gz&*;ht`3L)18zFIxSz1k`n#CPb9fD4kmK+cp^zD>O((2s(*EIY(xPU`j-d z$@BrVT7$SoQQU^en90mAnF=u$xNLxPC0enP34<02StWgEc zP7~RwJ#LiIs2zN~Ln!)YW;I3bAT+&LZi^~@Z>dGv7 zKS=Kn0Id}}vngI+bm%A_|IBCEsavulXEvRswbN(Fkcx@(ws9qUi{?spzi<9{wM~c1 z4IxIXR=jljH1))A|LjcZA2t#WyrPOqdxc*CQm0V z-_o@eUDsiCj&rLBC9TS+V#~yD5%L*R&U5m#m!Ap@h#||85rT;7`PBBZj5;jd&nvHU zUE`wm-d}LeEsj6?kjMVG)}pmaa847@G%;}BqaQ})MQS523l`!wi@=oJGm<6OT>R!L zq^bT(S#H?bnNb!6nbQ39`4`xF>m7!LA;uek)80~db6IM(FT`q12!SFWW0a$C1ETL~ zj)v=c{h}gT0$K~ctuPYl>TL{xQ86Gkme59WTi~?AL`AM0PB|nvtS)JTrHgJM@u^LV zvK3lSP-=?R9jX`bvO0sPsGBBjKUy=)3t|W$E!uRnZAII4I5Wc91I(rl(JPoiK_wG* zxWKvDYrJyq1vZ;Y42Em0tsY}G?TFqHy=8TLkV}_#$n(LTfVi7E??OsfLNa>s6|Z)S zf}$c_S7U8X+r{LQ*a>-F&~*Xl9BtboB3P4wiZ@(_cR52BkqcT2=iUvY-p{7@$6_sn z7GlI0LlDb}d+w(=a2S(iGs}r{M)F~fk1aX|+MOLrhMB!E-s-!h>18~r`Z5a`;`Wk_ro7DGx` zlA9b62wg){Pw3hT>x3*I$%&5+J>V7yA^axPt(FsnSrjvS>49g}*Sje3i0Twn$TZ=AnGIasG^ zTAVFVra-F#i5YbrlCV?TEM@P@cQ}1+FVlryknNU=kFxatb6CR~heXZF$~s27UZ?uX zEL>gnG;zb61kP#T?6nrbevsZDdlsYgFox1}L2>;42RL%_Bwi(hTj`;XpdqSV^H3%H zOq~t^ni$c}(siCp8*CFe{kc;E=b z!73sas|-5C>wNDtd0w)$y+xK~>`X7>{TAMDbK=~Zx6USo+K)>aR(GCF`nbHj{NO*UkEGD9h$qhe$GO|0__ ziXlf1-AP%j({-@3v(3)*5>2}erbgL@=w~$T2E)=&l$L6`&1`#0mSq(A3T+c;+Xkf+ zqrAWxLkNK`1iX(JJ#agj$uY|8;?AP4SxnsfreFQ~KUbgz-ik4XeIE7x_*vOtGJFwq z&=_5?-LyRXu_p+IURBSPi+B*G!q$aQb4qme$8kUaR3K2_gN34ww;K;7eFkop5XggszK0r{Oi#z8yd*M~e zaYoYy?!W)TG?l^WF?q3)@N)5~(Z)cKn)US&;v3poLupr8AKi|V1Nd1%XbNKMsH^l^ zj4_KDW26n9aya0zM?b=+KmJ?%`p18pfAiOVj|Xpmj4b4=4NIzxOPHqPfIC3DrMUOl zBmCAUe}@&fj@5=&etMcWE}p?tFACFM6kdU-4)BO-m=|Ri?2|qBZz<3&y)Uu1;@VwH zI(1tHN9jhauO8{4j}JUf2H-8*FZ4H*Qg{8bEcpF#6JQSj-#d@10&6pNs)~Cbd4vOp zj?$ff6Ws1`UWvixhRqo$g&KfqXLh{T0!*0&IVWJEwNz^-q zzBV(Z$C>BKj%m$jM6X#{Kg6IMqm^UsOq3FrWghkW7Qo*~7&{PSe8Vbr*M2zKAMYAi zdMrA3hOYH=+H&~lF&=vO6i85I?ivN z@=(60~B@uK`-P_FHaEO$3$T_P(yqT_{>6sp;tBA zRrlH-`y(A^qfePcT~%ZQLzb5uf8c(;{q--ANlc9^F?lm4J#)jn8*E-sjOywTZD}k@ zDVkY}LtzFMnu@p2{De!dyvpIb9;RE$!0BNV)8pW34xwOn>-P&u*IVd55(#J&(syU> z8UqB0WXj@$p&YD`nG$K$RT8#qwMxgU+}!ke*R@z{Y1*0`!|K|Q=g$0ygRA#&;TQz-DX#A%=?a!N{$-Ej~1-u(!N*KWs)P*)YvMz%J0*qKy} z4=BcY$%gj}+M1!4B()hYoOy<`ub<`QvAa2W$DL#`qn&hUCY5|Ez+?!EfVQj0eMh$$=<23-QwMLNFbPS7~!t~|)pd=jv zR7?+-=>2H*D?DT_o{nZv43X&ZUErS2ev+L(`~z;QJj1Bzv}M|QY+llZq}}1$fJHzO z$2F+k2AD-4^?h+hDO90(dv=L@RMMS$o7pR`GP?h9-kw%u;~`z_=&G6`&&iZQ+Vr~v z8sGce%^x2_;MOoP*Ne@=#@$SpzNsa|z;HOA@ev<{%#<8Cemg&X_64%x+-avbCXT8X z1)9rj_L(4Y5f0)aSf!sSbR={wnaeSvsOy>-Yo7ek7x}pTH#m9MJ#<~<VOp(9kD(v*Y%S)a{j zRugIP@dD?nOT7KU^L+p1lEX(1aQM(+@~kA!bJ%*8OP4P2##^tjv2_legR%vUuL&y0 zC=F4O8whPpVN+ME?zPp!{9f;A0gnqj+V$V(`hvD58CUqA84Zsxoor(>@O}ehR=DlZ zada#}B2oHUO+q423XEY7wtcM{#60P}R^o7ZOM3tPuSF4TNJ22LEOvWI-X4O&h{CIV z+aOCJZN@Z+sO;}%D=fdWB zx~^fo;y8Ed9lrdvukyylSD3cj1YHw#2ZY`!O796wKBOj*jv8YP+Blk6^ZNNM-n#fx zv`!XWA+#thSZgr@Ax6bcU89`cL-V|ni+{Bs85co(xt6U(Eq-33=+MNd+1{BZ@5&I! zb;fu&W>l=wRb8)V^MQ|7lv3D~A*q-4!_fYC*K8VSW1z?jLgO(;qm3bi$cf{(bKiXr za^^=b5{;p2JjNA3K#350OQWB~uA2uQNRq1akr(jHGtaQIxq%&yh%qAGF=`dM_QVQR+99lcXuvlSpHX$o5-CXKbg;X<;KomhO z(2>ifJHz6mwt}`6r!5^V6%{VC=s{#!UEcFto}#$Z_T z()phdrD1zxi?^P8lM9>gQ27ZRH7ym<$O3wa3ul$x;zW@rhm^Ufk<|_pBW)Myd`nrB zIBPLFRh*46Xf4E;V1^rG8KHLoxkt z@2`)f{9Ylr%jyv?0a%ruqL=2C%ei-A+TrD0cCY>Ms~4-$V+E_%zdBy3|n=L?F*Yq zc4=+%hhZodwCVr=AOJ~3K~&zvkRXp+rO5L0X%;7;Ax+_i+NmC#)h5D)OF2Hy^S&&n}ISAblQ!*np<1#BEeCm*IxS$(>F|o5b(YN(Ku@9)gegD_dH42h1 z0ce}{;nzfDvLA}}$GZ*Z997%k@@$dy28pz7%c&1Nh8_-Sf?#_SS``CXuQC>|Voq_n zM)e#6*HOrf#Trf5CDEUajf*_{!yhtoj_6y8EGPEeZ_eg~cv(SB-HMi7n%HBgp{If+ zygtp&HyEoH0!+?kAchA{KEm-MchNKrQ3aG$$-P|y7*NI#WZBI`FM@r#V&6t>GrFjl zH6GELtSrE2>d>%VZK7OY?NG_ep`0pia(4SQ&Q0IoeDwyeZ@tJwpJ&^@O=B)GI$&8j zQlLxdjE77or9>romCDCnB@KWU_joV zFmBw2nOG>hI^gM^b-u-_wDvLz9=-1q+;;eWVl*^e!w1#&H&0lMFfqbw7DW;|R0>31 zSnR(__GHs$KbzhkXq2-A(lI-Tr-@x+5i~6ioI1t9V<*^Z8~;$QFjx~956Bx859{3Jw`Mt z6g+hQM_C;orO3(~qF(q5WYjP0!h$Nb1&hd!=ZkrH?g#7rF%L9d5Nwvw1mCOKfzBLV z3v26#x$p50Q>hd@V+a_f`ye7oQ+g?jzS?q%G4?JR&~+VzNMRjXU|i(9a^@$z^V(~S ztR=P`R-47o8M9RAnR8=%As$il0C_WUqg_eOsJN=r$lU5_o>g_V$EX|?EQeN(Gaeqm zh$eIqs|?m!V)S$&wQkS#mFGIvi)XjLPBn`-8Dc_7s5DJ2RI@;gnj%{v%SV)hRa{ok zhGfB;mnA08Xu5#tlF1Ze957zFjp6VZqQ)3CpvczHlA*<-#b8N7NEVO9gK7!VQHPdk zTQO;7%(|L3b_5j&D$s-mF&dL+m^?#_Mzq1@1*4VK8&)X*k5_@HOkX#k7Y=KXrlOs} z`sfIjg3u_u4#9QRpbo?}wxP@ouAO8k{TezU`R@3TrY#5W1+i z`=N*N&fzt5ZHI^_b{!%LXY-%=`>RDhQir&vNdkuaRpAb%nJy ziT(t@nkzEqTamD&`mVP5$@TK=H-vylB;PTfI&IvxzF>Aad)E%A-T?kkW~1((9ks3 zn^7-QjT%thVjG*^qv#t3dC5w)!iVmA3>^*5IYI~@IOC%a*3YLTS)sK6A!4n4ZaI5e zZgkn}o3KBARflmIUFdLHWBPSpRTX7Eq^bjtf9lsL4y;ktE#tKzv1>8b5JggZ_|F(c z>8Pq|K{=a0xGr;84YW1S{qXxJA6OlYsV38uRU1dwg}tZ0-rDNcV*aZI46(;$MD=C@ zz1b{1c00&(+F6T^7E8{-)#La|QJOK{N1CQZi)J_|sh0v-yqfd7ieyI7)fI&`m>6hh z6`3(Otq~7SISd9BH7aTh7N;|;a_ByjFR~%IcIW_w$smAlJh{nI7pm;e1cHRQcS_$d zC3Ia&2!83v9~N`onmrHPN~z0y5U=N07>S`nWr3(#x(M3kN!P?TXi2ZB-tpXd>?FCf zbZwo2^$ozyuB9S5kGIAoR*XhL6!7smi$W{VX1AJpdV!_wkNxp;EkI4cvU23(r3Xz%L=;ifb*(K06h`>=*S?OG$j*G~KGV9^KJuK-u zroUd=qs*2o| z=wQh7kdHm`3ADF_t|7||+uM_V1-}2UVV;~M$y;?=q<1^dtxK1Waz+#zi(S4C+H+4k zBe&*Ja(~5Isey-D%VmLxoba z6#P|#POT$o1T6+F1`{1%`po!RX0q3UD}J8u{~23V6!I*``)-lBcRqm20E?26JCEMa zByCZ#Qm{9QdVbB zqWaFdfDwi1i)4af+j~wu@eyq1a4xBlb>5?Nl0dy4^*04%Dxlh$AD@1PL2j8$r=XJF z$mLdYa~GL-3$0@#EMS0n5LOH4?7c<6MBS66&CnrD#%g|ydvAY$*eIk^5F*2(B#2L# z@FG~7t7g@)55}?2ibZvZ3ca^r+&k6oOxb@5*w7)WNs^+ffxcL%@MzgvDoz|~bHjq| z^^D1M!e}@ohQzxMfDeITxk_j>CaiM%(Ff7dqJa=PwkO+^#fY}`+^B-IdKU#qF)5?g zQJ&Vlu84@B%e+{Q;y=9Nht+#?=-3~>DnlpiQTB$>fMMc0?tSP*Tsy-RR4;a<=SNzX zv3~qGx7~FIzVrAHa8{v=rn}DeTOA`Ycvgn+HNVSAc zIRd-Ws{~yoWfaqEIPaZ;L2#+7PjZl)fL+LjCixPx2+{#fP@0ILQ--)83EsRh91hvq zo={{fC~`ys#%ZS0z`Z9<@z|-)Fm#70-4LrZR%u4%fUXT#d;JCu=>)2-xlU7K*y1id zHDA|RmZ7al-_Y;GKUmIo_Q(EsuSYjgfVG$`u#o z;}>}4#h0+w(sdnL>pjrNyNSc!?5uhhm%jX7n#)cadoyY+Al@LYLdTp#>xa4LuKTdc z;IzGZ2EXSsjgTW!B9dJ(J-Z)c&w!3fFD8-^WJr)PLB@y-QE^0|B#;xxi7IUbVW}U| z&x7`|tBUCN7WCfh_x&8h>Vl?LX~WTqHnto-aFmmGJ;dthIMt4zMbk7Dpy}F3*9F4+ z*zYobm*>bv`p6IFOLrXws10Tub`K4Nbtt zh*W{-Ee|~YF+!fvDF}1Xq@uXK#jKO2#MagpW1I8suYQFLh0&6_$%qRF^f{d?=w0w* z;oEpCfJXv0>V>*o0K5)~f3DNtZ=yp>YW7h*#Gs;(nVdrB*hX>R(UT0k;b69cR2^B| z?RxYJie3tKMs$DjF?gk~m@xmitCgZf@6zoSzbio)i^AX%Y!l`X%T-{Cg+Tu=y#C^n z8Sv$2h1kzj1YO?X(uU!PXtristgV-9?`&a$rHCa`IqtpVVGgVuLj_9+vIv$%QP8$6 ztD^y0U;o;8NZ+T?iBLL^3QTE*7W`VmBkp6?vi1p@Yecqva}p@_+pi-}?O5kn=4ESjBjSwHYl6 zmz7N04vCr!hZBpDMA5TJiGx&JoX>9YHA_VILC=XN6OWUCqrs>eLyMX>jqE8x&I3OD*n3aT`!FL~B8r9>W>^zIDk6cbED)VDX(C=*V&1XS zoMm;zVnRm|R#}zfJaX3)=+;80G1f0^7TdNZ%MEQa!KiLY-})CDUPru+SB0oF;A3K< zSgXJ1l_xVA@t$#BBxuhVgGHwrf$-hSk7sg=m1XwF%^7A%H(8WU>{}%%mx@UYU!xfx zImYexouY~0gJ2c7>lD=qC`@Lkni;FZf-}!N&Gy-|DBrL?9#b{5UARsX&U$%fXKr?; zA-ew?-QG5D6^Oc{>sm%diFi=KpjFObFv67u(`tw9$p)E&k#+pwE8pOs{lou_jUT_x z7yrq>pgT9=aDISxr>4vccB(08&D#2yrm2Z>H{XT1wNok*YKdr3SRj*#i#<<6c8QLO z72nEVnpePF^aaW*qQ9~^vzvUT5BEBc?gC_ z?tYyA;NSg^DAgF_)n&BKWlTN1Zzr;=CMqJ|rB6#MRceDBuc5xU?R42L}W;m7e&lEAzfH6db=(7mAQ~&0EE+P= zlu=W9%^+yDAe501CM5Dnr$a+KsmNC@&JF zkpP*cm5A0t*G{ms{MY}*-y!U5lC>?PHgYV7v(LTEg%@8V->w;Uni9=;P~eK3(ZMxZ z0-?`BFK;7p{e3b^h-vIu8y<`F5=%E7*Y7u%y-9-ZiEIZRk+aw1ZfY4P)oZ^(Q zJ~%+3*D37*KKh|gP}(6*JMe-Mvp8|hnJ&p7aen&wVDzZ_;`=w}NphAaCm(G9r@-YF zmH3g{O8Dl&=vFQ7F#F?IdFaJaZ5Ip+OMF+f0;_34;N%1Mvv&9>Q5!@QnZABen@%FZ z3ra;o7kK;3OHAH5pEMGdIuk?RWZ`-UuT#+Ko6BUH->0Bc&8O`PhiOAma6eVZF==vxeElmNf->1HSdSFLV0OzrkoH;C5Qt z?HNYFTO04t>V6v&gXyP>X@VgtL!%9|em2_l?kuSgnMKnVBX8d7)6u0V22CKUj;MUU z{xsfOv>s#Zm=JQhCL%uIOkn67lS?f}hj;Q@pZc4eJbEuy6&yKul(vhzN$Dae_d)st zjD7u8z8PcOo@<>HHp8J(Bas41mtz-3nS5iOReu0>==Nd4F4mt1Bs3tKAIM#Wd$lGHF8-8ZC4xO)H8lrcLjrpXwGf zEHLYNlTHA{8nVFSE2JzJa9Y#VU<+E~$*fC@3J$^BxOPh{#_o2kHH>Ka_xp1`|zBm5PbBN#F3G zpo@X1B3=T)SRVh(rK`+m|j#1^@h>LSq?)0^wB9%!qE})s1 zpplyz4*C}MN4plQv=CqQb+MFz|a$@~%KKrr1!H4esFmJqij`47v z&;&B;D2gKKp983{=j0`Ou74wQ=e^#RMnETM?=N6i_BC^69;nkFkXgIDvwU4d!hW#c zAA162Voxt}j^$&0z+j9*#N%V65zm7kega(#$OZ*5sJ-^%zu=fmCakOt3BF^TXMFo> zUxVOL!S6m+J;&F^^fyNpZynZ>3VVZd56?s$xwySSHXc$*;NtcssK|%r2b)5l;b>f%# z$h}J5oSC6|#Mkuv?M3VO6-EaibT*wRgBstnKbAE_=-b#^lm62-Qd|gux~j>Q;*D2d;?0*|B<~^cs8a^LNI;BRvVXb-j_-YNw*54H z?DGm@X4ncfdYmCn$R%>XTK@OH|KIcScb{TaJF=)yq6i_PoMy9{;YvqPj3N4n(ghiy;~za{)sIGNqbrk&*M*L!aXD2R}Z-S zOlyFz*gnJF&#L#wq6>hzF>Bnj__SzptFZIT!)VI21Kj!0!+6y0(egSm>JZX(2SW!4 zldVl&eg4O+7v+MhtCT`?THYZfXAzmJ!QFHS$)IGHX)!-kM&kLsL)~lzBndz$E^0$t#D{%#84T&|0mD#^?&+B!YdbeBtOLA zN?47KOdG1;xzNst!wfebvN_p6OJt}lD>lRVi0LBED5|cx>Gy6}jzJ7{)1ad;EOT8dHdZirRKdbx&`l3cCu$o(JXBM<)?hgR=om><9_)E_TPIG zN)(S0AvYG+8$(~AgzcQ8blE&Oe>oj@?S72fAA6Q?Dj>>I@KG3n=#w&>5}^x%3`*{Q z^ihH}1myZ<8=@llFh^|W@HBMojOV`p16-Fn-I5!(TDXIR5HKO&ZXvx>Cy4?!FXE;4 z@nr65GEHbIMn>_{Q$OT?{Cj_oEC`M7?x{fSF@|#W7 z70YxQab`@Bm-yHqVTVlB3tet@%GV56Nxe~=H|{{-u!BTTnEJF#XwSV0*Dp}jKxlVYE6rE~q4X< z>WaZ2r;9CZ@C*k7>bhpK5jZ#);4Z>DPrb#T{=w(?`ak;iG%FN4fsuDiwBjLdPS zo0j$QQ4Xyi#)pa!E0TUTy@qxwFb6d^v~ZVAy$Ia%!tafE4a?F2yIMB};LqlpxiN;K zDE9P1UKXfTdeC|wMWxigo-g_osLg()`V}5?-QlPsK_?bKGBHgHF4qPfg|6#R+7OJP zSX<+v$3MiRuCM$0N?F8*ltJbWoT92*stXr+<+&Hq@o~w`+Dk-Gs3<5OZZf^>vU|A@ z=kGIOfdlz~BR1#r|LFhVyI=S-4re*DolSy^_&gHKZVP(UJ$|nG4@|hU1%xlU>f8*%}3wooPj_gisdL+OXBO zV6vM_Z&ZP%s|i{#Ca0bVG$Zal_7R>q^&32T&!;)QelISTSV{(YN!zw;Zf??bfh^0f z$cEGDpx3OKD*-O%-eCO!jDIarV|wm%=IkDbW)A?ZNxC1-xuntsUj$ka$&}KtUsT*5 zSB*O9IF64Afb{`gWXyu6)QTrQ`ceM0%#zdc^(>)~GEcN3NJN~1Ho2ud{jKlv#P9z; zAuGw11EmPTCy^e#+o*G^tl+TZuA)fnkD=0h^>hD1zBqLmS5h-ywxVj7qYnH!WOyY!UHIB+u4aamTpp=qWz@z{gSI zh^-<+BO#DEkM|zmdRA8tpta-tg$oP^c~8O9d$ZhL(|g_wO}9l(&kF_uLnXvX$EZ+jzuNNjTR%Xb9`U>X*?)px zF1OH)16C{_F5nttQo`?Kk~(g-6x=81=%?z!ft!5Zw7!HT*Y;V(cio=JFFsrt%gd*PUwTSKm`;Dp3nSM3R z?G2ak{x29|6A4&tDO93N&jU4p6#_^v2d^vznxiYLY;JCXjaX~H;C1}HfJZ5AspI4R z_&{^Og#^J`gTcKeVBE!LeQ0)+W zL~D&vLaPM&4PmahCz5b&t=ts>CaSbc8NkJ)!7SuGR_q9vJ*K^3xjEwUCXqDo4%Gkv zAOJ~3K~&ITRmM0QaBgFhmE%V^yLkbZ30epK;s5*(c=?4VSsO%LY-ziiVOenQe8s^- zt5j{bI2IT}Dk$#R`66{S!{w=4)HeZxrqqtur3RopH#E`X9k_ue1c6}4&4~Z>KmITI z`0xBKKaDe1w>u7_0g^mhXjxWRJ?`oX)#k*OF+^I>SfM z63`lq6_nCQ>UOT|ygLiOjd#G5V0s6 ziUXsSwBA%I)Q1?b%jUJZKT>I>808t0E})2iJkO~14Q}^?wUT7GY33P0zqpguf~N9e z!4J-(LquVQBkud~NBH5Nf1XikiCyY~6G;v*d0x;}6;3O(#D)B4?Ab^*q#2dQo3K#9 zlOEe`2_S)&o_U5({Kj9WvKq;9d5thj>E)!+|NS>fv7%%CZ_CodpcodWrgkSZL9=VUCtlg|s0DGW@<%CMl?o}qG=p2KTou(AZFq}LJp1CK%L6d>DK%^=Tc>zXJ~3`-_; z%isR@{zHEAZ~ZIwXf^LV{9{N)Ev3s-N3DjG zM?{oL`h^R6km_%)Q;Okm$o8d8MD!a<%NG>!w?7D}zy0ycJDhVgO@mUpXAMQX4>)Ui zt042{R3aY`Xd$TLUVR#cir790ESj=VGEU2;92<&oA1k)$N1my!$veD9mz z;otf9cVKlL-2czsn@8D}q}6@DFCxx(Z;o%Os-CK+s-7nh5)JM~LN*oxYe@)6*npO7 zGh?qM))KaXWr0A=DB1#p86}XAFk&>_0t*=lkQlmvo@lD4YPzesYkEVz%)HYXBI5HO zaqi8V`QGd9s#jI73NmV)bzfFaH}8$u=ZhWt+gs`S9uQ1kFmD&Qc)aS11XLnXl|aN= zH?Wmps$H7AN*nRwqs*dGV2ydZKyQjiBxq(JD;t>gj0L>ahv;S26~F+E@Zj2PP-iiiZW+0 zo3gomhW&X5&hY5Lg3tbfzs=wN+OKBcL7zD)snBUe`hY~(9zBcCe#3M5*rh|R&SvZ% zTxHSB38AA84U5GDZ6?%pF;sJr7<+u|)AoI^z-{^o)~1Zdq-QR$)=}pbc{TzsoZWsF z7w&pC8}(T>t22y>Em&k^+Tx@@)vxS8*P8S2-aq}7X=>2n%}I?c5fS-SA|G3R$;N23 zQZ}W>h*)#uZkCN|#6{VMs2IarfWLAIMW^HA&O_BCIdXQ*bunco-f_nroIih_=HZV~ zS_2xfF2`t~^$q07vli|UFM1oBvwl_WALL_m|GB`gv5uzIQ9(sg_KlV{>+dadq z?-N%pLa>4tn<_ii)Hfi*4nZtRq6kt29an6s(d2Pq)DA(d<%;GAYgMf*W1*H3wj;?^ z`YQ%B?K+QfLMO_2tHv%G?tkCA`HAoTPV$2(XE(RdzF~hF7;jkeqNJHN_;!j|V0e9( z74>=#PfFYwbneS@gSF?je5RY@iU;n$%&2zQe8lr!_*}mH8~-;Bb4Qaof)PSc{A|wF zxMH-q$*dWp)^OLIFTzLYeM{drwC#efU(oqEA@uAYUO^;PjeY3p`*x^uL-G)>>vT{y z1eI8ulV=5WQB&q)wny7^t>C1j$j8LaF`KqnacpmFF+047u_^G?T6+wnLkLeyfbx?# zj4{WUm*K|pmgQlv6GL4UL)*BfgJVq$hda*2=<0|l&vNoSXC8X2nqQ6~{(!}vmei-? z=x{{W4$6SgcC6duMkClF$B1Qndz)9?{c_&+kq2-tPwn6)bz$u5zPp!7f!VYp+Q$Er`3+Q@LbqBL$H?l-aa%?)En&YPY7m;$s9$NQMwIA zrY061F^VyY+;$hn93{_(5E9TBgE5Y_iPt1YLE7RQW9VjGa#~bj(M`!DV^kTupE5bv zqpGT*B4zdSm*eRU(o0}gj`({4Rn=eCfqE&50>^T7mVVUB3R@(#b7w`#OJDUGI#P~e43Q8c)}&?`D}oukk!{ojO3M~&krbjE z0xm>c022mK)KmpKg~j}kcf9@g31LA|3f6hNZE1BuF)9(~z-EYXgTznhgQxY8#`nj% z?v6lujgMw|z^Q9Dt3+$qSYTr|B!XmXoMxg≺9*o$YO$4{TfEU;V)MasPYXL9HG? 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%0AhSnN-YY66K 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 Date: Wed, 3 May 2023 01:44:05 +0300 Subject: [PATCH 157/164] fix(database): fix saving to Neo4j --- app/src/main/kotlin/dataBase/Neo4j.kt | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/dataBase/Neo4j.kt b/app/src/main/kotlin/dataBase/Neo4j.kt index 7265254..47982ac 100644 --- a/app/src/main/kotlin/dataBase/Neo4j.kt +++ b/app/src/main/kotlin/dataBase/Neo4j.kt @@ -44,13 +44,31 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { validateName(treeName) removeTree(treeName) - executeQuery("CREATE (:Tree {name: '$treeName', type: '${tree::class.simpleName}', " + - "viewX: ${viewCoordinates.first}, viewY: ${viewCoordinates.second}})") + addTreeNode(treeName, tree, viewCoordinates) var prevKey: Int? = null tree.getKeyValueList() .forEach { saveNode(it.first, it.second.first, it.second.second, prevKey, treeName); prevKey = it.first } } + private fun addTreeNode( + treeName: String, + tree: BinTree>>, + coordinates: Pair + ) { + session.executeWrite { tx -> + tx.run( + "CREATE (:Tree {name: \$name, type: \$type, " + + "viewX: \$x, viewY: \$y})", + mutableMapOf( + "name" to treeName, + "type" to tree::class.simpleName, + "x" to coordinates.first, + "y" to coordinates.second + ) as Map + ) + } + } + private fun saveNode( key: Int, value: String, @@ -60,7 +78,7 @@ class Neo4j(uri: String, user: String, password: String) : DataBase { ) { session.executeWrite { tx -> tx.run( - "OPTIONAL MATCH (prevNode:${if (prevKey == null) "Tree WHERE prevNode.name = '$treeName'" else "${treeName}Node WHERE prevNode.key = '$prevKey'"}) " + + "OPTIONAL MATCH (prevNode:${if (prevKey == null) "Tree WHERE prevNode.name = '$treeName'" else "${treeName}Node WHERE prevNode.key = $prevKey"}) " + "CREATE (prevNode)-[:next]->(b:${treeName}Node {key:\$key, value:\$value, x:\$x, y:\$y})", mutableMapOf( "key" to key, From 216709c8408114ae575118c622589c34af1407d0 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 3 May 2023 01:53:21 +0300 Subject: [PATCH 158/164] feat(ui): add validating to tree, saving of offsetX, Y --- app/src/main/kotlin/app/OpenTree.kt | 16 ++++++++-------- app/src/main/kotlin/app/TreeWindow.kt | 12 +++++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/OpenTree.kt index a1ae9b5..dcc9af3 100644 --- a/app/src/main/kotlin/app/OpenTree.kt +++ b/app/src/main/kotlin/app/OpenTree.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.zIndex @Composable fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { val files = remember { mutableStateOf(mutableStateListOf>>()) } - var dataBaseType = Controller.DatabaseType.Json + val dataBaseType = remember { mutableStateOf(Controller.DatabaseType.Json) } Column( modifier = Modifier.fillMaxSize().padding(start = 120.dp, end = 120.dp), verticalArrangement = Arrangement.aligned(Alignment.CenterVertically), @@ -42,7 +42,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { onClick = { files.value = Controller.Database(Controller.DatabaseType.Json).getAllTrees().toMutableStateList() - dataBaseType = Controller.DatabaseType.Json + dataBaseType.value = Controller.DatabaseType.Json }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f).height(57.dp), @@ -60,7 +60,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { onClick = { files.value = Controller.Database(Controller.DatabaseType.SQLite).getAllTrees().toMutableStateList() - dataBaseType = Controller.DatabaseType.SQLite + dataBaseType.value = Controller.DatabaseType.SQLite }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f).height(57.dp), @@ -78,7 +78,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { onClick = { files.value = Controller.Database(Controller.DatabaseType.Neo4j).getAllTrees().toMutableStateList() - dataBaseType = Controller.DatabaseType.Neo4j + dataBaseType.value = Controller.DatabaseType.Neo4j }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(0.3f).height(57.dp), @@ -98,7 +98,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - Controller.Database(dataBaseType).clean() + Controller.Database(dataBaseType.value).clean() files.value = mutableStateListOf() }, shape = MaterialTheme.shapes.extraLarge, @@ -107,7 +107,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { containerColor = MaterialTheme.colorScheme.onErrorContainer ) ) { - Text("delete all trees saved in $dataBaseType") + Text("delete all trees saved in ${dataBaseType.value}") } Spacer(modifier = Modifier.height(10.dp)) } @@ -128,7 +128,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { ) { Button( onClick = { - onClick(Controller.DrawTree(file.first, dataBaseType)) + onClick(Controller.DrawTree(file.first, dataBaseType.value)) }, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.weight(3f).width(30.dp).fillMaxHeight(), @@ -152,7 +152,7 @@ fun OpenTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( onClick = { - Controller.Database(dataBaseType).removeTree(file.first) + Controller.Database(dataBaseType.value).removeTree(file.first) files.value.remove(file) }, shape = MaterialTheme.shapes.extraLarge, diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/TreeWindow.kt index 013ceae..19566e1 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/TreeWindow.kt @@ -17,10 +17,11 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { var textForUser by remember { mutableStateOf("") } val openDialog = remember { mutableStateOf(false) } - val offSetX = remember { mutableStateOf(0f) } - val offSetY = remember { mutableStateOf(0f) } + val offSetX = remember { mutableStateOf(tree.viewCoordinates.first) } + val offSetY = remember { mutableStateOf(tree.viewCoordinates.second) } tree.reInitAllDrawNodes() + tree.viewCoordinates = Pair(offSetX.value, offSetY.value) Row(modifier = Modifier.fillMaxSize().background(Color.White).padding(6.dp)) { Column( @@ -28,12 +29,14 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { ) { Insert( onClick = { key, value -> - if ((key != "") && (value != "")) { + if (!Controller.validKey(key)) { + textForUser = "Oops.. it's not Int, bro" + } else if (key != "") { tree.drawInsert(key, value) tree.reInitAllDrawNodes() textForUser = "I insert node with key: $key and value: $value :)" } else { - textForUser = "Give me key and value pls :(" + textForUser = "Give me key pls :(" } } ) @@ -316,4 +319,3 @@ fun Find(onClick: (key: String) -> Unit) { } } } - From 96aab0ef1c885ca2a5df8a135136d81822547f28 Mon Sep 17 00:00:00 2001 From: juliakononov Date: Wed, 3 May 2023 01:55:33 +0300 Subject: [PATCH 159/164] fix(app): icon --- app/src/main/kotlin/app/Main.kt | 4 +++- app/src/main/{kotlin/app => }/resources/icon.png | Bin 2 files changed, 3 insertions(+), 1 deletion(-) rename app/src/main/{kotlin/app => }/resources/icon.png (100%) diff --git a/app/src/main/kotlin/app/Main.kt b/app/src/main/kotlin/app/Main.kt index bfc86c8..78296b7 100644 --- a/app/src/main/kotlin/app/Main.kt +++ b/app/src/main/kotlin/app/Main.kt @@ -5,13 +5,15 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import UIT.AppTheme +import androidx.compose.ui.res.painterResource fun main() = application { Window( onCloseRequest = ::exitApplication, title = "Trees", - state = rememberWindowState(width = 900.dp, height = 700.dp) + state = rememberWindowState(width = 900.dp, height = 700.dp), + icon = painterResource("icon.png") ) { AppTheme { Main(window) diff --git a/app/src/main/kotlin/app/resources/icon.png b/app/src/main/resources/icon.png similarity index 100% rename from app/src/main/kotlin/app/resources/icon.png rename to app/src/main/resources/icon.png From df9d8a7ee372f45a6539378cc8dca5613dfa9ebe Mon Sep 17 00:00:00 2001 From: juliakononov Date: Wed, 3 May 2023 02:54:40 +0300 Subject: [PATCH 160/164] refactor(app): change project struct --- app/src/main/kotlin/app/Main.kt | 5 +++-- app/src/main/kotlin/app/{ => controller}/Controller.kt | 4 ++-- app/src/main/kotlin/app/{ => theme}/Color.kt | 2 +- app/src/main/kotlin/app/{ => theme}/Theme.kt | 4 ++-- app/src/main/kotlin/app/{ => ui}/CreatNewWindow.kt | 3 ++- app/src/main/kotlin/app/{ => ui}/MainWindow.kt | 9 ++++++--- app/src/main/kotlin/app/{ => ui}/OpenTree.kt | 8 ++++++-- app/src/main/kotlin/app/{ => ui}/TreeWindow.kt | 3 ++- app/src/main/kotlin/app/{ => ui}/ViewTree.kt | 3 ++- app/src/main/kotlin/app/{ => ui}/Windows.kt | 9 +++++---- 10 files changed, 31 insertions(+), 19 deletions(-) rename app/src/main/kotlin/app/{ => controller}/Controller.kt (98%) rename app/src/main/kotlin/app/{ => theme}/Color.kt (99%) rename app/src/main/kotlin/app/{ => theme}/Theme.kt (99%) rename app/src/main/kotlin/app/{ => ui}/CreatNewWindow.kt (99%) rename app/src/main/kotlin/app/{ => ui}/MainWindow.kt (89%) rename app/src/main/kotlin/app/{ => ui}/OpenTree.kt (96%) rename app/src/main/kotlin/app/{ => ui}/TreeWindow.kt (99%) rename app/src/main/kotlin/app/{ => ui}/ViewTree.kt (98%) rename app/src/main/kotlin/app/{ => ui}/Windows.kt (81%) diff --git a/app/src/main/kotlin/app/Main.kt b/app/src/main/kotlin/app/Main.kt index 78296b7..9ad5217 100644 --- a/app/src/main/kotlin/app/Main.kt +++ b/app/src/main/kotlin/app/Main.kt @@ -1,11 +1,12 @@ package app +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState -import UIT.AppTheme -import androidx.compose.ui.res.painterResource +import app.theme.AppTheme +import app.ui.Main fun main() = application { diff --git a/app/src/main/kotlin/app/Controller.kt b/app/src/main/kotlin/app/controller/Controller.kt similarity index 98% rename from app/src/main/kotlin/app/Controller.kt rename to app/src/main/kotlin/app/controller/Controller.kt index 2f4b054..21f6e54 100644 --- a/app/src/main/kotlin/app/Controller.kt +++ b/app/src/main/kotlin/app/controller/Controller.kt @@ -1,4 +1,4 @@ -package app +package app.controller import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf @@ -59,7 +59,7 @@ object Controller { } fun getTree(treeType: TreeType, keysType: KeysType) = when(keysType) { - KeysType.Int -> Controller.getTree(treeType) + KeysType.Int -> getTree(treeType) else -> throw IllegalArgumentException("Only Int support now") } diff --git a/app/src/main/kotlin/app/Color.kt b/app/src/main/kotlin/app/theme/Color.kt similarity index 99% rename from app/src/main/kotlin/app/Color.kt rename to app/src/main/kotlin/app/theme/Color.kt index 0e3e905..2b2e0bb 100644 --- a/app/src/main/kotlin/app/Color.kt +++ b/app/src/main/kotlin/app/theme/Color.kt @@ -1,4 +1,4 @@ -package UIT +package app.theme import androidx.compose.ui.graphics.Color diff --git a/app/src/main/kotlin/app/Theme.kt b/app/src/main/kotlin/app/theme/Theme.kt similarity index 99% rename from app/src/main/kotlin/app/Theme.kt rename to app/src/main/kotlin/app/theme/Theme.kt index d414ac5..b1319ac 100644 --- a/app/src/main/kotlin/app/Theme.kt +++ b/app/src/main/kotlin/app/theme/Theme.kt @@ -1,9 +1,9 @@ -package UIT +package app.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.lightColorScheme import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable diff --git a/app/src/main/kotlin/app/CreatNewWindow.kt b/app/src/main/kotlin/app/ui/CreatNewWindow.kt similarity index 99% rename from app/src/main/kotlin/app/CreatNewWindow.kt rename to app/src/main/kotlin/app/ui/CreatNewWindow.kt index a75c561..04e274e 100644 --- a/app/src/main/kotlin/app/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/ui/CreatNewWindow.kt @@ -1,4 +1,4 @@ -package app +package app.ui import androidx.compose.foundation.layout.* import androidx.compose.material.DropdownMenu @@ -8,6 +8,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import app.controller.Controller @Composable fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { diff --git a/app/src/main/kotlin/app/MainWindow.kt b/app/src/main/kotlin/app/ui/MainWindow.kt similarity index 89% rename from app/src/main/kotlin/app/MainWindow.kt rename to app/src/main/kotlin/app/ui/MainWindow.kt index 1379e81..de4e236 100644 --- a/app/src/main/kotlin/app/MainWindow.kt +++ b/app/src/main/kotlin/app/ui/MainWindow.kt @@ -1,8 +1,11 @@ -package app +package app.ui import androidx.compose.foundation.layout.* -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp diff --git a/app/src/main/kotlin/app/OpenTree.kt b/app/src/main/kotlin/app/ui/OpenTree.kt similarity index 96% rename from app/src/main/kotlin/app/OpenTree.kt rename to app/src/main/kotlin/app/ui/OpenTree.kt index dcc9af3..3d448a2 100644 --- a/app/src/main/kotlin/app/OpenTree.kt +++ b/app/src/main/kotlin/app/ui/OpenTree.kt @@ -1,17 +1,21 @@ -package app +package app.ui import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import app.controller.Controller @Composable diff --git a/app/src/main/kotlin/app/TreeWindow.kt b/app/src/main/kotlin/app/ui/TreeWindow.kt similarity index 99% rename from app/src/main/kotlin/app/TreeWindow.kt rename to app/src/main/kotlin/app/ui/TreeWindow.kt index 19566e1..2c9cda8 100644 --- a/app/src/main/kotlin/app/TreeWindow.kt +++ b/app/src/main/kotlin/app/ui/TreeWindow.kt @@ -1,4 +1,4 @@ -package app +package app.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import app.controller.Controller @OptIn(ExperimentalMaterialApi::class) @Composable diff --git a/app/src/main/kotlin/app/ViewTree.kt b/app/src/main/kotlin/app/ui/ViewTree.kt similarity index 98% rename from app/src/main/kotlin/app/ViewTree.kt rename to app/src/main/kotlin/app/ui/ViewTree.kt index e58c5b3..3e4aaf2 100644 --- a/app/src/main/kotlin/app/ViewTree.kt +++ b/app/src/main/kotlin/app/ui/ViewTree.kt @@ -1,4 +1,4 @@ -package app +package app.ui import androidx.compose.foundation.* import androidx.compose.foundation.gestures.detectDragGestures @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex +import app.controller.Controller import kotlin.math.roundToInt open class ViewTree { diff --git a/app/src/main/kotlin/app/Windows.kt b/app/src/main/kotlin/app/ui/Windows.kt similarity index 81% rename from app/src/main/kotlin/app/Windows.kt rename to app/src/main/kotlin/app/ui/Windows.kt index 99a5dad..56269f3 100644 --- a/app/src/main/kotlin/app/Windows.kt +++ b/app/src/main/kotlin/app/ui/Windows.kt @@ -1,7 +1,8 @@ -package app +package app.ui import androidx.compose.runtime.* import androidx.compose.ui.awt.ComposeWindow +import app.controller.Controller sealed class Screen { @@ -26,19 +27,19 @@ fun Main(window: ComposeWindow) { is Screen.CreatNewWindow -> CreatNewTree( - onBack = {screenState = Screen.MainWindow}, + onBack = {screenState = Screen.MainWindow }, onClick = { screenState = Screen.TreeWindow(tree = it) } ) is Screen.OpenTree -> OpenTree( - onBack = {screenState = Screen.MainWindow}, + onBack = {screenState = Screen.MainWindow }, onClick = { screenState = Screen.TreeWindow(tree = it) } ) is Screen.TreeWindow -> Tree( - onBack = {screenState = Screen.MainWindow}, + onBack = {screenState = Screen.MainWindow }, tree = screen.tree ) } From 53881e7cf72cdc38c7bd12dec3022c1d7814e05e Mon Sep 17 00:00:00 2001 From: juliakononov <113186929+juliakononov@users.noreply.github.com> Date: Wed, 3 May 2023 02:58:39 +0300 Subject: [PATCH 161/164] Create README.md --- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed121a4 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +logo + +# Tree Structure + +Trees are a data structures that link nodes in a parent/child relationship, in the sense that there're nodes that depend on or come off other nodes. Each node contains a key and a value. + + +[![Apache License](https://img.shields.io/badge/license-Apache%202.0-black.svg)](https://www.apache.org/licenses/LICENSE-2.0) + +![image](https://user-images.githubusercontent.com/113186929/235809712-99ff32ab-0c26-4095-8914-edbe727d6343.png) + + +## Build tool + +The Gradle build tool is used to manage the project. +You only need to write one line to build a project: +```bash + ./gradlew build +``` +## How to use app + +To run app write one line to build a project: +``` +./gradlew run +``` + +After launching the app, you will see three buttons: + - `New` Creates a new tree, you just have to type in a name and choose one of three tree types: Binary search Tree, AVL Tree, Red-Black Tree. +- `Open` Invites you to open a tree of three possible databases: Json, SQLite, Neo4j. Point and click on the database you want, and you will see a list of trees that already exist. Then choose the file you want. +- `Exit` Close the app. + +After creating or opening a tree, a window will appear where you can: +- Insert key and value to your tree. If a node with this name already exists, its value will be overwritten with the new one. +- Remove the node with the entered key. +- Find the value of a node using a key. +- Save the tree in three possible databases: Json, SQLite, Neo4j. If you want to save the tree with an existing name, the app will overwrite the old file. + + + + +## How to use library +- `insert` - Inserts a node into the tree. It takes `Key` and `Value` and uses them to add. If a node with this name already exists, its value will be overwritten with the new one. +- `remove` - Removes a node from the tree. It accepts the `Key` and uses it to delete the node. +- `get` - Retrieves a given node. Use the `Key` to get the `Value`. If there is no such key in the tree the program will return null. + + +## How to use Data Bases + +- ## Json +If you want to change the save folder of your tree, change `json_save_dir` value in the `trees-11/app/src/main/resources/Json.properties` file. + +- ## SQLite +If you want to change the saving path of your tree, change `sqlite_path` value in the `trees-11/app/src/main/resources/SQLite.properties` file. +- ## Neo4j +You should download Neo4j Desktop [here](https://neo4j.com/). + +Open app. + +

+

+

+

+ +If you want to change uri, user name or password of your data Base, change relevant fields in the `trees-11/app/src/main/resources/Neo4j.properties` file. + From 8c48200df8218edfc6d241fb84b3fd61c30af907 Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 3 May 2023 03:09:33 +0300 Subject: [PATCH 162/164] fix(ui): fix calculation of coordinates --- app/src/main/kotlin/app/controller/Controller.kt | 13 ++++++------- app/src/main/kotlin/app/ui/CreatNewWindow.kt | 2 +- lib/src/main/kotlin/trees/BinTree.kt | 2 ++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/app/controller/Controller.kt b/app/src/main/kotlin/app/controller/Controller.kt index 21f6e54..41030f9 100644 --- a/app/src/main/kotlin/app/controller/Controller.kt +++ b/app/src/main/kotlin/app/controller/Controller.kt @@ -41,8 +41,7 @@ object Controller { try { key.toInt() true - } - catch (ex: Exception) { + } catch (ex: Exception) { false } } @@ -145,13 +144,13 @@ object Controller { return listOfDrawNodes } - fun reInitAllDrawNodes(){ + fun reInitAllDrawNodes() { content.value = getAllDrawNodes() } private fun rewriteAllCoordinates() { - fun offsetOnLevel(level: Int, height: Int) = - ((height - 2) * xMinInterval * (0.5.pow(level) - 1) * (-2)).toFloat() //the sum of the terms of the geometric progression + fun offsetOnLevel(level: Int, height: Int) = if (height == 2 && level != 0) xMinInterval / 2 else + ((0.5.pow(level) - 1) * (height - 2) * xMinInterval * (-2)).toFloat() //the sum of the terms of the geometric progression var lastLevel = -1 var curX = startCoordinate.first @@ -160,7 +159,7 @@ object Controller { tree.rewriteAllValue(true) { value, level, height -> if (level != lastLevel) { curY -= yInterval - curX = -offsetOnLevel(level, height) + curX = startCoordinate.first - offsetOnLevel(level, height) levelInterval = xMinInterval * 2F.pow(height - level - 1) } else curX += levelInterval lastLevel = level @@ -189,7 +188,7 @@ object Controller { } fun saveToDB(databaseType: DatabaseType) { - getDatabase(databaseType).saveTree(treeName, tree, viewCoordinates,) + getDatabase(databaseType).saveTree(treeName, tree, viewCoordinates) } fun clean() { diff --git a/app/src/main/kotlin/app/ui/CreatNewWindow.kt b/app/src/main/kotlin/app/ui/CreatNewWindow.kt index 04e274e..b37bb31 100644 --- a/app/src/main/kotlin/app/ui/CreatNewWindow.kt +++ b/app/src/main/kotlin/app/ui/CreatNewWindow.kt @@ -156,7 +156,7 @@ fun CreatNewTree(onBack: () -> Unit, onClick: (Controller.DrawTree) -> Unit) { Button( enabled = error.value == null && name.isNotEmpty(), onClick = { - val tree = Controller.DrawTree(name, Controller.TreeType.RBTree, keysType.value) + val tree = Controller.DrawTree(name, treeType.value, keysType.value) name = "" onClick(tree) }, diff --git a/lib/src/main/kotlin/trees/BinTree.kt b/lib/src/main/kotlin/trees/BinTree.kt index 0af7ca0..b3499f9 100644 --- a/lib/src/main/kotlin/trees/BinTree.kt +++ b/lib/src/main/kotlin/trees/BinTree.kt @@ -256,6 +256,8 @@ abstract class BinTree, Value> : Tree { listOfLevel = mutableListOf() } } + if (listOfLevel.isNotEmpty()) + listOfAllNodes.add(listOfLevel) var curLevel = 0 val height = listOfAllNodes.size From bbc8d2b0a031fe3be50707a23f33ff757d05e85d Mon Sep 17 00:00:00 2001 From: juliakononov <113186929+juliakononov@users.noreply.github.com> Date: Wed, 3 May 2023 03:23:43 +0300 Subject: [PATCH 163/164] Update README.md fix: Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed121a4..c835003 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -logo +logo # Tree Structure From 2664aee2eb68c1ba00155ee73a828d8e76f18b3e Mon Sep 17 00:00:00 2001 From: Arseniy Kuznetsov Date: Wed, 3 May 2023 03:24:21 +0300 Subject: [PATCH 164/164] feat(ui): Secret --- app/src/main/kotlin/app/ui/TreeWindow.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/kotlin/app/ui/TreeWindow.kt b/app/src/main/kotlin/app/ui/TreeWindow.kt index 2c9cda8..46d792b 100644 --- a/app/src/main/kotlin/app/ui/TreeWindow.kt +++ b/app/src/main/kotlin/app/ui/TreeWindow.kt @@ -116,6 +116,18 @@ fun Tree(onBack: () -> Unit, tree: Controller.DrawTree) { ) { Text("go to tree Root!") } + Button( + onClick = { + tree.yInterval = -tree.yInterval + }, + shape = MaterialTheme.shapes.extraLarge, + modifier = Modifier.width(400.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White + ) + ) { + Text("secret", color = Color.White) + } } ViewTree().drawTree(tree, offSetX, offSetY) }