From fb454ac197cfbcd803b9a62d198f9287ee998621 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 10:12:30 +0000 Subject: [PATCH 001/296] Setting up GitHub Classroom Feedback From 072b56d17da2b67f24fb7bff3812d537852606e4 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 15:51:18 +0300 Subject: [PATCH 002/296] Init commit --- .gitattributes | 9 + .gitignore | 6 + app/build.gradle.kts | 16 ++ app/src/main/kotlin/org/tree/app/App.kt | 12 + binaryTree/build.gradle.kts | 7 + .../main/kotlin/org/tree/binaryTree/Nodes.kt | 20 ++ buildSrc/build.gradle.kts | 17 ++ buildSrc/settings.gradle.kts | 7 + ....kotlin-application-conventions.gradle.kts | 11 + ....tree.kotlin-common-conventions.gradle.kts | 28 ++ ...tree.kotlin-library-conventions.gradle.kts | 11 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 ++++++++++++++++++ gradlew.bat | 92 +++++++ settings.gradle.kts | 11 + 16 files changed, 497 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/src/main/kotlin/org/tree/app/App.kt create mode 100644 binaryTree/build.gradle.kts create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/org.tree.kotlin-application-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/org.tree.kotlin-library-conventions.gradle.kts create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..097f9f98 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..36f47ba8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Gradle +.gradle +build + +# Idea +.idea diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..37cd884b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,16 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id("org.tree.kotlin-application-conventions") +} + +dependencies { + implementation(project(":binaryTree")) +} + +application { + // Define the main class for the application. + mainClass.set("org.tree.app.AppKt") +} diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt new file mode 100644 index 00000000..77872c3e --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -0,0 +1,12 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package org.tree.app + + +import org.tree.binaryTree.Node + +fun main() { + val nd = Node(2) + println(nd.elem) +} diff --git a/binaryTree/build.gradle.kts b/binaryTree/build.gradle.kts new file mode 100644 index 00000000..33cf2ad8 --- /dev/null +++ b/binaryTree/build.gradle.kts @@ -0,0 +1,7 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id("org.tree.kotlin-library-conventions") +} diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt new file mode 100644 index 00000000..23d8750e --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -0,0 +1,20 @@ +package org.tree.binaryTree + +abstract class TemplateNode, NODE_T : TemplateNode>(v: T) { + var elem: T = v + var left: NODE_T? = null + var right: NODE_T? = null +} + +class Node>(v: T) : TemplateNode>(v) + +class RBNode>(p: RBNode?, v: T) : TemplateNode>(v) { + var parent: RBNode? = p + var col: Colour = Colour.RED + + enum class Colour { RED, BLACK } +} + +class AVLNode>(v: T) : TemplateNode>(v) { + var height: Int = 0 +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..d227aa78 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,17 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build. + `kotlin-dsl` +} + +repositories { + // Use the plugin portal to apply community plugins in convention plugins. + gradlePluginPortal() +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 00000000..3f3e665c --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,7 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This settings file is used to specify which projects to include in your build-logic build. + */ + +rootProject.name = "buildSrc" diff --git a/buildSrc/src/main/kotlin/org.tree.kotlin-application-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.tree.kotlin-application-conventions.gradle.kts new file mode 100644 index 00000000..e203baa4 --- /dev/null +++ b/buildSrc/src/main/kotlin/org.tree.kotlin-application-conventions.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the common convention plugin for shared build configuration between library and application projects. + id("org.tree.kotlin-common-conventions") + + // Apply the application plugin to add support for building a CLI application in Java. + application +} diff --git a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts new file mode 100644 index 00000000..f743af2e --- /dev/null +++ b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts @@ -0,0 +1,28 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. + id("org.jetbrains.kotlin.jvm") +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + constraints { + // Define dependency versions as constraints + implementation("org.apache.commons:commons-text:1.9") + } + + // Use JUnit Jupiter for testing. + testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") +} + +tasks.named("test") { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/buildSrc/src/main/kotlin/org.tree.kotlin-library-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.tree.kotlin-library-conventions.gradle.kts new file mode 100644 index 00000000..765f7be8 --- /dev/null +++ b/buildSrc/src/main/kotlin/org.tree.kotlin-library-conventions.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the common convention plugin for shared build configuration between library and application projects. + id("org.tree.kotlin-common-conventions") + + // Apply the java-library plugin for API and implementation separation. + `java-library` +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 zcmb5VV{~QRw)Y#`wrv{~+qP{x72B%VwzFc}c2cp;N~)5ZbDrJayPv(!dGEd-##*zr z)#n-$y^sH|_dchh3@8{H5D*j;5D<{i*8l5IFJ|DjL!e)upfGNX(kojugZ3I`oH1PvW`wFW_ske0j@lB9bX zO;2)`y+|!@X(fZ1<2n!Qx*)_^Ai@Cv-dF&(vnudG?0CsddG_&Wtae(n|K59ew)6St z#dj7_(Cfwzh$H$5M!$UDd8=4>IQsD3xV=lXUq($;(h*$0^yd+b{qq63f0r_de#!o_ zXDngc>zy`uor)4A^2M#U*DC~i+dc<)Tb1Tv&~Ev@oM)5iJ4Sn#8iRw16XXuV50BS7 zdBL5Mefch(&^{luE{*5qtCZk$oFr3RH=H!c3wGR=HJ(yKc_re_X9pD` zJ;uxPzUfVpgU>DSq?J;I@a+10l0ONXPcDkiYcihREt5~T5Gb}sT0+6Q;AWHl`S5dV>lv%-p9l#xNNy7ZCr%cyqHY%TZ8Q4 zbp&#ov1*$#grNG#1vgfFOLJCaNG@K|2!W&HSh@3@Y%T?3YI75bJp!VP*$*!< z;(ffNS_;@RJ`=c7yX04!u3JP*<8jeqLHVJu#WV&v6wA!OYJS4h<_}^QI&97-;=ojW zQ-1t)7wnxG*5I%U4)9$wlv5Fr;cIizft@&N+32O%B{R1POm$oap@&f| zh+5J{>U6ftv|vAeKGc|zC=kO(+l7_cLpV}-D#oUltScw})N>~JOZLU_0{Ka2e1evz z{^a*ZrLr+JUj;)K&u2CoCAXLC2=fVScI(m_p~0FmF>>&3DHziouln?;sxW`NB}cSX z8?IsJB)Z=aYRz!X=yJn$kyOWK%rCYf-YarNqKzmWu$ZvkP12b4qH zhS9Q>j<}(*frr?z<%9hl*i^#@*O2q(Z^CN)c2c z>1B~D;@YpG?G!Yk+*yn4vM4sO-_!&m6+`k|3zd;8DJnxsBYtI;W3We+FN@|tQ5EW= z!VU>jtim0Mw#iaT8t_<+qKIEB-WwE04lBd%Letbml9N!?SLrEG$nmn7&W(W`VB@5S zaY=sEw2}i@F_1P4OtEw?xj4@D6>_e=m=797#hg}f*l^`AB|Y0# z9=)o|%TZFCY$SzgSjS|8AI-%J4x}J)!IMxY3_KYze`_I=c1nmrk@E8c9?MVRu)7+Ue79|)rBX7tVB7U|w4*h(;Gi3D9le49B38`wuv zp7{4X^p+K4*$@gU(Tq3K1a#3SmYhvI42)GzG4f|u zwQFT1n_=n|jpi=70-yE9LA+d*T8u z`=VmmXJ_f6WmZveZPct$Cgu^~gFiyL>Lnpj*6ee>*0pz=t$IJ}+rE zsf@>jlcG%Wx;Cp5x)YSVvB1$yyY1l&o zvwX=D7k)Dn;ciX?Z)Pn8$flC8#m`nB&(8?RSdBvr?>T9?E$U3uIX7T?$v4dWCa46 z+&`ot8ZTEgp7G+c52oHJ8nw5}a^dwb_l%MOh(ebVj9>_koQP^$2B~eUfSbw9RY$_< z&DDWf2LW;b0ZDOaZ&2^i^g+5uTd;GwO(-bbo|P^;CNL-%?9mRmxEw~5&z=X^Rvbo^WJW=n_%*7974RY}JhFv46> zd}`2|qkd;89l}R;i~9T)V-Q%K)O=yfVKNM4Gbacc7AOd>#^&W&)Xx!Uy5!BHnp9kh z`a(7MO6+Ren#>R^D0K)1sE{Bv>}s6Rb9MT14u!(NpZOe-?4V=>qZ>}uS)!y~;jEUK z&!U7Fj&{WdgU#L0%bM}SYXRtM5z!6M+kgaMKt%3FkjWYh=#QUpt$XX1!*XkpSq-pl zhMe{muh#knk{9_V3%qdDcWDv}v)m4t9 zQhv{;} zc{}#V^N3H>9mFM8`i`0p+fN@GqX+kl|M94$BK3J-X`Hyj8r!#x6Vt(PXjn?N)qedP z=o1T^#?1^a{;bZ&x`U{f?}TMo8ToN zkHj5v|}r}wDEi7I@)Gj+S1aE-GdnLN+$hw!=DzglMaj#{qjXi_dwpr|HL(gcCXwGLEmi|{4&4#OZ4ChceA zKVd4K!D>_N=_X;{poT~4Q+!Le+ZV>=H7v1*l%w`|`Dx8{)McN@NDlQyln&N3@bFpV z_1w~O4EH3fF@IzJ9kDk@7@QctFq8FbkbaH7K$iX=bV~o#gfh?2JD6lZf(XP>~DACF)fGFt)X%-h1yY~MJU{nA5 ze2zxWMs{YdX3q5XU*9hOH0!_S24DOBA5usB+Ws$6{|AMe*joJ?RxfV}*7AKN9V*~J zK+OMcE@bTD>TG1*yc?*qGqjBN8mgg@h1cJLDv)0!WRPIkC` zZrWXrceVw;fB%3`6kq=a!pq|hFIsQ%ZSlo~)D z|64!aCnw-?>}AG|*iOl44KVf8@|joXi&|)1rB;EQWgm+iHfVbgllP$f!$Wf42%NO5b(j9Bw6L z;0dpUUK$5GX4QbMlTmLM_jJt!ur`_0~$b#BB7FL*%XFf<b__1o)Ao3rlobbN8-(T!1d-bR8D3S0@d zLI!*GMb5s~Q<&sjd}lBb8Nr0>PqE6_!3!2d(KAWFxa{hm`@u|a(%#i(#f8{BP2wbs zt+N_slWF4IF_O|{w`c~)Xvh&R{Au~CFmW#0+}MBd2~X}t9lz6*E7uAD`@EBDe$>7W zzPUkJx<`f$0VA$=>R57^(K^h86>09?>_@M(R4q($!Ck6GG@pnu-x*exAx1jOv|>KH zjNfG5pwm`E-=ydcb+3BJwuU;V&OS=6yM^4Jq{%AVqnTTLwV`AorIDD}T&jWr8pB&j28fVtk_y*JRP^t@l*($UZ z6(B^-PBNZ+z!p?+e8@$&jCv^EWLb$WO=}Scr$6SM*&~B95El~;W_0(Bvoha|uQ1T< zO$%_oLAwf1bW*rKWmlD+@CP&$ObiDy=nh1b2ejz%LO9937N{LDe7gle4i!{}I$;&Y zkexJ9Ybr+lrCmKWg&}p=`2&Gf10orS?4$VrzWidT=*6{KzOGMo?KI0>GL0{iFWc;C z+LPq%VH5g}6V@-tg2m{C!-$fapJ9y}c$U}aUmS{9#0CM*8pC|sfer!)nG7Ji>mfRh z+~6CxNb>6eWKMHBz-w2{mLLwdA7dA-qfTu^A2yG1+9s5k zcF=le_UPYG&q!t5Zd_*E_P3Cf5T6821bO`daa`;DODm8Ih8k89=RN;-asHIigj`n=ux>*f!OC5#;X5i;Q z+V!GUy0|&Y_*8k_QRUA8$lHP;GJ3UUD08P|ALknng|YY13)}!!HW@0z$q+kCH%xet zlWf@BXQ=b=4}QO5eNnN~CzWBbHGUivG=`&eWK}beuV*;?zt=P#pM*eTuy3 zP}c#}AXJ0OIaqXji78l;YrP4sQe#^pOqwZUiiN6^0RCd#D271XCbEKpk`HI0IsN^s zES7YtU#7=8gTn#lkrc~6)R9u&SX6*Jk4GFX7){E)WE?pT8a-%6P+zS6o&A#ml{$WX zABFz#i7`DDlo{34)oo?bOa4Z_lNH>n;f0nbt$JfAl~;4QY@}NH!X|A$KgMmEsd^&Y zt;pi=>AID7ROQfr;MsMtClr5b0)xo|fwhc=qk33wQ|}$@?{}qXcmECh>#kUQ-If0$ zseb{Wf4VFGLNc*Rax#P8ko*=`MwaR-DQ8L8V8r=2N{Gaips2_^cS|oC$+yScRo*uF zUO|5=?Q?{p$inDpx*t#Xyo6=s?bbN}y>NNVxj9NZCdtwRI70jxvm3!5R7yiWjREEd zDUjrsZhS|P&|Ng5r+f^kA6BNN#|Se}_GF>P6sy^e8kBrgMv3#vk%m}9PCwUWJg-AD zFnZ=}lbi*mN-AOm zCs)r=*YQAA!`e#1N>aHF=bb*z*hXH#Wl$z^o}x##ZrUc=kh%OHWhp=7;?8%Xj||@V?1c ziWoaC$^&04;A|T)!Zd9sUzE&$ODyJaBpvqsw19Uiuq{i#VK1!htkdRWBnb z`{rat=nHArT%^R>u#CjjCkw-7%g53|&7z-;X+ewb?OLWiV|#nuc8mp*LuGSi3IP<<*Wyo9GKV7l0Noa4Jr0g3p_$ z*R9{qn=?IXC#WU>48-k5V2Oc_>P;4_)J@bo1|pf=%Rcbgk=5m)CJZ`caHBTm3%!Z9 z_?7LHr_BXbKKr=JD!%?KhwdYSdu8XxPoA{n8^%_lh5cjRHuCY9Zlpz8g+$f@bw@0V z+6DRMT9c|>1^3D|$Vzc(C?M~iZurGH2pXPT%F!JSaAMdO%!5o0uc&iqHx?ImcX6fI zCApkzc~OOnfzAd_+-DcMp&AOQxE_EsMqKM{%dRMI5`5CT&%mQO?-@F6tE*xL?aEGZ z8^wH@wRl`Izx4sDmU>}Ym{ybUm@F83qqZPD6nFm?t?(7>h*?`fw)L3t*l%*iw0Qu#?$5eq!Qc zpQvqgSxrd83NsdO@lL6#{%lsYXWen~d3p4fGBb7&5xqNYJ)yn84!e1PmPo7ChVd%4 zHUsV0Mh?VpzZD=A6%)Qrd~i7 z96*RPbid;BN{Wh?adeD_p8YU``kOrGkNox3D9~!K?w>#kFz!4lzOWR}puS(DmfjJD z`x0z|qB33*^0mZdM&6$|+T>fq>M%yoy(BEjuh9L0>{P&XJ3enGpoQRx`v6$txXt#c z0#N?b5%srj(4xmPvJxrlF3H%OMB!jvfy z;wx8RzU~lb?h_}@V=bh6p8PSb-dG|-T#A?`c&H2`_!u+uenIZe`6f~A7r)`9m8atC zt(b|6Eg#!Q*DfRU=Ix`#B_dK)nnJ_+>Q<1d7W)eynaVn`FNuN~%B;uO2}vXr5^zi2 z!ifIF5@Zlo0^h~8+ixFBGqtweFc`C~JkSq}&*a3C}L?b5Mh-bW=e)({F_g4O3 zb@SFTK3VD9QuFgFnK4Ve_pXc3{S$=+Z;;4+;*{H}Rc;845rP?DLK6G5Y-xdUKkA6E3Dz&5f{F^FjJQ(NSpZ8q-_!L3LL@H* zxbDF{gd^U3uD;)a)sJwAVi}7@%pRM&?5IaUH%+m{E)DlA_$IA1=&jr{KrhD5q&lTC zAa3c)A(K!{#nOvenH6XrR-y>*4M#DpTTOGQEO5Jr6kni9pDW`rvY*fs|ItV;CVITh z=`rxcH2nEJpkQ^(;1c^hfb8vGN;{{oR=qNyKtR1;J>CByul*+=`NydWnSWJR#I2lN zTvgnR|MBx*XFsfdA&;tr^dYaqRZp*2NwkAZE6kV@1f{76e56eUmGrZ>MDId)oqSWw z7d&r3qfazg+W2?bT}F)4jD6sWaw`_fXZGY&wnGm$FRPFL$HzVTH^MYBHWGCOk-89y zA+n+Q6EVSSCpgC~%uHfvyg@ufE^#u?JH?<73A}jj5iILz4Qqk5$+^U(SX(-qv5agK znUkfpke(KDn~dU0>gdKqjTkVk`0`9^0n_wzXO7R!0Thd@S;U`y)VVP&mOd-2 z(hT(|$=>4FY;CBY9#_lB$;|Wd$aOMT5O_3}DYXEHn&Jrc3`2JiB`b6X@EUOD zVl0S{ijm65@n^19T3l%>*;F(?3r3s?zY{thc4%AD30CeL_4{8x6&cN}zN3fE+x<9; zt2j1RRVy5j22-8U8a6$pyT+<`f+x2l$fd_{qEp_bfxfzu>ORJsXaJn4>U6oNJ#|~p z`*ZC&NPXl&=vq2{Ne79AkQncuxvbOG+28*2wU$R=GOmns3W@HE%^r)Fu%Utj=r9t` zd;SVOnA(=MXgnOzI2@3SGKHz8HN~Vpx&!Ea+Df~`*n@8O=0!b4m?7cE^K*~@fqv9q zF*uk#1@6Re_<^9eElgJD!nTA@K9C732tV~;B`hzZ321Ph=^BH?zXddiu{Du5*IPg} zqDM=QxjT!Rp|#Bkp$(mL)aar)f(dOAXUiw81pX0DC|Y4;>Vz>>DMshoips^8Frdv} zlTD=cKa48M>dR<>(YlLPOW%rokJZNF2gp8fwc8b2sN+i6&-pHr?$rj|uFgktK@jg~ zIFS(%=r|QJ=$kvm_~@n=ai1lA{7Z}i+zj&yzY+!t$iGUy|9jH#&oTNJ;JW-3n>DF+ z3aCOzqn|$X-Olu_p7brzn`uk1F*N4@=b=m;S_C?#hy{&NE#3HkATrg?enaVGT^$qIjvgc61y!T$9<1B@?_ibtDZ{G zeXInVr5?OD_nS_O|CK3|RzzMmu+8!#Zb8Ik;rkIAR%6?$pN@d<0dKD2c@k2quB%s( zQL^<_EM6ow8F6^wJN1QcPOm|ehA+dP(!>IX=Euz5qqIq}Y3;ibQtJnkDmZ8c8=Cf3 zu`mJ!Q6wI7EblC5RvP*@)j?}W=WxwCvF3*5Up_`3*a~z$`wHwCy)2risye=1mSp%p zu+tD6NAK3o@)4VBsM!@);qgsjgB$kkCZhaimHg&+k69~drbvRTacWKH;YCK(!rC?8 zP#cK5JPHSw;V;{Yji=55X~S+)%(8fuz}O>*F3)hR;STU`z6T1aM#Wd+FP(M5*@T1P z^06O;I20Sk!bxW<-O;E081KRdHZrtsGJflFRRFS zdi5w9OVDGSL3 zNrC7GVsGN=b;YH9jp8Z2$^!K@h=r-xV(aEH@#JicPy;A0k1>g1g^XeR`YV2HfmqXY zYbRwaxHvf}OlCAwHoVI&QBLr5R|THf?nAevV-=~V8;gCsX>jndvNOcFA+DI+zbh~# zZ7`qNk&w+_+Yp!}j;OYxIfx_{f0-ONc?mHCiCUak=>j>~>YR4#w# zuKz~UhT!L~GfW^CPqG8Lg)&Rc6y^{%3H7iLa%^l}cw_8UuG;8nn9)kbPGXS}p3!L_ zd#9~5CrH8xtUd?{d2y^PJg+z(xIfRU;`}^=OlehGN2=?}9yH$4Rag}*+AWotyxfCJ zHx=r7ZH>j2kV?%7WTtp+-HMa0)_*DBBmC{sd$)np&GEJ__kEd`xB5a2A z*J+yx>4o#ZxwA{;NjhU*1KT~=ZK~GAA;KZHDyBNTaWQ1+;tOFFthnD)DrCn`DjBZ% zk$N5B4^$`n^jNSOr=t(zi8TN4fpaccsb`zOPD~iY=UEK$0Y70bG{idLx@IL)7^(pL z{??Bnu=lDeguDrd%qW1)H)H`9otsOL-f4bSu};o9OXybo6J!Lek`a4ff>*O)BDT_g z<6@SrI|C9klY(>_PfA^qai7A_)VNE4c^ZjFcE$Isp>`e5fLc)rg@8Q_d^Uk24$2bn z9#}6kZ2ZxS9sI(RqT7?El2@B+($>eBQrNi_k#CDJ8D9}8$mmm z4oSKO^F$i+NG)-HE$O6s1--6EzJa?C{x=QgK&c=)b(Q9OVoAXYEEH20G|q$}Hue%~ zO3B^bF=t7t48sN zWh_zA`w~|){-!^g?6Mqf6ieV zFx~aPUOJGR=4{KsW7I?<=J2|lY`NTU=lt=%JE9H1vBpkcn=uq(q~=?iBt_-r(PLBM zP-0dxljJO>4Wq-;stY)CLB4q`-r*T$!K2o}?E-w_i>3_aEbA^MB7P5piwt1dI-6o!qWCy0 ztYy!x9arGTS?kabkkyv*yxvsPQ7Vx)twkS6z2T@kZ|kb8yjm+^$|sEBmvACeqbz)RmxkkDQX-A*K!YFziuhwb|ym>C$}U|J)4y z$(z#)GH%uV6{ec%Zy~AhK|+GtG8u@c884Nq%w`O^wv2#A(&xH@c5M`Vjk*SR_tJnq z0trB#aY)!EKW_}{#L3lph5ow=@|D5LzJYUFD6 z7XnUeo_V0DVSIKMFD_T0AqAO|#VFDc7c?c-Q%#u00F%!_TW1@JVnsfvm@_9HKWflBOUD~)RL``-!P;(bCON_4eVdduMO>?IrQ__*zE@7(OX zUtfH@AX*53&xJW*Pu9zcqxGiM>xol0I~QL5B%Toog3Jlenc^WbVgeBvV8C8AX^Vj& z^I}H})B=VboO%q1;aU5ACMh{yK4J;xlMc`jCnZR^!~LDs_MP&8;dd@4LDWw~*>#OT zeZHwdQWS!tt5MJQI~cw|Ka^b4c|qyd_ly(+Ql2m&AAw^ zQeSXDOOH!!mAgzAp0z)DD>6Xo``b6QwzUV@w%h}Yo>)a|xRi$jGuHQhJVA%>)PUvK zBQ!l0hq<3VZ*RnrDODP)>&iS^wf64C;MGqDvx>|p;35%6(u+IHoNbK z;Gb;TneFo*`zUKS6kwF*&b!U8e5m4YAo03a_e^!5BP42+r)LFhEy?_7U1IR<; z^0v|DhCYMSj<-;MtY%R@Fg;9Kky^pz_t2nJfKWfh5Eu@_l{^ph%1z{jkg5jQrkvD< z#vdK!nku*RrH~TdN~`wDs;d>XY1PH?O<4^U4lmA|wUW{Crrv#r%N>7k#{Gc44Fr|t z@UZP}Y-TrAmnEZ39A*@6;ccsR>)$A)S>$-Cj!=x$rz7IvjHIPM(TB+JFf{ehuIvY$ zsDAwREg*%|=>Hw$`us~RP&3{QJg%}RjJKS^mC_!U;E5u>`X`jW$}P`Mf}?7G7FX#{ zE(9u1SO;3q@ZhDL9O({-RD+SqqPX)`0l5IQu4q)49TUTkxR(czeT}4`WV~pV*KY&i zAl3~X%D2cPVD^B43*~&f%+Op)wl<&|D{;=SZwImydWL6@_RJjxP2g)s=dH)u9Npki zs~z9A+3fj0l?yu4N0^4aC5x)Osnm0qrhz@?nwG_`h(71P znbIewljU%T*cC=~NJy|)#hT+lx#^5MuDDnkaMb*Efw9eThXo|*WOQzJ*#3dmRWm@! zfuSc@#kY{Um^gBc^_Xdxnl!n&y&}R4yAbK&RMc+P^Ti;YIUh|C+K1|=Z^{nZ}}rxH*v{xR!i%qO~o zTr`WDE@k$M9o0r4YUFFeQO7xCu_Zgy)==;fCJ94M_rLAv&~NhfvcLWCoaGg2ao~3e zBG?Ms9B+efMkp}7BhmISGWmJsKI@a8b}4lLI48oWKY|8?zuuNc$lt5Npr+p7a#sWu zh!@2nnLBVJK!$S~>r2-pN||^w|fY`CT{TFnJy`B|e5;=+_v4l8O-fkN&UQbA4NKTyntd zqK{xEKh}U{NHoQUf!M=2(&w+eef77VtYr;xs%^cPfKLObyOV_9q<(%76-J%vR>w9!us-0c-~Y?_EVS%v!* z15s2s3eTs$Osz$JayyH|5nPAIPEX=U;r&p;K14G<1)bvn@?bM5kC{am|C5%hyxv}a z(DeSKI5ZfZ1*%dl8frIX2?);R^^~LuDOpNpk-2R8U1w92HmG1m&|j&J{EK=|p$;f9 z7Rs5|jr4r8k5El&qcuM+YRlKny%t+1CgqEWO>3;BSRZi(LA3U%Jm{@{y+A+w(gzA< z7dBq6a1sEWa4cD0W7=Ld9z0H7RI^Z7vl(bfA;72j?SWCo`#5mVC$l1Q2--%V)-uN* z9ha*s-AdfbDZ8R8*fpwjzx=WvOtmSzGFjC#X)hD%Caeo^OWjS(3h|d9_*U)l%{Ab8 zfv$yoP{OuUl@$(-sEVNt{*=qi5P=lpxWVuz2?I7Dc%BRc+NGNw+323^ z5BXGfS71oP^%apUo(Y#xkxE)y?>BFzEBZ}UBbr~R4$%b7h3iZu3S(|A;&HqBR{nK& z$;GApNnz=kNO^FL&nYcfpB7Qg;hGJPsCW44CbkG1@l9pn0`~oKy5S777uH)l{irK!ru|X+;4&0D;VE*Ii|<3P zUx#xUqvZT5kVQxsF#~MwKnv7;1pR^0;PW@$@T7I?s`_rD1EGUdSA5Q(C<>5SzE!vw z;{L&kKFM-MO>hy#-8z`sdVx})^(Dc-dw;k-h*9O2_YZw}|9^y-|8RQ`BWJUJL(Cer zP5Z@fNc>pTXABbTRY-B5*MphpZv6#i802giwV&SkFCR zGMETyUm(KJbh+&$8X*RB#+{surjr;8^REEt`2&Dubw3$mx>|~B5IKZJ`s_6fw zKAZx9&PwBqW1Oz0r0A4GtnZd7XTKViX2%kPfv+^X3|_}RrQ2e3l=KG_VyY`H?I5&CS+lAX5HbA%TD9u6&s#v!G> zzW9n4J%d5ye7x0y`*{KZvqyXUfMEE^ZIffzI=Hh|3J}^yx7eL=s+TPH(Q2GT-sJ~3 zI463C{(ag7-hS1ETtU;_&+49ABt5!A7CwLwe z=SoA8mYZIQeU;9txI=zcQVbuO%q@E)JI+6Q!3lMc=Gbj(ASg-{V27u>z2e8n;Nc*pf}AqKz1D>p9G#QA+7mqqrEjGfw+85Uyh!=tTFTv3|O z+)-kFe_8FF_EkTw!YzwK^Hi^_dV5x-Ob*UWmD-})qKj9@aE8g240nUh=g|j28^?v7 zHRTBo{0KGaWBbyX2+lx$wgXW{3aUab6Bhm1G1{jTC7ota*JM6t+qy)c5<@ zpc&(jVdTJf(q3xB=JotgF$X>cxh7k*(T`-V~AR+`%e?YOeALQ2Qud( zz35YizXt(aW3qndR}fTw1p()Ol4t!D1pitGNL95{SX4ywzh0SF;=!wf=?Q?_h6!f* zh7<+GFi)q|XBsvXZ^qVCY$LUa{5?!CgwY?EG;*)0ceFe&=A;!~o`ae}Z+6me#^sv- z1F6=WNd6>M(~ z+092z>?Clrcp)lYNQl9jN-JF6n&Y0mp7|I0dpPx+4*RRK+VQI~>en0Dc;Zfl+x z_e_b7s`t1_A`RP3$H}y7F9_na%D7EM+**G_Z0l_nwE+&d_kc35n$Fxkd4r=ltRZhh zr9zER8>j(EdV&Jgh(+i}ltESBK62m0nGH6tCBr90!4)-`HeBmz54p~QP#dsu%nb~W z7sS|(Iydi>C@6ZM(Us!jyIiszMkd)^u<1D+R@~O>HqZIW&kearPWmT>63%_t2B{_G zX{&a(gOYJx!Hq=!T$RZ&<8LDnxsmx9+TBL0gTk$|vz9O5GkK_Yx+55^R=2g!K}NJ3 zW?C;XQCHZl7H`K5^BF!Q5X2^Mj93&0l_O3Ea3!Ave|ixx+~bS@Iv18v2ctpSt4zO{ zp#7pj!AtDmti$T`e9{s^jf(ku&E|83JIJO5Qo9weT6g?@vX!{7)cNwymo1+u(YQ94 zopuz-L@|5=h8A!(g-MXgLJC0MA|CgQF8qlonnu#j z;uCeq9ny9QSD|p)9sp3ebgY3rk#y0DA(SHdh$DUm^?GI<>%e1?&}w(b zdip1;P2Z=1wM+$q=TgLP$}svd!vk+BZ@h<^4R=GS2+sri7Z*2f`9 z5_?i)xj?m#pSVchk-SR!2&uNhzEi+#5t1Z$o0PoLGz*pT64%+|Wa+rd5Z}60(j?X= z{NLjtgRb|W?CUADqOS@(*MA-l|E342NxRaxLTDqsOyfWWe%N(jjBh}G zm7WPel6jXijaTiNita+z(5GCO0NM=Melxud57PP^d_U## zbA;9iVi<@wr0DGB8=T9Ab#2K_#zi=$igyK48@;V|W`fg~7;+!q8)aCOo{HA@vpSy-4`^!ze6-~8|QE||hC{ICKllG9fbg_Y7v z$jn{00!ob3!@~-Z%!rSZ0JO#@>|3k10mLK0JRKP-Cc8UYFu>z93=Ab-r^oL2 zl`-&VBh#=-?{l1TatC;VweM^=M7-DUE>m+xO7Xi6vTEsReyLs8KJ+2GZ&rxw$d4IT zPXy6pu^4#e;;ZTsgmG+ZPx>piodegkx2n0}SM77+Y*j^~ICvp#2wj^BuqRY*&cjmL zcKp78aZt>e{3YBb4!J_2|K~A`lN=u&5j!byw`1itV(+Q_?RvV7&Z5XS1HF)L2v6ji z&kOEPmv+k_lSXb{$)of~(BkO^py&7oOzpjdG>vI1kcm_oPFHy38%D4&A4h_CSo#lX z2#oqMCTEP7UvUR3mwkPxbl8AMW(e{ARi@HCYLPSHE^L<1I}OgZD{I#YH#GKnpRmW3 z2jkz~Sa(D)f?V?$gNi?6)Y;Sm{&?~2p=0&BUl_(@hYeX8YjaRO=IqO7neK0RsSNdYjD zaw$g2sG(>JR=8Iz1SK4`*kqd_3-?;_BIcaaMd^}<@MYbYisWZm2C2|Np_l|8r9yM|JkUngSo@?wci(7&O9a z%|V(4C1c9pps0xxzPbXH=}QTxc2rr7fXk$9`a6TbWKPCz&p=VsB8^W96W=BsB|7bc zf(QR8&Ktj*iz)wK&mW`#V%4XTM&jWNnDF56O+2bo<3|NyUhQ%#OZE8$Uv2a@J>D%t zMVMiHh?es!Ex19q&6eC&L=XDU_BA&uR^^w>fpz2_`U87q_?N2y;!Z!bjoeKrzfC)} z?m^PM=(z{%n9K`p|7Bz$LuC7!>tFOuN74MFELm}OD9?%jpT>38J;=1Y-VWtZAscaI z_8jUZ#GwWz{JqvGEUmL?G#l5E=*m>`cY?m*XOc*yOCNtpuIGD+Z|kn4Xww=BLrNYS zGO=wQh}Gtr|7DGXLF%|`G>J~l{k^*{;S-Zhq|&HO7rC_r;o`gTB7)uMZ|WWIn@e0( zX$MccUMv3ABg^$%_lNrgU{EVi8O^UyGHPNRt%R!1#MQJn41aD|_93NsBQhP80yP<9 zG4(&0u7AtJJXLPcqzjv`S~5;Q|5TVGccN=Uzm}K{v)?f7W!230C<``9(64}D2raRU zAW5bp%}VEo{4Rko`bD%Ehf=0voW?-4Mk#d3_pXTF!-TyIt6U+({6OXWVAa;s-`Ta5 zTqx&8msH3+DLrVmQOTBOAj=uoxKYT3DS1^zBXM?1W+7gI!aQNPYfUl{3;PzS9*F7g zWJN8x?KjBDx^V&6iCY8o_gslO16=kh(|Gp)kz8qlQ`dzxQv;)V&t+B}wwdi~uBs4? zu~G|}y!`3;8#vIMUdyC7YEx6bb^1o}G!Jky4cN?BV9ejBfN<&!4M)L&lRKiuMS#3} z_B}Nkv+zzxhy{dYCW$oGC&J(Ty&7%=5B$sD0bkuPmj7g>|962`(Q{ZZMDv%YMuT^KweiRDvYTEop3IgFv#)(w>1 zSzH>J`q!LK)c(AK>&Ib)A{g`Fdykxqd`Yq@yB}E{gnQV$K!}RsgMGWqC3DKE(=!{}ekB3+(1?g}xF>^icEJbc z5bdxAPkW90atZT+&*7qoLqL#p=>t-(-lsnl2XMpZcYeW|o|a322&)yO_8p(&Sw{|b zn(tY$xn5yS$DD)UYS%sP?c|z>1dp!QUD)l;aW#`%qMtQJjE!s2z`+bTSZmLK7SvCR z=@I4|U^sCwZLQSfd*ACw9B@`1c1|&i^W_OD(570SDLK`MD0wTiR8|$7+%{cF&){$G zU~|$^Ed?TIxyw{1$e|D$050n8AjJvvOWhLtLHbSB|HIfjMp+gu>DraHZJRrdO53(= z+o-f{+qNog+qSLB%KY;5>Av6X(>-qYk3IIEwZ5~6a+P9lMpC^ z8CJ0q>rEpjlsxCvJm=kms@tlN4+sv}He`xkr`S}bGih4t`+#VEIt{1veE z{ZLtb_pSbcfcYPf4=T1+|BtR!x5|X#x2TZEEkUB6kslKAE;x)*0x~ES0kl4Dex4e- zT2P~|lT^vUnMp{7e4OExfxak0EE$Hcw;D$ehTV4a6hqxru0$|Mo``>*a5=1Ym0u>BDJKO|=TEWJ5jZu!W}t$Kv{1!q`4Sn7 zrxRQOt>^6}Iz@%gA3&=5r;Lp=N@WKW;>O!eGIj#J;&>+3va^~GXRHCY2}*g#9ULab zitCJt-OV0*D_Q3Q`p1_+GbPxRtV_T`jyATjax<;zZ?;S+VD}a(aN7j?4<~>BkHK7bO8_Vqfdq1#W&p~2H z&w-gJB4?;Q&pG9%8P(oOGZ#`!m>qAeE)SeL*t8KL|1oe;#+uOK6w&PqSDhw^9-&Fa zuEzbi!!7|YhlWhqmiUm!muO(F8-F7|r#5lU8d0+=;<`{$mS=AnAo4Zb^{%p}*gZL! zeE!#-zg0FWsSnablw!9$<&K(#z!XOW z;*BVx2_+H#`1b@>RtY@=KqD)63brP+`Cm$L1@ArAddNS1oP8UE$p05R=bvZoYz+^6 z<)!v7pRvi!u_-V?!d}XWQR1~0q(H3{d^4JGa=W#^Z<@TvI6J*lk!A zZ*UIKj*hyO#5akL*Bx6iPKvR3_2-^2mw|Rh-3O_SGN3V9GRo52Q;JnW{iTGqb9W99 z7_+F(Op6>~3P-?Q8LTZ-lwB}xh*@J2Ni5HhUI3`ct|*W#pqb>8i*TXOLn~GlYECIj zhLaa_rBH|1jgi(S%~31Xm{NB!30*mcsF_wgOY2N0XjG_`kFB+uQuJbBm3bIM$qhUyE&$_u$gb zpK_r{99svp3N3p4yHHS=#csK@j9ql*>j0X=+cD2dj<^Wiu@i>c_v zK|ovi7}@4sVB#bzq$n3`EgI?~xDmkCW=2&^tD5RuaSNHf@Y!5C(Is$hd6cuyoK|;d zO}w2AqJPS`Zq+(mc*^%6qe>1d&(n&~()6-ZATASNPsJ|XnxelLkz8r1x@c2XS)R*H(_B=IN>JeQUR;T=i3<^~;$<+8W*eRKWGt7c#>N`@;#!`kZ!P!&{9J1>_g8Zj zXEXxmA=^{8A|3=Au+LfxIWra)4p<}1LYd_$1KI0r3o~s1N(x#QYgvL4#2{z8`=mXy zQD#iJ0itk1d@Iy*DtXw)Wz!H@G2St?QZFz zVPkM%H8Cd2EZS?teQN*Ecnu|PrC!a7F_XX}AzfZl3fXfhBtc2-)zaC2eKx*{XdM~QUo4IwcGgVdW69 z1UrSAqqMALf^2|(I}hgo38l|Ur=-SC*^Bo5ej`hb;C$@3%NFxx5{cxXUMnTyaX{>~ zjL~xm;*`d08bG_K3-E+TI>#oqIN2=An(C6aJ*MrKlxj?-;G zICL$hi>`F%{xd%V{$NhisHSL~R>f!F7AWR&7b~TgLu6!3s#~8|VKIX)KtqTH5aZ8j zY?wY)XH~1_a3&>#j7N}0az+HZ;is;Zw(Am{MX}YhDTe(t{ZZ;TG}2qWYO+hdX}vp9 z@uIRR8g#y~-^E`Qyem(31{H0&V?GLdq9LEOb2(ea#e-$_`5Q{T%E?W(6 z(XbX*Ck%TQM;9V2LL}*Tf`yzai{0@pYMwBu%(I@wTY!;kMrzcfq0w?X`+y@0ah510 zQX5SU(I!*Fag4U6a7Lw%LL;L*PQ}2v2WwYF(lHx_Uz2ceI$mnZ7*eZ?RFO8UvKI0H z9Pq-mB`mEqn6n_W9(s~Jt_D~j!Ln9HA)P;owD-l~9FYszs)oEKShF9Zzcmnb8kZ7% zQ`>}ki1kwUO3j~ zEmh140sOkA9v>j@#56ymn_RnSF`p@9cO1XkQy6_Kog?0ivZDb`QWOX@tjMd@^Qr(p z!sFN=A)QZm!sTh(#q%O{Ovl{IxkF!&+A)w2@50=?a-+VuZt6On1;d4YtUDW{YNDN_ zG@_jZi1IlW8cck{uHg^g=H58lPQ^HwnybWy@@8iw%G! zwB9qVGt_?~M*nFAKd|{cGg+8`+w{j_^;nD>IrPf-S%YjBslSEDxgKH{5p)3LNr!lD z4ii)^%d&cCXIU7UK?^ZQwmD(RCd=?OxmY(Ko#+#CsTLT;p#A%{;t5YpHFWgl+@)N1 zZ5VDyB;+TN+g@u~{UrWrv)&#u~k$S&GeW)G{M#&Di)LdYk?{($Cq zZGMKeYW)aMtjmKgvF0Tg>Mmkf9IB#2tYmH-s%D_9y3{tfFmX1BSMtbe<(yqAyWX60 zzkgSgKb3c{QPG2MalYp`7mIrYg|Y<4Jk?XvJK)?|Ecr+)oNf}XLPuTZK%W>;<|r+% zTNViRI|{sf1v7CsWHvFrkQ$F7+FbqPQ#Bj7XX=#M(a~9^80}~l-DueX#;b}Ajn3VE z{BWI}$q{XcQ3g{(p>IOzFcAMDG0xL)H%wA)<(gl3I-oVhK~u_m=hAr&oeo|4lZbf} z+pe)c34Am<=z@5!2;_lwya;l?xV5&kWe}*5uBvckm(d|7R>&(iJNa6Y05SvlZcWBlE{{%2- z`86)Y5?H!**?{QbzGG~|k2O%eA8q=gxx-3}&Csf6<9BsiXC)T;x4YmbBIkNf;0Nd5 z%whM^!K+9zH>on_<&>Ws?^v-EyNE)}4g$Fk?Z#748e+GFp)QrQQETx@u6(1fk2!(W zWiCF~MomG*y4@Zk;h#2H8S@&@xwBIs|82R*^K(i*0MTE%Rz4rgO&$R zo9Neb;}_ulaCcdn3i17MO3NxzyJ=l;LU*N9ztBJ30j=+?6>N4{9YXg$m=^9@Cl9VY zbo^{yS@gU=)EpQ#;UIQBpf&zfCA;00H-ee=1+TRw@(h%W=)7WYSb5a%$UqNS@oI@= zDrq|+Y9e&SmZrH^iA>Of8(9~Cf-G(P^5Xb%dDgMMIl8gk6zdyh`D3OGNVV4P9X|EvIhplXDld8d z^YWtYUz@tpg*38Xys2?zj$F8%ivA47cGSl;hjD23#*62w3+fwxNE7M7zVK?x_`dBSgPK zWY_~wF~OEZi9|~CSH8}Xi>#8G73!QLCAh58W+KMJJC81{60?&~BM_0t-u|VsPBxn* zW7viEKwBBTsn_A{g@1!wnJ8@&h&d>!qAe+j_$$Vk;OJq`hrjzEE8Wjtm)Z>h=*M25 zOgETOM9-8xuuZ&^@rLObtcz>%iWe%!uGV09nUZ*nxJAY%&KAYGY}U1WChFik7HIw% zZP$3Bx|TG_`~19XV7kfi2GaBEhKap&)Q<9`aPs#^!kMjtPb|+-fX66z3^E)iwyXK7 z8)_p<)O{|i&!qxtgBvWXx8*69WO$5zACl++1qa;)0zlXf`eKWl!0zV&I`8?sG)OD2Vy?reNN<{eK+_ za4M;Hh%&IszR%)&gpgRCP}yheQ+l#AS-GnY81M!kzhWxIR?PW`G3G?} z$d%J28uQIuK@QxzGMKU_;r8P0+oIjM+k)&lZ39i#(ntY)*B$fdJnQ3Hw3Lsi8z&V+ zZly2}(Uzpt2aOubRjttzqrvinBFH4jrN)f0hy)tj4__UTwN)#1fj3-&dC_Vh7}ri* zfJ=oqLMJ-_<#rwVyN}_a-rFBe2>U;;1(7UKH!$L??zTbbzP#bvyg7OQBGQklJ~DgP zd<1?RJ<}8lWwSL)`jM53iG+}y2`_yUvC!JkMpbZyb&50V3sR~u+lok zT0uFRS-yx@8q4fPRZ%KIpLp8R#;2%c&Ra4p(GWRT4)qLaPNxa&?8!LRVdOUZ)2vrh zBSx&kB%#Y4!+>~)<&c>D$O}!$o{<1AB$M7-^`h!eW;c(3J~ztoOgy6Ek8Pwu5Y`Xion zFl9fb!k2`3uHPAbd(D^IZmwR5d8D$495nN2`Ue&`W;M-nlb8T-OVKt|fHk zBpjX$a(IR6*-swdNk@#}G?k6F-~c{AE0EWoZ?H|ZpkBxqU<0NUtvubJtwJ1mHV%9v?GdDw; zAyXZiD}f0Zdt-cl9(P1la+vQ$Er0~v}gYJVwQazv zH#+Z%2CIfOf90fNMGos|{zf&N`c0@x0N`tkFv|_9af3~<0z@mnf*e;%r*Fbuwl-IW z{}B3=(mJ#iwLIPiUP`J3SoP~#)6v;aRXJ)A-pD2?_2_CZ#}SAZ<#v7&Vk6{*i(~|5 z9v^nC`T6o`CN*n%&9+bopj^r|E(|pul;|q6m7Tx+U|UMjWK8o-lBSgc3ZF=rP{|l9 zc&R$4+-UG6i}c==!;I#8aDIbAvgLuB66CQLRoTMu~jdw`fPlKy@AKYWS-xyZzPg&JRAa@m-H43*+ne!8B7)HkQY4 zIh}NL4Q79a-`x;I_^>s$Z4J4-Ngq=XNWQ>yAUCoe&SMAYowP>r_O}S=V+3=3&(O=h zNJDYNs*R3Y{WLmBHc?mFEeA4`0Y`_CN%?8qbDvG2m}kMAiqCv`_BK z_6a@n`$#w6Csr@e2YsMx8udNWtNt=kcqDZdWZ-lGA$?1PA*f4?X*)hjn{sSo8!bHz zb&lGdAgBx@iTNPK#T_wy`KvOIZvTWqSHb=gWUCKXAiB5ckQI`1KkPx{{%1R*F2)Oc z(9p@yG{fRSWE*M9cdbrO^)8vQ2U`H6M>V$gK*rz!&f%@3t*d-r3mSW>D;wYxOhUul zk~~&ip5B$mZ~-F1orsq<|1bc3Zpw6)Ws5;4)HilsN;1tx;N6)tuePw& z==OlmaN*ybM&-V`yt|;vDz(_+UZ0m&&9#{9O|?0I|4j1YCMW;fXm}YT$0%EZ5^YEI z4i9WV*JBmEU{qz5O{#bs`R1wU%W$qKx?bC|e-iS&d*Qm7S=l~bMT{~m3iZl+PIXq{ zn-c~|l)*|NWLM%ysfTV-oR0AJ3O>=uB-vpld{V|cWFhI~sx>ciV9sPkC*3i0Gg_9G!=4ar*-W?D9)?EFL1=;O+W8}WGdp8TT!Fgv z{HKD`W>t(`Cds_qliEzuE!r{ihwEv1l5o~iqlgjAyGBi)$%zNvl~fSlg@M=C{TE;V zQkH`zS8b&!ut(m)%4n2E6MB>p*4(oV>+PT51#I{OXs9j1vo>9I<4CL1kv1aurV*AFZ^w_qfVL*G2rG@D2 zrs87oV3#mf8^E5hd_b$IXfH6vHe&lm@7On~Nkcq~YtE!}ad~?5*?X*>y`o;6Q9lkk zmf%TYonZM`{vJg$`lt@MXsg%*&zZZ0uUSse8o=!=bfr&DV)9Y6$c!2$NHyYAQf*Rs zk{^?gl9E z5Im8wlAsvQ6C2?DyG@95gUXZ3?pPijug25g;#(esF_~3uCj3~94}b*L>N2GSk%Qst z=w|Z>UX$m!ZOd(xV*2xvWjN&c5BVEdVZ0wvmk)I+YxnyK%l~caR=7uNQ=+cnNTLZ@&M!I$Mj-r{!P=; z`C2)D=VmvK8@T5S9JZoRtN!S*D_oqOxyy!q6Zk|~4aT|*iRN)fL)c>-yycR>-is0X zKrko-iZw(f(!}dEa?hef5yl%p0-v-8#8CX8!W#n2KNyT--^3hq6r&`)5Y@>}e^4h- zlPiDT^zt}Ynk&x@F8R&=)k8j$=N{w9qUcIc&)Qo9u4Y(Ae@9tA`3oglxjj6c{^pN( zQH+Uds2=9WKjH#KBIwrQI%bbs`mP=7V>rs$KG4|}>dxl_k!}3ZSKeEen4Iswt96GGw`E6^5Ov)VyyY}@itlj&sao|>Sb5 zeY+#1EK(}iaYI~EaHQkh7Uh>DnzcfIKv8ygx1Dv`8N8a6m+AcTa-f;17RiEed>?RT zk=dAksmFYPMV1vIS(Qc6tUO+`1jRZ}tcDP? zt)=7B?yK2RcAd1+Y!$K5*ds=SD;EEqCMG6+OqPoj{&8Y5IqP(&@zq@=A7+X|JBRi4 zMv!czlMPz)gt-St2VZwDD=w_S>gRpc-g zUd*J3>bXeZ?Psjohe;z7k|d<*T21PA1i)AOi8iMRwTBSCd0ses{)Q`9o&p9rsKeLaiY zluBw{1r_IFKR76YCAfl&_S1*(yFW8HM^T()&p#6y%{(j7Qu56^ZJx1LnN`-RTwimdnuo*M8N1ISl+$C-%=HLG-s} zc99>IXRG#FEWqSV9@GFW$V8!{>=lSO%v@X*pz*7()xb>=yz{E$3VE;e)_Ok@A*~El zV$sYm=}uNlUxV~6e<6LtYli1!^X!Ii$L~j4e{sI$tq_A(OkGquC$+>Rw3NFObV2Z)3Rt~Jr{oYGnZaFZ^g5TDZlg;gaeIP} z!7;T{(9h7mv{s@piF{-35L=Ea%kOp;^j|b5ZC#xvD^^n#vPH=)lopYz1n?Kt;vZmJ z!FP>Gs7=W{sva+aO9S}jh0vBs+|(B6Jf7t4F^jO3su;M13I{2rd8PJjQe1JyBUJ5v zcT%>D?8^Kp-70bP8*rulxlm)SySQhG$Pz*bo@mb5bvpLAEp${?r^2!Wl*6d7+0Hs_ zGPaC~w0E!bf1qFLDM@}zso7i~(``)H)zRgcExT_2#!YOPtBVN5Hf5~Ll3f~rWZ(UsJtM?O*cA1_W0)&qz%{bDoA}{$S&-r;0iIkIjbY~ zaAqH45I&ALpP=9Vof4OapFB`+_PLDd-0hMqCQq08>6G+C;9R~}Ug_nm?hhdkK$xpI zgXl24{4jq(!gPr2bGtq+hyd3%Fg%nofK`psHMs}EFh@}sdWCd!5NMs)eZg`ZlS#O0 zru6b8#NClS(25tXqnl{|Ax@RvzEG!+esNW-VRxba(f`}hGoqci$U(g30i}2w9`&z= zb8XjQLGN!REzGx)mg~RSBaU{KCPvQx8)|TNf|Oi8KWgv{7^tu}pZq|BS&S<53fC2K4Fw6>M^s$R$}LD*sUxdy6Pf5YKDbVet;P!bw5Al-8I1Nr(`SAubX5^D9hk6$agWpF}T#Bdf{b9-F#2WVO*5N zp+5uGgADy7m!hAcFz{-sS0kM7O)qq*rC!>W@St~^OW@R1wr{ajyYZq5H!T?P0e+)a zaQ%IL@X_`hzp~vRH0yUblo`#g`LMC%9}P;TGt+I7qNcBSe&tLGL4zqZqB!Bfl%SUa z6-J_XLrnm*WA`34&mF+&e1sPCP9=deazrM=Pc4Bn(nV;X%HG^4%Afv4CI~&l!Sjzb z{rHZ3od0!Al{}oBO>F*mOFAJrz>gX-vs!7>+_G%BB(ljWh$252j1h;9p~xVA=9_`P z5KoFiz96_QsTK%B&>MSXEYh`|U5PjX1(+4b#1PufXRJ*uZ*KWdth1<0 zsAmgjT%bowLyNDv7bTUGy|g~N34I-?lqxOUtFpTLSV6?o?<7-UFy*`-BEUsrdANh} zBWkDt2SAcGHRiqz)x!iVoB~&t?$yn6b#T=SP6Ou8lW=B>=>@ik93LaBL56ub`>Uo!>0@O8?e)$t(sgy$I z6tk3nS@yFFBC#aFf?!d_3;%>wHR;A3f2SP?Na8~$r5C1N(>-ME@HOpv4B|Ty7%jAv zR}GJwsiJZ5@H+D$^Cwj#0XA_(m^COZl8y7Vv(k=iav1=%QgBOVzeAiw zaDzzdrxzj%sE^c9_uM5D;$A_7)Ln}BvBx^=)fO+${ou%B*u$(IzVr-gH3=zL6La;G zu0Kzy5CLyNGoKRtK=G0-w|tnwI)puPDOakRzG(}R9fl7#<|oQEX;E#yCWVg95 z;NzWbyF&wGg_k+_4x4=z1GUcn6JrdX4nOVGaAQ8#^Ga>aFvajQN{!+9rgO-dHP zIp@%&ebVg}IqnRWwZRTNxLds+gz2@~VU(HI=?Epw>?yiEdZ>MjajqlO>2KDxA>)cj z2|k%dhh%d8SijIo1~20*5YT1eZTDkN2rc^zWr!2`5}f<2f%M_$to*3?Ok>e9$X>AV z2jYmfAd)s|(h?|B(XYrIfl=Wa_lBvk9R1KaP{90-z{xKi+&8=dI$W0+qzX|ZovWGOotP+vvYR(o=jo?k1=oG?%;pSqxcU* zWVGVMw?z__XQ9mnP!hziHC`ChGD{k#SqEn*ph6l46PZVkm>JF^Q{p&0=MKy_6apts z`}%_y+Tl_dSP(;Ja&sih$>qBH;bG;4;75)jUoVqw^}ee=ciV;0#t09AOhB^Py7`NC z-m+ybq1>_OO+V*Z>dhk}QFKA8V?9Mc4WSpzj{6IWfFpF7l^au#r7&^BK2Ac7vCkCn{m0uuN93Ee&rXfl1NBY4NnO9lFUp zY++C1I;_{#OH#TeP2Dp?l4KOF8ub?m6zE@XOB5Aiu$E~QNBM@;r+A5mF2W1-c7>ex zHiB=WJ&|`6wDq*+xv8UNLVUy4uW1OT>ey~Xgj@MMpS@wQbHAh>ysYvdl-1YH@&+Q! z075(Qd4C!V`9Q9jI4 zSt{HJRvZec>vaL_brKhQQwbpQd4_Lmmr0@1GdUeU-QcC{{8o=@nwwf>+dIKFVzPriGNX4VjHCa zTbL9w{Y2V87c2ofX%`(48A+4~mYTiFFl!e{3K^C_k%{&QTsgOd0*95KmWN)P}m zTRr{`f7@=v#+z_&fKYkQT!mJn{*crj%ZJz#(+c?>cD&2Lo~FFAWy&UG*Op^pV`BR^I|g?T>4l5;b|5OQ@t*?_Slp`*~Y3`&RfKD^1uLezIW(cE-Dq2z%I zBi8bWsz0857`6e!ahet}1>`9cYyIa{pe53Kl?8|Qg2RGrx@AlvG3HAL-^9c^1GW;)vQt8IK+ zM>!IW*~682A~MDlyCukldMd;8P|JCZ&oNL(;HZgJ>ie1PlaInK7C@Jg{3kMKYui?e!b`(&?t6PTb5UPrW-6DVU%^@^E`*y-Fd(p|`+JH&MzfEq;kikdse ziFOiDWH(D< zyV7Rxt^D0_N{v?O53N$a2gu%1pxbeK;&ua`ZkgSic~$+zvt~|1Yb=UfKJW2F7wC^evlPf(*El+#}ZBy0d4kbVJsK- z05>;>?HZO(YBF&v5tNv_WcI@O@LKFl*VO?L(!BAd!KbkVzo;v@~3v`-816GG?P zY+H3ujC>5=Am3RIZDdT#0G5A6xe`vGCNq88ZC1aVXafJkUlcYmHE^+Z{*S->ol%-O znm9R0TYTr2w*N8Vs#s-5=^w*{Y}qp5GG)Yt1oLNsH7y~N@>Eghms|K*Sdt_u!&I}$ z+GSdFTpbz%KH+?B%Ncy;C`uW6oWI46(tk>r|5|-K6)?O0d_neghUUOa9BXHP*>vi; z={&jIGMn-92HvInCMJcyXwHTJ42FZp&Wxu+9Rx;1x(EcIQwPUQ@YEQQ`bbMy4q3hP zNFoq~Qd0=|xS-R}k1Im3;8s{BnS!iaHIMLx)aITl)+)?Yt#fov|Eh>}dv@o6R{tG>uHsy&jGmWN5+*wAik|78(b?jtysPHC#e+Bzz~V zS3eEXv7!Qn4uWi!FS3B?afdD*{fr9>B~&tc671fi--V}~E4un;Q|PzZRwk-azprM$4AesvUb5`S`(5x#5VJ~4%ET6&%GR$}muHV-5lTsCi_R|6KM(g2PCD@|yOpKluT zakH!1V7nKN)?6JmC-zJoA#ciFux8!)ajiY%K#RtEg$gm1#oKUKX_Ms^%hvKWi|B=~ zLbl-L)-=`bfhl`>m!^sRR{}cP`Oim-{7}oz4p@>Y(FF5FUEOfMwO!ft6YytF`iZRq zfFr{!&0Efqa{1k|bZ4KLox;&V@ZW$997;+Ld8Yle91he{BfjRhjFTFv&^YuBr^&Pe zswA|Bn$vtifycN8Lxr`D7!Kygd7CuQyWqf}Q_PM}cX~S1$-6xUD%-jrSi24sBTFNz(Fy{QL2AmNbaVggWOhP;UY4D>S zqKr!UggZ9Pl9Nh_H;qI`-WoH{ceXj?m8y==MGY`AOJ7l0Uu z)>M%?dtaz2rjn1SW3k+p`1vs&lwb%msw8R!5nLS;upDSxViY98IIbxnh{}mRfEp=9 zbrPl>HEJeN7J=KnB6?dwEA6YMs~chHNG?pJsEj#&iUubdf3JJwu=C(t?JpE6xMyhA3e}SRhunDC zn-~83*9=mADUsk^sCc%&&G1q5T^HR9$P#2DejaG`Ui*z1hI#h7dwpIXg)C{8s< z%^#@uQRAg-$z&fmnYc$Duw63_Zopx|n{Bv*9Xau{a)2%?H<6D>kYY7_)e>OFT<6TT z0A}MQLgXbC2uf`;67`mhlcUhtXd)Kbc$PMm=|V}h;*_%vCw4L6r>3Vi)lE5`8hkSg zNGmW-BAOO)(W((6*e_tW&I>Nt9B$xynx|sj^ux~?q?J@F$L4;rnm_xy8E*JYwO-02u9_@@W0_2@?B@1J{y~Q39N3NX^t7#`=34Wh)X~sU&uZWgS1Z09%_k|EjA4w_QqPdY`oIdv$dJZ;(!k)#U8L+|y~gCzn+6WmFt#d{OUuKHqh1-uX_p*Af8pFYkYvKPKBxyid4KHc}H` z*KcyY;=@wzXYR{`d{6RYPhapShXIV?0cg_?ahZ7do)Ot#mxgXYJYx}<%E1pX;zqHd zf!c(onm{~#!O$2`VIXezECAHVd|`vyP)Uyt^-075X@NZDBaQt<>trA3nY-Dayki4S zZ^j6CCmx1r46`4G9794j-WC0&R9(G7kskS>=y${j-2;(BuIZTLDmAyWTG~`0)Bxqk zd{NkDe9ug|ms@0A>JVmB-IDuse9h?z9nw!U6tr7t-Lri5H`?TjpV~8(gZWFq4Vru4 z!86bDB;3lpV%{rZ`3gtmcRH1hjj!loI9jN>6stN6A*ujt!~s!2Q+U1(EFQEQb(h4E z6VKuRouEH`G6+8Qv2C)K@^;ldIuMVXdDDu}-!7FS8~k^&+}e9EXgx~)4V4~o6P^52 z)a|`J-fOirL^oK}tqD@pqBZi_;7N43%{IQ{v&G9^Y^1?SesL`;Z(dt!nn9Oj5Odde%opv&t zxJ><~b#m+^KV&b?R#)fRi;eyqAJ_0(nL*61yPkJGt;gZxSHY#t>ATnEl-E%q$E16% zZdQfvhm5B((y4E3Hk6cBdwGdDy?i5CqBlCVHZr-rI$B#>Tbi4}Gcvyg_~2=6O9D-8 zY2|tKrNzbVR$h57R?Pe+gUU_il}ZaWu|Az#QO@};=|(L-RVf0AIW zq#pO+RfM7tdV`9lI6g;{qABNId`fG%U9Va^ravVT^)CklDcx)YJKeJdGpM{W1v8jg z@&N+mR?BPB=K1}kNwXk_pj44sd>&^;d!Z~P>O78emE@Qp@&8PyB^^4^2f7e)gekMv z2aZNvP@;%i{+_~>jK7*2wQc6nseT^n6St9KG#1~Y@$~zR_=AcO2hF5lCoH|M&c{vR zSp(GRVVl=T*m~dIA;HvYm8HOdCkW&&4M~UDd^H)`p__!4k+6b)yG0Zcek8OLw$C^K z3-BbLiG_%qX|ZYpXJ$(c@aa7b4-*IQkDF}=gZSV`*ljP|5mWuHSCcf$5qqhZTv&P?I$z^>}qP(q!Aku2yA5vu38d8x*q{6-1`%PrE_r0-9Qo?a#7Zbz#iGI7K<(@k^|i4QJ1H z4jx?{rZbgV!me2VT72@nBjucoT zUM9;Y%TCoDop?Q5fEQ35bCYk7!;gH*;t9t-QHLXGmUF;|vm365#X)6b2Njsyf1h9JW#x$;@x5Nx2$K$Z-O3txa%;OEbOn6xBzd4n4v)Va=sj5 z%rb#j7{_??Tjb8(Hac<^&s^V{yO-BL*uSUk2;X4xt%NC8SjO-3?;Lzld{gM5A=9AV z)DBu-Z8rRvXXwSVDH|dL-3FODWhfe1C_iF``F05e{dl(MmS|W%k-j)!7(ARkV?6r~ zF=o42y+VapxdZn;GnzZfGu<6oG-gQ7j7Zvgo7Am@jYxC2FpS@I;Jb%EyaJDBQC(q% zKlZ}TVu!>;i3t~OAgl@QYy1X|T~D{HOyaS*Bh}A}S#a9MYS{XV{R-|niEB*W%GPW! zP^NU(L<}>Uab<;)#H)rYbnqt|dOK(-DCnY==%d~y(1*{D{Eo1cqIV8*iMfx&J*%yh zx=+WHjt0q2m*pLx8=--UqfM6ZWjkev>W-*}_*$Y(bikH`#-Gn#!6_ zIA&kxn;XYI;eN9yvqztK-a113A%97in5CL5Z&#VsQ4=fyf&3MeKu70)(x^z_uw*RG zo2Pv&+81u*DjMO6>Mrr7vKE2CONqR6C0(*;@4FBM;jPIiuTuhQ-0&C)JIzo_k>TaS zN_hB;_G=JJJvGGpB?uGgSeKaix~AkNtYky4P7GDTW6{rW{}V9K)Cn^vBYKe*OmP!; zohJs=l-0sv5&pL6-bowk~(swtdRBZQHh8)m^r2+qTtZ zt4m$B?OQYNyfBA0E)g28a*{)a=%%f-?{F;++-Xs#5|7kSHTD*E9@$V ztE%7zX4A(L`n)FY8Y4pOnKC|Pf)j$iR#yP;V0+|Hki+D;t4I4BjkfdYliK9Gf6RYw z;3px$Ud5aTd`yq$N7*WOs!{X91hZZ;AJ9iQOH%p;v$R%OQum_h#rq9*{ve(++|24z zh2P;{-Z?u#rOqd0)D^_Ponv(Y9KMB9#?}nJdUX&r_rxF0%3__#8~ZwsyrSPmtWY27 z-54ZquV2t_W!*+%uwC=h-&_q~&nQer0(FL74to%&t^byl^C?wTaZ-IS9OssaQFP)1 zAov0o{?IRAcCf+PjMWSdmP42gysh|c9Ma&Q^?_+>>+-yrC8WR;*XmJ;>r9v*>=W}tgWG;WIt{~L8`gk8DP{dSdG z4SDM7g5ahMHYHHk*|mh9{AKh-qW7X+GEQybJt9A@RV{gaHUAva+=lSroK^NUJYEiL z?X6l9ABpd)9zzA^;FdZ$QQs#uD@hdcaN^;Q=AXlbHv511Meye`p>P4Y2nblEDEeZo}-$@g&L98Aih6tgLz--${eKTxymIipy0xSYgZZ zq^yyS4yNPTtPj-sM?R8@9Q1gtXPqv{$lb5i|C1yymwnGdfYV3nA-;5!Wl zD0fayn!B^grdE?q^}ba{-LIv*Z}+hZm_F9c$$cW!bx2DgJD&6|bBIcL@=}kQA1^Eh zXTEznqk)!!IcTl>ey?V;X8k<+C^DRA{F?T*j0wV`fflrLBQq!l7cbkAUE*6}WabyF zgpb+|tv=aWg0i}9kBL8ZCObYqHEycr5tpc-$|vdvaBsu#lXD@u_e1iL z{h>xMRS0a7KvW?VttrJFpX^5DC4Bv4cp6gNG6#8)7r7IxXfSNSp6)_6tZ4l>(D+0I zPhU)N!sKywaBusHdVE!yo5$20JAU8V_XcW{QmO!p*~ns8{2~bhjydnmA&=r zX9NSM9QYogYMDZ~kS#Qx`mt>AmeR3p@K$`fbJ%LQ1c5lEOz<%BS<}2DL+$>MFcE%e zlxC)heZ7#i80u?32eOJI9oQRz0z;JW@7Th4q}YmQ-`Z?@y3ia^_)7f37QMwDw~<-@ zT)B6fftmK_6YS!?{uaj5lLxyR++u*ZY2Mphm5cd7PA5=%rd)95hJ9+aGSNfjy>Ylc zoI0nGIT3sKmwX8h=6CbvhVO+ehFIR155h8iRuXZx^cW>rq5K4z_dvM#hRER=WR@THs%WELI9uYK9HN44Em2$#@k)hD zicqRPKV#yB;UlcsTL_}zCMK0T;eXHfu`y2(dfwm(v)IBbh|#R>`2cot{m7}8_X&oD zr@94PkMCl%d3FsC4pil=#{3uv^+)pvxfwmPUr)T)T|GcZVD$wVj$mjkjDs`5cm8N! zXVq2CvL;gWGpPI4;9j;2&hS*o+LNp&C5Ac=OXx*W5y6Z^az)^?G0)!_iAfjH5wiSE zD(F}hQZB#tF5iEx@0sS+dP70DbZ*<=5X^)Pxo^8aKzOzuyc2rq=<0-k;Y_ID1>9^v z+)nc36}?>jen*1%OX3R*KRASj${u$gZ$27Hpcj=95kK^aLzxhW6jj_$w6}%#1*$5D zG1H_vYFrCSwrRqYw*9<}OYAOQT)u%9lC`$IjZV<4`9Sc;j{Qv_6+uHrYifK&On4V_7yMil!0Yv55z@dFyD{U@Sy>|vTX=P_( zRm<2xj*Z}B30VAu@0e+}at*y?wXTz|rPalwo?4ZZc>hS0Ky6~mi@kv#?xP2a;yt?5=(-CqvP_3&$KdjB7Ku;# z`GLE*jW1QJB5d&E?IJO?1+!Q8HQMGvv^RuFoi=mM4+^tOqvX%X&viB%Ko2o-v4~~J z267ui;gsW?J=qS=D*@*xJvAy3IOop5bEvfR4MZC>9Y4Z$rGI|EHNNZ7KX;Ix{xSvm z-)Cau-xuTm|7`4kUdXvd_d^E=po(76ELfq5OgxIt3aqDy#zBfIy-5<3gpn{Ce`-ha z<;6y@{Bgqw?c~h*&j{FozQCh=`Lv-5Iw!KdSt;%GDOq%=(V!dJ-}|}|0o5G2kJj6{ z`jCSPs$9Fe8O(+qALZiJ$WtR=<@GvsdM)IJ`7XrBfW0iyYE#Vy^e@zbysg*B5Z_kSL6<)vqoaH zQ{!9!*{e9UZo^h+qZ`T@LfVwAEwc&+9{C8c%oj41q#hyn<&zA9IIur~V|{mmu`n5W z8)-Ou$YgjQ*PMIqHhZ_9E?(uoK0XM5aQkarcp}WT^7b^FC#^i>#8LGZ9puDuXUYas z7caX)V5U6uY-L5Wl%)j$qRkR;7@3T*N64YK_!`Fw=>CAwe~2loI1<>DZW&sb7Q)X;6E08&$h! z2=c1i4UOO{R4TmkTz+o9n`}+%d%blR6P;5{`qjtxlN$~I%tMMDCY`~e{+mRF!rj5( z3ywv)P_PUUqREu)TioPkg&5RKjY6z%pRxQPQ{#GNMTPag^S8(8l{!{WGNs2U1JA-O zq02VeYcArhTAS;v3);k(&6ayCH8SXN@r;1NQeJ*y^NHM+zOd;?t&c!Hq^SR_w6twGV8dl>j zjS+Zc&Yp7cYj&c1y3IxQ%*kWiYypvoh(k8g`HrY<_Bi-r%m-@SLfy-6mobxkWHxyS z>TtM2M4;Uqqy|+8Q++VcEq$PwomV1D4UzNA*Tgkg9#Gpz#~&iPf|Czx!J?qss?e|3 z4gTua75-P{2X7w9eeK3~GE0ip-D;%%gTi)8bR~Ez@)$gpuS~jZs`CrO5SR-Xy7bkA z89fr~mY}u4A$|r1$fe-;T{yJh#9Ime1iRu8eo?uY9@yqAU3P!rx~SsP;LTBL zeoMK(!;(Zt8313 z3)V)q_%eflKW?BnMZa}6E0c7t!$-mC$qt44OME5F(6B$E8w*TUN-h}0dOiXI+TH zYFrr&k1(yO(|J0vP|{22@Z}bxm@7BkjO)f)&^fv|?_JX+s)1*|7X7HH(W?b3QZ3!V|~m?8}uJsF>NvE4@fik zjyyh+U*tt`g6v>k9ub88a;ySvS1QawGn7}aaR**$rJA=a#eUT~ngUbJ%V=qsFIekLbv!YkqjTG{_$F;$w19$(ivIs*1>?2ka%uMOx@B9`LD zhm~)z@u4x*zcM1WhiX)!U{qOjJHt1xs{G1S?rYe)L)ntUu^-(o_dfqZu)}W(X%Uu| zN*qI@&R2fB#Jh|Mi+eMrZDtbNvYD3|v0Kx>E#Ss;Be*T$@DC!2A|mb%d}TTN3J+c= zu@1gTOXFYy972S+=C;#~)Z{Swr0VI5&}WYzH22un_Yg5o%f9fvV(`6!{C<(ZigQ2`wso)cj z9O12k)15^Wuv#rHpe*k5#4vb%c znP+Gjr<-p%01d<+^yrSoG?}F=eI8X;?=Fo2a~HUiJ>L!oE#9tXRp!adg-b9D;(6$E zeW0tH$US04zTX$OxM&X+2ip>KdFM?iG_fgOD-qB|uFng8*#Z5jgqGY=zLU?4!OlO#~YBTB9b9#~H@nqQ#5 z6bV));d?IJTVBC+79>rGuy1JgxPLy$dA7;_^^L)02m}XLjFR*qH`eI~+eJo(7D`LH z(W%lGnGK+Vk_3kyF*zpgO=1MxMg?hxe3}}YI>dVs8l}5eWjYu4=w6MWK09+05 zGdpa#$awd>Q|@aZa*z{5F3xy3n@E4YT9%TmMo0jxW59p0bI?&S}M+ z&^NG%rf7h*m9~p#b19|`wO5OMY-=^XT+=yrfGNpl<&~~FGsx_`IaFn+sEgF$hgOa~oAVAiu^a$jHcqkE=dj`ze z=axsfrzzh6VGD0x#6Ff=t%+VTiq!n6^gv*uIUD<9fOhvR;al5kcY${uunn}-!74<7 zmP^3cl-kyN(QY!!Z-^PY-OUkh=3ZWk6>le$_Q&xk4cgH{?i)C%2RM@pX5Q{jdSlo! zVau5v44cQX5|zQlQDt;dCg)oM0B<=P1CR!W%!^m$!{pKx;bn9DePJjWBX)q!`$;0K zqJIIyD#aK;#-3&Nf=&IhtbV|?ZGYHSphp~6th`p2rkw&((%kBV7<{siEOU7AxJj+FuRdDu$ zcmTW8usU_u!r)#jg|J=Gt{##7;uf4A5cdt6Y02}f(d2)z~ z)CH~gVAOwBLk$ZiIOn}NzDjvfw(w$u|BdCBI#)3xB-Ot?nz?iR38ayCm48M=_#9r7 zw8%pwQ<9mbEs5~_>pN3~#+Er~Q86J+2TDXM6umCbukd-X6pRIr5tF?VauT8jW> zY^#)log>jtJs2s3xoiPB7~8#1ZMv>Zx0}H58k-@H2huNyw~wsl0B8j)H5)H9c7y&i zp8^0;rKbxC1eEZ-#Qxvz)Xv$((8lK9I>BspPajluysw^f#t9P;OUis43mmEzX+lk* zc4T-Ms9_687GR+~QS#0~vxK#DSGN=a-m(@eZTqw2<+lN9>R~gK2)3;sT4%nI%Y|0m zX9SPR!>?~s=j5H4WMqeTW8QaLZ=1bWS5I3xZ&$(ypc=tHrv+hX@s)VG(tc!yvLM7n zshN=C#v={X1r;)xn0Pow_1eMhkn!{;x$BJ#PIz)m585&%cmzk;btQzZAN_^zis;n? z?6I~bN?s;7vg_dtoTc4A5Ow*Rb}No#UYl)sN|RmoYo}k^cKLXd8F`44?RrokkPvN5 ztUrx;U~B;jbE_qGd3n0j2i}A{enJvJ?gSF~NQj~EP5vM-w4@;QQ5n(Npic}XNW6B0 zq9F4T%6kp7qGhd0vpQcz+nMk8GOAmbz8Bt4@GtewGr6_>Xj>ge)SyfY}nu>Y!a@HoIx(StD zx`!>RT&}tpBL%nOF%7XIFW?n1AP*xthCMzhrU6G!U6?m4!CPWTvn#Yaoi_95CT2!L z|B=5zeRW30&ANGN>J9#GtCm&3SF6n4TqDz<-{@ZXkrkRDCpV$DwCtI^e&3i1A{Ar&JZtS^c+lyPa6 z%JJr42S_;eFC#M~bdtQePhOU32WDiZ4@H&af)z#$Y|hnQNb)8(3?1Ad>5uaZ1z zU~!jt3XUI@gpWb8tWTyH7DGvKvzYfqNIy3P{9vpwz_C-QL&`+8Io$F5PS-@YQJoEO z17D9P(+sXajWSH_8&C?fn>rTLX+(?KiwX#JNV)xE0!Q@>Tid$V2#r4y6fkph?YZ>^ z(o^q(0*P->3?I0cELXJn(N|#qTm6 zAPIL~n)m!50;*?5=MOOc4Wk;w(0c$(!e?vpV23S|n|Y7?nyc8)fD8t-KI&nTklH&BzqQ}D(1gH3P+5zGUzIjT~x`;e8JH=86&5&l-DP% z)F+Et(h|GJ?rMy-Zrf>Rv@<3^OrCJ1xv_N*_@-K5=)-jP(}h1Rts44H&ou8!G_C1E zhTfUDASJ2vu!4@j58{NN;78i?6__xR75QEDC4JN{>RmgcNrn-EOpEOcyR<8FS@RB@ zH!R7J=`KK^u06eeI|X@}KvQmdKE3AmAy8 zM4IIvde#e4O(iwag`UL5yQo>6&7^=D4yE-Eo9$9R2hR} zn;Z9i-d=R-xZl4@?s%8|m1M`$J6lW1r0Y)+8q$}Vn4qyR1jqTjGH;@Z!2KiGun2~x zaiEfzVT<|_b6t}~XPeflAm8hvCHP3Bp*tl{^y_e{Jsn@w+KP{7}bH_s=1S2E1sj=18a39*Ag~lbkT^_OQuYQey=b zW^{0xlQ@O$^cSxUZ8l(Mspg8z0cL*?yH4;X2}TdN)uN31A%$3$a=4;{S@h#Y(~i%) zc=K7Ggl=&2hYVic*W65gpSPE70pU;FN@3k?BYdNDKv6wlsBAF^);qiqI zhklsX4TaWiC%VbnZ|yqL+Pcc;(#&E*{+Rx&<&R{uTYCn^OD|mAk4%Q7gbbgMnZwE{ zy7QMK%jIjU@ye?0; z;0--&xVeD}m_hq9A8a}c9WkI2YKj8t!Mkk!o%AQ?|CCBL9}n570}OmZ(w)YI6#QS&p<={tcek*D{CPR%eVA1WBGUXf z%gO2vL7iVDr1$!LAW)1@H>GoIl=&yyZ7=*9;wrOYQ}O}u>h}4FWL?N2ivURlUi11- zl{G0fo`9?$iAEN<4kxa#9e0SZPqa{pw?K=tdN5tRc7HDX-~Ta6_+#s9W&d`6PB7dF*G@|!Mc}i zc=9&T+edI(@la}QU2An#wlkJ&7RmTEMhyC_A8hWM54?s1WldCFuBmT5*I3K9=1aj= z6V@93P-lUou`xmB!ATp0(We$?)p*oQs;(Kku15~q9`-LSl{(Efm&@%(zj?aK2;5}P z{6<@-3^k^5FCDT@Z%XABEcuPoumYkiD&)-8z2Q}HO9OVEU3WM;V^$5r4q>h^m73XF z5!hZ7SCjfxDcXyj(({vg8FU(m2_}36L_yR>fnW)u=`1t@mPa76`2@%8v@2@$N@TE` z)kYhGY1jD;B9V=Dv1>BZhR9IJmB?X9Wj99f@MvJ2Fim*R`rsRilvz_3n!nPFLmj({EP!@CGkY5R*Y_dSO{qto~WerlG}DMw9k+n}pk z*nL~7R2gB{_9=zpqX|*vkU-dx)(j+83uvYGP?K{hr*j2pQsfXn<_As6z%-z+wFLqI zMhTkG>2M}#BLIOZ(ya1y8#W<+uUo@(43=^4@?CX{-hAuaJki(_A(uXD(>`lzuM~M;3XA48ZEN@HRV{1nvt?CV)t;|*dow0Ue2`B*iA&!rI`fZQ=b28= z_dxF}iUQ8}nq0SA4NK@^EQ%=)OY;3fC<$goJ&Kp|APQ@qVbS-MtJQBc)^aO8mYFsbhafeRKdHPW&s^&;%>v zlTz`YE}CuQ@_X&mqm{+{!h2r)fPGeM_Ge4RRYQkrma`&G<>RW<>S(?#LJ}O-t)d$< zf}b0svP^Zu@)MqwEV^Fb_j zPYYs~vmEC~cOIE6Nc^@b@nyL!w5o?nQ!$mGq(Pa|1-MD}K0si<&}eag=}WLSDO zE4+eA~!J(K}605x&4 zT72P7J^)Y)b(3g2MZ@1bv%o1ggwU4Yb!DhQ=uu-;vX+Ix8>#y6wgNKuobvrPNx?$3 zI{BbX<=Y-cBtvY&#MpGTgOLYU4W+csqWZx!=AVMb)Z;8%#1*x_(-)teF>45TCRwi1 z)Nn>hy3_lo44n-4A@=L2gI$yXCK0lPmMuldhLxR8aI;VrHIS{Dk}yp= zwjhB6v@0DN=Hnm~3t>`CtnPzvA*Kumfn5OLg&-m&fObRD};c}Hf?n&mS< z%$wztc%kjWjCf-?+q(bZh9k~(gs?i4`XVfqMXvPVkUWfm4+EBF(nOkg!}4u)6I)JT zU6IXqQk?p1a2(bz^S;6ZH3Wy9!JvbiSr7%c$#G1eK2^=~z1WX+VW)CPD#G~)13~pX zErO(>x$J_4qu-)lNlZkLj2}y$OiKn0ad5Imu5p-2dnt)(YI|b7rJ3TBUQ8FB8=&ym50*ibd2NAbj z;JA&hJ$AJlldM+tO;Yl3rBOFiP8fDdF?t(`gkRpmT9inR@uX{bThYNmxx-LN5K8h0 ztS%w*;V%b`%;-NARbNXn9he&AO4$rvmkB#;aaOx?Wk|yBCmN{oMTK&E)`s&APR<-5 z#;_e75z;LJ)gBG~h<^`SGmw<$Z3p`KG|I@7Pd)sTJnouZ1hRvm3}V+#lPGk4b&A#Y z4VSNi8(R1z7-t=L^%;*;iMTIAjrXl;h106hFrR{n9o8vlz?+*a1P{rEZ2ie{luQs} zr6t746>eoqiO5)^y;4H%2~&FT*Qc*9_oC2$+&syHWsA=rn3B~4#QEW zf4GT3i_@)f(Fj}gAZj`7205M8!B&HhmbgyZB& z+COyAVNxql#DwfP;H48Yc+Y~ChV6b9auLnfXXvpjr<~lQ@>VbCpQvWz=lyVf1??_c zAo3C^otZD@(v?X)UX*@w?TF|F8KF>l7%!Dzu+hksSA^akEkx8QD(V(lK+HBCw6C}2onVExW)f$ zncm*HI(_H;jF@)6eu}Tln!t?ynRkcqBA5MitIM@L^(4_Ke}vy7c%$w{(`&7Rn=u>oDM+Z^RUYcbSOPwT(ONyq76R>$V6_M_UP4vs=__I#io{{((| zy5=k=oVr-Qt$FImP~+&sN8rf2UH*vRMpwohPc@9?id17La4weIfBNa>1Djy+1=ugn z@}Zs;eFY1OC}WBDxDF=i=On_33(jWE-QYV)HbQ^VM!n>Ci9_W0Zofz7!m>do@KH;S z4k}FqEAU2)b%B_B-QcPnM5Zh=dQ+4|DJoJwo?)f2nWBuZE@^>a(gP~ObzMuyNJTgJFUPcH`%9UFA(P23iaKgo0)CI!SZ>35LpFaD7 z)C2sW$ltSEYNW%%j8F;yK{iHI2Q^}coF@LX`=EvxZb*_O;2Z0Z5 z7 zlccxmCfCI;_^awp|G748%Wx%?t9Sh8!V9Y(9$B?9R`G)Nd&snX1j+VpuQ@GGk=y(W zK|<$O`Cad`Y4#W3GKXgs%lZduAd1t1<7LwG4*zaStE*S)XXPFDyKdgiaVXG2)LvDn zf}eQ_S(&2!H0Mq1Yt&WpM1!7b#yt_ie7naOfX129_E=)beKj|p1VW9q>>+e$3@G$K zrB%i_TT1DHjOf7IQ8)Wu4#K%ZSCDGMP7Ab|Kvjq7*~@ewPm~h_-8d4jmNH<&mNZC@CI zKxG5O08|@<4(6IEC@L-lcrrvix&_Dj4tBvl=8A}2UX|)~v#V$L22U}UHk`B-1MF(t zU6aVJWR!>Y0@4m0UA%Sq9B5;4hZvsOu=>L`IU4#3r_t}os|vSDVMA??h>QJ1FD1vR z*@rclvfD!Iqoxh>VP+?b9TVH8g@KjYR@rRWQy44A`f6doIi+8VTP~pa%`(Oa@5?=h z8>YxNvA##a3D0)^P|2|+0~f|UsAJV=q(S>eq-dehQ+T>*Q@qN zU8@kdpU5gGk%ozt?%c8oM6neA?GuSsOfU_b1U)uiEP8eRn~>M$p*R z43nSZs@^ahO78s zulbK@@{3=2=@^yZ)DuIC$ki;`2WNbD_#`LOHN9iMsrgzt-T<8aeh z(oXrqI$Kgt6)Icu=?11NWs>{)_ed1wh>)wv6RYNUA-C&bejw{cBE_5Wzeo!AHdTd+ z)d(_IKN7z^n|As~3XS=cCB_TgM7rK;X586re`{~Foml$aKs zb!4Pe7hEP|370EWwn$HKPM!kL94UPZ1%8B^e5fB+=Iw^6=?5n3tZGYjov83CLB&OQ++p)WCMeshCv_9-~G9C_2x`LxTDjUcW$l6e!6-&a^fM3oP9*g(H zmCk0nGt1UMdU#pfg1G0um5|sc|KO<+qU1E4iBF~RvN*+`7uNHH^gu{?nw2DSCjig% zI@ymKZSK=PhHJa(jW&xeApv&JcfSmNJ4uQ|pY=Lcc>=J|{>5Ug3@x#R_b@55xFgfs za^ANzWdD$ZYtFs$d7+oiw0ZmPk2&l|< zc8()wfiJx@EGpQT zG$8iLkQZ-086doF1R zh<#9cz_vRsJdoXbD=QgOtpm}cFAJX8c}>Jew;PQJSXSb^;wlC zxXLHTS|!GZ-VK_4wV<9bk4RUmlsByzW_^b>)$6R+jQ}^wco1nMA`9Lncs;&QGp!`5Tx#aXXU?}5_RrtUY zx(EMzDhl-a^y^f5yfFLMnOO#u)l69&4M?|ne|2EV>zQ}4JQCBel?~2I4?D|>L$%H(peOOII!U}i z-j)*h1rODe9{0`xmhG;`AKqw1p0_KhEIU8)DoGnEn9wAhXPaxO_(jNSij~J5m$P*$ z9Mt(t;eV}2+i|kjQpBFcNb7_(VbuF<;RQB~R~p>2*Lg>a&7DEEuq*I%Ls4{zHeUDq z+M0&YhEn^C*9-B4Q7HJ$xj)dORCXPK+)ZtLOa0o&)Sl+f(Y{p*68$-#yagW5^HQnQ z0pWpoQpxg8<&gx9im(>=x6v#&RbQ7^AsjxeSDA? zi4MEJUC~ByG!PiBjq7$pK&FA^5 z=Y@dtQnuy%IfsaR`TVP0q^3mixl&J-3!$H!ua#{A>0Z1JdLq#d4UV9nlYm641ZHl zH6mK~iI6lR3OUEVL}Z5{ONZ_6{Nk%Bv03ag<1HVN?R%w2^aR5@E>6(r>}IoMl$wRF zWr-DItN*k7T$NTT8B)+23c?171sADhjInb2Xb>GhFYGC&3{b>huvLlaS4O z^{j5q+b5H?Z)yuy%AByaVl2yj9cnalY1sMQ zXI#e%*CLajxGxP!K6xf9RD2pMHOfAa1d^Lr6kE`IBpxOiGXfNcoQ*FI6wsNtLD!T+ zC4r2q>5qz0f}UY^RY#1^0*FPO*Zp-U1h9U|qWjwqJaDB(pZ`<`U-xo7+JB$zvwV}^ z2>$0&Q5k#l|Er7*PPG1ycj4BGz zg&`d*?nUi1Q!OB>{V@T$A;)8@h;*Rb1{xk_8X<34L`s}xkH-rQZvjM`jI=jaJRGRg zeEcjYChf-78|RLrao%4HyZBfnAx5KaE~@Sx+o-2MLJ>j-6uDb!U`odj*=)0k)K75l zo^)8-iz{_k7-_qy{Ko~N#B`n@o#A22YbKiA>0f3k=p-B~XX=`Ug>jl$e7>I=hph0&AK z?ya;(NaKY_!od=tFUcGU5Kwt!c9EPUQLi;JDCT*{90O@Wc>b| zI;&GIY$JlQW^9?R$-OEUG|3sp+hn+TL(YK?S@ZW<4PQa}=IcUAn_wW3d!r#$B}n08 z*&lf(YN21NDJ74DqwV`l`RX(4zJ<(E4D}N0@QaE-hnfdPDku~@yhb^AeZL73RgovX z6=e>!`&e^l@1WA5h!}}PwwL*Gjg!LbC5g0|qb8H$^S{eGs%cc?4vTyVFW=s6KtfW? z@&Xm+E(uz(qDbwDvRQI9DdB<2sW}FYK9sg*f%-i*>*n{t-_wXvg~N7gM|a91B!x|K zyLbJ~6!!JZpZ`#HpCB8g#Q*~VU47Rp$NyZb3WhEgg3ivSwnjGJgi0BEV?!H}Z@QF| zrO`Kx*52;FR#J-V-;`oR-pr!t>bYf)UYcixN=(FUR6$fhN@~i09^3WeP3*)D*`*mJ z1u%klAbzQ=P4s%|FnVTZv%|@(HDB+ap5S#cFSJUSGkyI*Y>9Lwx|0lTs%uhoCW(f1 zi+|a9;vDPfh3nS<7m~wqTM6+pEm(&z-Ll;lFH!w#(Uk#2>Iv~2Hu}lITn7hnOny`~ z*Vj=r<&Nwpq^@g5m`u&QTBRoK*}plAuHg$L$~NO#wF0!*r0OfcS%)k0A??uY*@B^C zJe9WdU(w){rTIf<;rwJt^_35^d<A@$FqEZW6kwyfAo2x0T$Ye2MZox6Z7<%Qbu$}}u{rtE+h2M+Z}T4I zxF1cwJ(Uvp!T#mogWkhb(?SxD4_#tV(Sc8N4Gu*{Fh#})Pvb^ef%jrlnG*&Ie+J5 zsly5oo?1((um&lLDxn(DkYtk`My>lgKTp3Y4?hTQ4_`YNOFtjF-FUY#d#(EQd(rfz zB8z%Vi;?x)ZM$3c>yc5H8KBvSevnWNdCbAj?QCac)6-K~Xz@EZp}~N9q)5*Ufjz3C z6kkOeI{3H(^VO8hKDrVjy2DXd;5wr4nb`19yJi0DO@607MSx+7F$ zz3F7sl8JV@@sM$6`#JmSilqI%Bs)}Py2eFT;TjcG5?8$zwV60b(_5A>b#uk~7U^bO z>y|6SCrP2IGST(8HFuX|XQUXPLt2gL_hm|uj1Ws`O2VW>SyL^uXkl>Zvkcpi?@!F7 z%svLoT@{R#XrIh^*dE~$YhMwC+b7JE09NAS47kT%Ew zD!XjxA@1+KOAyu`H2z#h+pGm!lG>WI0v745l+Fd><3dh{ATq%h?JSdEt zu%J*zfFUx%Tx&0DS5WSbE)vwZSoAGT=;W#(DoiL($BcK;U*w`xA&kheyMLI673HCb7fGkp{_vdV2uo;vSoAH z9BuLM#Vzwt#rJH>58=KXa#O;*)_N{$>l7`umacQ0g$pI3iW4=L--O;Wiq0zy7OKp`j2r^y3`7X!?sq9rr5B{41BkBr1fEd1#Q3 z-dXc2RSb4U>FvpVhlQCIzQ-hs=8420z=7F2F(^xD;^RXgpjlh8S6*xCP#Gj2+Q0bAg?XARw3dnlQ*Lz3vk}m`HXmCgN=?bIL{T zi}Ds-xn|P)dxhraT@XY$ZQ&^%x8y!o+?n#+>+dZ1c{hYwNTNRke@3enT(a@}V*X{! z81+{Jc2UR;+Zcbc6cUlafh4DFKwp>;M}8SGD+YnW3Q_)*9Z_pny_z+MeYQmz?r%EVaN0d!NE*FVPq&U@vo{ef6wkMIDEWLbDs zz91$($XbGnQ?4WHjB~4xgPgKZts{p|g1B{-4##}#c5aL5C6_RJ_(*5>85B1}U!_<``}q-97Q7~u)(&lsb(WT^(*n7H%33%@_b zO5(?-v??s??33b19xiB7t_YT!q8!qAzN1#RD@3;kYAli%kazt#YN7}MhVu=ljuz27 z1`<+g8oVwy57&$`CiHeaM)tz(OSt4E# zJ@P6E*e504oUw~RD(=9WP8QdW^6wRdFbKII!GAWecJ(?{`EzTR@?j!3g?$@LLCt;U={>!9z7DU!(1Jq zqEwdx5q?W1Ncm7mXP8MFwAr?nw5$H%cb>Q><9j{Tk2RY9ngGvaJgWXx^r!ywk{ph- zs2PFto4@IIwBh{oXe;yMZJYlS?3%a-CJ#js90hoh5W5d^OMwCFmpryHFr|mG+*ZP$ zqyS5BW@s}|3xUO0PR<^{a2M(gkP5BDGxvkWkPudSV*TMRK5Qm4?~VuqVAOerffRt$HGAvp;M++Iq$E6alB z;ykBr-eZ6v_H^1Wip56Czj&=`mb^TsX|FPN#-gnlP03AkiJDM=?y|LzER1M93R4sC z*HT(;EV=*F*>!+Z{r!KG?6ODMGvkt3viG=@kQJHNMYd}bS4KrrHf4`&*(0m0R5Hqz zEk)r=sFeS?MZRvn<@Z0&bDw)XkMnw+_xqgp=W{;ioX`6;G-P9N%wfoYJ$-m$L#MC% z^sH?tSzA|WWP(cN3({~_*X$l{M*;1V{l$;T6b){#l4pswDTid26HaXgKed}13YIP= zJRvA3nmx{}R$Lr&S4!kWU3`~dxM}>VXWu6Xd(VP}z1->h&f%82eXD_TuTs@=c;l0T z|LHmWKJ+?7hkY=YM>t}zvb4|lV;!ARMtWFp!E^J=Asu9w&kVF*i{T#}sY++-qnVh! z5TQ|=>)+vutf{&qB+LO9^jm#rD7E5+tcorr^Fn5Xb0B;)f^$7Ev#}G_`r==ea294V z--v4LwjswWlSq9ba6i?IXr8M_VEGQ$H%hCqJTFQ3+1B9tmxDUhnNU%dy4+zbqYJ|o z3!N{b?A@{;cG2~nb-`|z;gEDL5ffF@oc3`R{fGi)0wtMqEkw4tRX3t;LVS3-zAmg^ zgL7Z{hmdPSz9oA@t>tZ1<|Khn&Lp=_!Q=@a?k+t~H&3jN?dr(}7s;{L+jiKY57?WsFBfW^mu6a03_^VKrdK=9egXw@!nzZ3TbYc*osyQNoCXPYoFS<&Nr97MrQCOK(gO8 z;0@iqRTJy4-RH)PJld5`AJN}n?5r^-enKrHQOR;z>UMfm+e8~4ZL5k>oXMiYq12Bx4eVQv0jFgp_zC#``sjZpywYqISMP}VZ@!~1Mf$!x|opj%mQ98JnSk@`~ zPmmyuPZKtZOnEC!1y!?`TYRsZ!II;d!iln}%e}bk5qIiUADERr*K$3dekgHV9TtBX zi5q!J!6Zgd#cLxRmZN^J`o@Zv{+p+<_#8^nvY)44Hw_2i@?R&5n^q33fpOnDg1nPQ z_r<$hURl~OketX|Tdbvf_7=3x^rSFJtEp@tuDpVB&uq)qW;xUQ7mmkr-@eZwa$l+? zoKk``Vz@TH#>jMce*8>@FZ+@BEUdYa_K0i|{*;j9MW3K%pnM*T;@>|o@lMhgLrpZP5aol(z>g;b4}|e$U~Fn zGL%(}p%Jsl4LxE!VW_Y4T>e}W4e#~F03H_^R!Q)kpJG{lO!@I4{mFo^V#ayHh_5~o zB$O71gcE(G@6xv);#Ky?e(Ed}^O+Ho(t=93T9T3TnEY(OVf_dR-gY@jj+iJSY?q|6prBv(S9A4k=2fNZz!W@S=B@~b?TJRTuBQq448@juN#Y=3q=^VCF>Z}n6wICJ<^^Kn8C;mK zZYiFSN#Z$?NDGV7(#}q2tAZAtE63icK-MY>UQu4MWlGIbJ$AF8Zt-jV;@7P5MPI>% zPWvO!t%1+s>-A%`;0^o8Ezeaa4DMwI8ooQrJ;ax@Qt*6XONWw)dPwOPI9@u*EG&844*1~EoZ2qsAe~M>d`;Bc_CWY zMoDKEmDh-}k9d6*<0g@aQmsnrM1H9IcKYZs)><)d92{|0Hh8?~XbF)7U+UmP@Pw_6geVB?7N$4J4*E0z3EO&5kRS(EE zv92(+e5WxLXMN{h;-|8@!Q#0q247hb^3R%*k3MuMO5*L}$0D#5P*N$aHd54C+=_RToYXTyewugOaDmGsCvb4H1s=@gkfVnzTCWKMa-Mm1v4Wq!t-JIrbV&EWwKDe ze#kJpOq#iRlFz%5#6Fio9IUlKnQ#X&DY8Ux#<-WqxAac-y%U_L+EZZ4Rg5*yNg`f< zSZn&uio@zanUCPqX1l4W&B!;UWs#P7B^|4WwoCxQXl|44n^cBNqu=3Vl*ltAqsUQO z9q_@nD0zq0O8r`coEm>9+|rA3HL#l}X;0##>SJS$cVavOZVCpSGf4mUU1( zWaRCUYc^9QbG9=vpWo%xP}CMFnMb{reA`K7tT(t5DM)d9l}jVPY>qoRzT zE3m-p#=i=$9x*CB`AL>SY}u3agYFl#uULNen#&44H;!L@I{RI=PlWxG8J((f)ma7A z@jLvQ>?Nx`n?3ChRG#HqE3MXP8*o3!Qq`+t8EMt_p)oeKHqPusBxPn!#?R??-=e3e zo73WNs_IZF`WLigre=|`aS2^> zN1zn!7k&Dh28t%VpJ%**&E!eAcB5oLjQFFcJQj*URMia%Ya3@q1UQ18=oWMM6`I}iT_&L1gl?*~6nU4q4Z0`H<5yDp(HeZ+RGf9`mM&= zn-qRp%i!g$R;i1d1aMZ{IewNjE@p2+Z{`x{*xL*x$?WV~{BjJpsP&C&JK0HLoyf z`0z^v&fBQSa!I7FU~9MaQ%e|?RP>sM^2PL!mE^Q1Ig_4M$5BRfi72oMYu6Ke?wmDX z@0a%-V|z}b23K=ye(W+fG#w|jJUnT{=KR5jfuq!RX}<1irTDw(${<&}dWQu4;EuE< z@3u4dBkQaCHHM&;cE0z50_V!(vJ1_V)A8?C#eJuLkt!98Z%|Bgzidc0j|z(&o)TCzYlrgZA zC3@i>L!&Gw_~7`>puB97I2lK)lESZQqVXc_8T^G2O#VHhO?IC$g zOYhXJ7)~C<8l|Xrftka@QuowScM{K&0zskoU$Aw~vIRVRF9TEQ4*3=_5)98B`=t8(N%ZuWqmwlW zllAzq=E5_5!sKDXam@w`ZD(nl%LAPxQuEtDcKPqu9LPJvNIITawU#c^PQ2HmZgs)r zH^+gRwZ?0)8IFQgU)+p@0Iqb^tcEoqcB@zhfz_FaOM&_d<|jnU>q5nSKa<@%9|dje zIupcg1!tRiMP4X=oG<7s4|AW&^-Cw4FL9OuI$t zxjc*y;Uw!G7a|jz>E*2+PlR(CemWebS7m-&*CDwnmxbiRqJvQ&os-sC&4OWt^(2@vG4|jui#Df@-D= zh3D%8Y3R6+jRBStSvH9pt&tCI`NK08J1*pC(?OM0h!bS-JK3I}`pDY-fDIaB_*W6KS+TO0Q*%kkeuN6uWITt=TsCGw6uBE710q; zRluI%j{?@jwhM|l5&TB!-TkQs!A=DXRE>u18t@;zndD0M$U@Igrt?UW2; z7%=dsHIVH_LCkGUU0fW&UMjDnvjcc0Mp(mK&;d~ZJ5EJ)#7@aTZvGDFXzFZg2Lq~s z5PR_LazNN)JD5K_uK*Hy{mXuHTkGGv|9V8KP#iQ$3!G*^>7UiE{|1G1A-qg(xH;Xa>&%f|BZkH zG=J^0pHzSAqv5*5ysQ{Puy^-_|IPrii zKS$mE10Zngf>Sgg@BjpRyJbrHeo zD8Ro0LI*W#+9?^xlOS^c>Z^^n^0I|FH^@^`ZR`{H=$ zjO0_$cnpBM7Zcm?H_RXIu-Lu~qweDSV|tEZBZh!e6hQy->}e;d#osZ1hQj{HhHkC0 zJ|F-HKmeTGgDe979ogBz24;@<|I7;TU!IXb@oWMsMECIETmQy`zPtM`|NP}PjzR_u zKMG1Z{%1kWeMfEf(10U#w!clmQ2)JC8zm(Fv!H4dUHQHCFLikID?hrd{0>kCQt?kP zdqn2ZG0}ytcQJ7t_B3s0ZvH3PYjkjQ`Q%;jV@?MK-+z3etBCGGo4f4`y^|AdCs!DH zThTQ;cL5dM{|tB_1y6K3bVa^hx_<9J(}5`2SDz1^0bT!Vm*JV;9~t&{IC{$DUAVV* z{|E=#yN{wNdTY@$6z{_KNA3&%w|vFu1n9XRcM0Ak>`UW!lQ`ah3D4r%}Z literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..bdc9a83b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..79a61d42 --- /dev/null +++ b/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..93e3f59f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..0f02415c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html + */ + +rootProject.name = "trees-2" +include("app", "binaryTree") From d566ca3d8fa0a127e20d36bd4187bcabc7ea7a17 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 17:09:30 +0300 Subject: [PATCH 003/296] struct: Add contributing advices --- CONTRIBUTING.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1e72371a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Внесение правок + +## Основные советы + +1. Никогда не используйте merge, только rebase для сохранения линейной истории коммитов +2. **Осторожно** с изменениями в чужих ветках. Придется больно и мучительно делать rebase. Лучше не трогайте чужие + ветки +3. Перепроверьте историю коммитов перед созданием пулл реквеста +4. Каждый коммит должен быть осознанным и быть одним логическим элементом +5. Каждый пулл реквест должен быть осознанным и быть одним логическим элементом +6. **Перепроверьте, что вы в правильной ветке**, никогда не коммитьте напрямую в main + +## Правила добавления коммитов + +Коммиты добавляются в соответствии с conventional commits. Т.е +`(): `. + +Поле `` должно принимать одно из этих значений: + +* `feat` для добавления новой функциональности +* `fix` для исправления бага в программе +* `refactor` для рефакторинга кода, например, переименования переменной +* `test` для добавления тестов, их рефакторинга +* `struct` для изменений связанных с изменением структуры проекта (НО НЕ КОДА), например изменение + расположения папок +* `ci` для различных задач ci/cd + +Поле `` опционально и показывает к какому модулю, классу, методу функции и т.п применены изменения. + +Поле `` содержит суть изменений в повелительном наклонении настоящего времени на английском языке без точки в +конце, первое слово - глагол с большой буквы. Текст сообщения должен включать мотивацию к изменению и контрасты с +предыдущим поведением. + +Примеры: + +* Хорошо: "Add module for future BST implementations" +* Плохо: "Added module for future BST implementations." +* Плохо: "Adds module". +* Очень плохо: "new bug." + +## Правила работы с ветками + +1. Из ветки `develop` создается ветка `release`. +2. Из ветки `develop` создаются ветки `feature`. +3. Когда работа над веткой `feature` завершается, она сливается в ветку `develop`. +4. Когда работа над веткой `release` завершается, она сливается с ветками `develop` и `main`. +5. Если в ветке `main` обнаруживается проблема, из `main` создается ветка `hotfix`. +6. Когда работа над веткой `hotfix` завершается, она сливается с ветками `develop` и `main`. + +### Правила именования и создания веток `feature` + +Ветка под одно (большое) логическое изменение. Формат для веток `/`. Тип аналогичен тому же в +коммитах, +а `` представляет собой короткое описание назначения ветки в kebab-case стиле. + +Примеры хороших названий: + +* `feat/add-avl-tree` +* `ci/add-klint` + +После одобрения пулл реквеста, ветка удаляется. А новая функциональность разрабатывается в новой ветке. + +### Именование веток `hotfix` + +Формат для веток `hotfix/`. Где `` представляет собой короткое описание назначения ветки в +kebab-case стиле. + +**Ветка `hotfix` создается только из ветки `main`** From 058587642d2f3c620d52db4d4143356d54f51daf Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 17:10:16 +0300 Subject: [PATCH 004/296] struct: Add license file --- LICENSE.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..a4623430 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,16 @@ +MIT License + +Copyright (c) 2023 Akhmedov David, Ermolovich Anna, Efremov Alexey, Yakshigulov Vadim, Dyachkov Vitaliy, Perevalov Efim + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From af0a2f61eca6531e240284a379ef35fa0217e832 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 17:11:16 +0300 Subject: [PATCH 005/296] struct: Add README with 'Contributing' and 'License' headers --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..802656b6 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +Trees-2 +============ + +--- + +## Contributing + +**Quick start**: + +1. Create a branch with new feature from `develop` branch (`git checkout -b feat/my-feature develop`) +2. Commit the changes (`git commit -m "feat: Add some awesome feature"`) +3. Push the branch to origin (`git push origin feat/add-amazing-feature`) +4. Open the pool request + +For more details, see [CONTRIBUTING.md](CONTRIBUTING.md) + +--- + +## License + +> You can check out the full license [here](LICENSE.md) + +This project is licensed under the terms of the **MIT** license. From 6beae94e867569146e90c393bce9d8b78e29ca17 Mon Sep 17 00:00:00 2001 From: Efremov Alexey <66139162+osogi@users.noreply.github.com> Date: Sun, 26 Mar 2023 22:27:00 +0300 Subject: [PATCH 006/296] struct: Fix typo in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 802656b6..6ca779f9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Trees-2 1. Create a branch with new feature from `develop` branch (`git checkout -b feat/my-feature develop`) 2. Commit the changes (`git commit -m "feat: Add some awesome feature"`) 3. Push the branch to origin (`git push origin feat/add-amazing-feature`) -4. Open the pool request +4. Open the pull request For more details, see [CONTRIBUTING.md](CONTRIBUTING.md) From 0c65ce86ced97fd969fd0dcd7b0cedbbbbe78d29 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 18:29:26 +0300 Subject: [PATCH 007/296] feat: add TemplateBSTree class --- .../src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt new file mode 100644 index 00000000..04b65bc9 --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -0,0 +1,5 @@ +package org.tree.binaryTree + +abstract class TemplateBSTree, NODE_T : TemplateNode> { + var root: NODE_T? = null +} From 2cae312e76a2b78dc4b3487c761796e5511fb4a6 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 18:37:59 +0300 Subject: [PATCH 008/296] feat(TemplateBSTree): add insert() method --- .../org/tree/binaryTree/TemplateBSTree.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 04b65bc9..3afd8eb4 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -2,4 +2,40 @@ package org.tree.binaryTree abstract class TemplateBSTree, NODE_T : TemplateNode> { var root: NODE_T? = null + + // Insert + protected open fun insertNode(curNode: NODE_T?, newNode: NODE_T): NODE_T? { + if (curNode == null) { + if (root === curNode) { + root = newNode + return newNode + } else { + throw IllegalArgumentException("Received a non-root null node") + } + } else { + if (newNode.elem < curNode.elem) { + if (curNode.left == null) { + curNode.left = newNode + } else { + insertNode(curNode.left, newNode) + } + return curNode.left + } else if (newNode.elem > curNode.elem) { + if (curNode.right == null) { + curNode.right = newNode + } else { + insertNode(curNode.right, newNode) + } + return curNode.right + } else { + return null // STTK: 10% + } + } + } + + protected abstract fun insert(curNode: NODE_T?, obj: T): NODE_T? + + fun insert(obj: T) { + insert(root, obj) + } } From 1a557f259b2f396513b6a64b141c892eec6381cf Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 18:52:57 +0300 Subject: [PATCH 009/296] feat(TemplateBSTree): add find() method --- .../org/tree/binaryTree/TemplateBSTree.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 3afd8eb4..3e911843 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -38,4 +38,23 @@ abstract class TemplateBSTree, NODE_T : TemplateNode Date: Sun, 26 Mar 2023 20:14:37 +0300 Subject: [PATCH 010/296] feat(TemplateNode): add getNonNullChild() method --- .../src/main/kotlin/org/tree/binaryTree/Nodes.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index 23d8750e..c116bb57 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -4,6 +4,17 @@ abstract class TemplateNode, NODE_T : TemplateNode> var elem: T = v var left: NODE_T? = null var right: NODE_T? = null + + fun getNonNullChild(): NODE_T? { + val res = if (left != null) { + left + } else if (right != null) { + right + } else { + null + } + return res + } } class Node>(v: T) : TemplateNode>(v) From 7006bce064fc71e2e6e2fdfce8ca7d3308a0a52e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 21:37:15 +0300 Subject: [PATCH 011/296] feat(TemplateNode): add countNullChild() method --- .../src/main/kotlin/org/tree/binaryTree/Nodes.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index c116bb57..89b3003f 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -15,6 +15,17 @@ abstract class TemplateNode, NODE_T : TemplateNode> } return res } + + fun countNullChild(): Int { + var res = 0 + if (left == null) { + res += 1 + } + if (right == null) { + res += 1 + } + return res + } } class Node>(v: T) : TemplateNode>(v) From 80437851e6e745bb880d5b9a6b88dfe8344f246b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 21:59:50 +0300 Subject: [PATCH 012/296] feat(TemplateBSTree): add findNext() method --- .../kotlin/org/tree/binaryTree/TemplateBSTree.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 3e911843..23a230f3 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -57,4 +57,19 @@ abstract class TemplateBSTree, NODE_T : TemplateNode Date: Sun, 26 Mar 2023 22:16:50 +0300 Subject: [PATCH 013/296] feat(TemplateBSTree): add remove() method and related methods --- .../org/tree/binaryTree/TemplateBSTree.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 23a230f3..5798a009 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -58,6 +58,65 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { + val nxt = + findNext(curNode) ?: throw IllegalArgumentException("Got null as next than right child isn't null") + val buf = nxt.elem + remove(nxt.elem) + curNode.elem = buf + } + + 1 -> { + replaceDeletedNode(curNode, parentNode, curNode.getNonNullChild()) + } + + else -> { + replaceDeletedNode(curNode, parentNode, null) + } + } + return res + } + + protected open fun remove(curNode: NODE_T?, parentNode: NODE_T?, obj: T): Int? { + if (curNode == null) { + return null + } + + if (curNode.elem == obj) { + return deleteNode(curNode, parentNode) + } else if (obj < curNode.elem) { + return remove(curNode.left, curNode, obj) + } else { + return remove(curNode.right, curNode, obj) + } + } + + fun remove(obj: T) { + remove(root, null, obj) + } + //Additional protected fun findNext(curNode: NODE_T): NODE_T? { var res = curNode.right From ca810ad43ed497fd84b71057326194d7ff15729e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 26 Mar 2023 22:43:42 +0300 Subject: [PATCH 014/296] feat: add new class BinSearchTree --- .../src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt new file mode 100644 index 00000000..44427ad7 --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt @@ -0,0 +1,7 @@ +package org.tree.binaryTree + +class BinSearchTree> : TemplateBSTree>() { + override fun insert(curNode: Node?, obj: T): Node? { + return insertNode(curNode, Node(obj)) + } +} From 06411dc7d80a08f41483117a979b835bf5b8cbd7 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 27 Mar 2023 10:10:42 +0300 Subject: [PATCH 015/296] feat: Init TemplateBalanceBSTree class --- .../kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt new file mode 100644 index 00000000..f54767d4 --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -0,0 +1,6 @@ +package org.tree.binaryTree + + +abstract class TemplateBalanceBSTree, NODE_T : TemplateNode> : + TemplateBSTree() { +} From 66af9259905a43bba20e586cbd76e2cd9791ffb9 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 27 Mar 2023 10:17:59 +0300 Subject: [PATCH 016/296] feat(BalanceTree): Add balance() method --- .../tree/binaryTree/TemplateBalanceBSTree.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index f54767d4..20f7b456 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -3,4 +3,22 @@ package org.tree.binaryTree abstract class TemplateBalanceBSTree, NODE_T : TemplateNode> : TemplateBSTree() { + + //Balance + protected class BalanceCase { + enum class Recursive { RECURSIVE_CALL, END } + enum class OpType { INSERT, REMOVE_0, REMOVE_1, REMOVE_2 } + } + + protected fun getBalanceRemoveType(a: Int): BalanceCase.OpType { + return when (a) { + 2 -> BalanceCase.OpType.REMOVE_2 + 1 -> BalanceCase.OpType.REMOVE_1 + else -> BalanceCase.OpType.REMOVE_0 + } + } + + protected abstract fun balance(curNode: NODE_T, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) + + } From 00c99fe084a48c76690fd31b09c566abededc255 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 27 Mar 2023 10:23:42 +0300 Subject: [PATCH 017/296] feat(BalanceTree): Override insertNode() method --- .../org/tree/binaryTree/TemplateBalanceBSTree.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 20f7b456..b98ad9a4 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -20,5 +20,15 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Mon, 27 Mar 2023 10:24:51 +0300 Subject: [PATCH 018/296] feat(BalanceTree): Override remove() method --- .../tree/binaryTree/TemplateBalanceBSTree.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index b98ad9a4..738d5054 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -31,4 +31,33 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Tue, 28 Mar 2023 00:47:23 +0300 Subject: [PATCH 019/296] refactor(BalanceTree): Add some comments to code --- .../kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 738d5054..df445408 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -6,7 +6,12 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode Date: Tue, 28 Mar 2023 11:36:48 +0300 Subject: [PATCH 020/296] refactor(TemplateBSTree): Change visibility and name of replaceNode() method --- .../org/tree/binaryTree/TemplateBSTree.kt | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 5798a009..72c23221 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -59,24 +59,6 @@ abstract class TemplateBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode { - replaceDeletedNode(curNode, parentNode, curNode.getNonNullChild()) + replaceNode(curNode, parentNode, curNode.getNonNullChild()) } else -> { - replaceDeletedNode(curNode, parentNode, null) + replaceNode(curNode, parentNode, null) } } return res @@ -131,4 +113,22 @@ abstract class TemplateBSTree, NODE_T : TemplateNode Date: Tue, 28 Mar 2023 20:15:03 +0300 Subject: [PATCH 021/296] fix(TemplateBSTree): Change return value of insertNode() --- .../main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 72c23221..448cdace 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -8,7 +8,7 @@ abstract class TemplateBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode curNode.elem) { if (curNode.right == null) { curNode.right = newNode + return curNode } else { - insertNode(curNode.right, newNode) + return insertNode(curNode.right, newNode) } - return curNode.right } else { return null // STTK: 10% } From 3c4f45b2ae88b30760cc33abf1123397f5182faa Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 28 Mar 2023 20:23:15 +0300 Subject: [PATCH 022/296] fix(BalanceTree): Fix insertNode() due to changes --- .../main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index df445408..c6daeb9c 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -29,7 +29,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Tue, 28 Mar 2023 22:02:45 +0300 Subject: [PATCH 023/296] feat(BalanceTree): Add rotates methods --- .../tree/binaryTree/TemplateBalanceBSTree.kt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index c6daeb9c..f20133fb 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -24,7 +24,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode Date: Tue, 28 Mar 2023 22:40:37 +0300 Subject: [PATCH 024/296] fix(BalanceTree): Fix typo --- .../main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index f20133fb..65576cee 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -24,7 +24,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Thu, 30 Mar 2023 15:58:24 +0300 Subject: [PATCH 025/296] refactor(BalanceTree): Refactor insertNode() --- .../kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 65576cee..9d10d705 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -27,15 +27,15 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Thu, 30 Mar 2023 16:00:12 +0300 Subject: [PATCH 026/296] refactor(countNullChild): Fix typo --- binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt | 2 +- .../src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index 89b3003f..921263dc 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -16,7 +16,7 @@ abstract class TemplateNode, NODE_T : TemplateNode> return res } - fun countNullChild(): Int { + fun countNullChildren(): Int { var res = 0 if (left == null) { res += 1 diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 448cdace..ceee7bab 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -60,7 +60,7 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { val nxt = From 4c282b05c57323943c7c881732c07005493f8ecd Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 30 Mar 2023 16:02:18 +0300 Subject: [PATCH 027/296] refactor(replaceNode): Fix typo --- .../src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index ceee7bab..19546c85 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -122,9 +122,9 @@ abstract class TemplateBSTree, NODE_T : TemplateNode Date: Thu, 30 Mar 2023 21:07:23 +0300 Subject: [PATCH 028/296] feat(TemplateNode): Add traversal() method for nodes --- .../main/kotlin/org/tree/binaryTree/Nodes.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index 921263dc..ebc7b15f 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -26,6 +26,33 @@ abstract class TemplateNode, NODE_T : TemplateNode> } return res } + + //Traversal + enum class Traversal { + INORDER, + PREORDER, + POSTORDER + } + + private fun traversal(res: MutableList, cs: Traversal) { + if (cs == Traversal.PREORDER) { + res.add(elem) + } + left?.traversal(res, cs) + if (cs == Traversal.INORDER) { + res.add(elem) + } + right?.traversal(res, cs) + if (cs == Traversal.POSTORDER) { + res.add(elem) + } + } + + fun traversal(order: Traversal): MutableList { + val res: MutableList = mutableListOf() + traversal(res, order) + return res + } } class Node>(v: T) : TemplateNode>(v) From 5baf4210244f3e3b2c195004ff7d538e1f8e04b9 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 30 Mar 2023 21:08:19 +0300 Subject: [PATCH 029/296] feat(TemplateBSTree): Add traversal() method for trees --- .../src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index 19546c85..e5382730 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -131,4 +131,8 @@ abstract class TemplateBSTree, NODE_T : TemplateNode? { + return root?.traversal(order) + } } From 58d29c871cf7066ee42a3b86947f9f6162ea3eba Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 30 Mar 2023 13:34:46 +0300 Subject: [PATCH 030/296] fix(BalanceTree): Add balance root case --- .../org/tree/binaryTree/TemplateBalanceBSTree.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 9d10d705..5e648be6 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -24,7 +24,12 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode changed node = root + protected abstract fun balance( + curNode: NODE_T?, + operationType: BalanceCase.OpType, + recursive: BalanceCase.Recursive + ) override fun insertNode(curNode: NODE_T?, newNode: NODE_T): NODE_T? { val parNode = super.insertNode(curNode, newNode) @@ -33,6 +38,9 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode Date: Fri, 31 Mar 2023 21:56:53 +0300 Subject: [PATCH 031/296] refactor(AVLNode): Change start value of height --- binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index ebc7b15f..61e27b26 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -65,5 +65,5 @@ class RBNode>(p: RBNode?, v: T) : TemplateNode } class AVLNode>(v: T) : TemplateNode>(v) { - var height: Int = 0 + var height: Int = 1 } From 6f8487f233483b59fb7db959c857de7d468f62c3 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 31 Mar 2023 22:00:42 +0300 Subject: [PATCH 032/296] refactor(TemplateBalanceBSTree): Made rotation methods open --- .../main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 5e648be6..743b57f7 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -74,7 +74,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode Date: Fri, 31 Mar 2023 22:07:22 +0300 Subject: [PATCH 033/296] feat: Add AVLTree class --- .../src/main/kotlin/org/tree/binaryTree/AVLTree.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt new file mode 100644 index 00000000..fdb4c40d --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -0,0 +1,11 @@ +package org.tree.binaryTree + +class AVLTree> : TemplateBalanceBSTree>() { + override fun insert(curNode: AVLNode?, obj: T): AVLNode? { + TODO("Not yet implemented") + } + + override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { + TODO("Not yet implemented") + } +} \ No newline at end of file From 4b5305c4aa13a7a4be2bb62f73701c237f494b63 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 31 Mar 2023 22:08:46 +0300 Subject: [PATCH 034/296] feat(AVLTree): Add insert() method --- binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index fdb4c40d..455fa8af 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -2,7 +2,7 @@ package org.tree.binaryTree class AVLTree> : TemplateBalanceBSTree>() { override fun insert(curNode: AVLNode?, obj: T): AVLNode? { - TODO("Not yet implemented") + return super.insertNode(curNode, AVLNode(obj)) } override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { From 5295214e284c3a6d4acdc6bd4467346cd1d5b1f7 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 31 Mar 2023 22:16:23 +0300 Subject: [PATCH 035/296] feat(AVLTree): Add height() method and related methods --- .../kotlin/org/tree/binaryTree/AVLTree.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index 455fa8af..ec84a622 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -1,6 +1,26 @@ package org.tree.binaryTree class AVLTree> : TemplateBalanceBSTree>() { + private fun height(avlNode: AVLNode?): Int { + return avlNode?.height ?: 0 + } + + private fun bfactor(avlNode: AVLNode?): Int { + return if (avlNode != null) { + height(avlNode.right)-height(avlNode.left) + } else { + 0 + } + } + + private fun fixheight(avlNode: AVLNode?) { + if (avlNode != null) { + val hl = height(avlNode.left) + val hr = height(avlNode.right) + avlNode.height = (if (hl > hr) hl else hr) + 1 + } + } + override fun insert(curNode: AVLNode?, obj: T): AVLNode? { return super.insertNode(curNode, AVLNode(obj)) } From 2dd141378461f04ad188502233708beeea832733 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 31 Mar 2023 22:23:18 +0300 Subject: [PATCH 036/296] refactor:(AVLTree): Refactor rotates methods --- .../src/main/kotlin/org/tree/binaryTree/AVLTree.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index ec84a622..fd33afd5 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -21,6 +21,20 @@ class AVLTree> : TemplateBalanceBSTree>() { } } + override fun rotateRight(curNode: AVLNode, parentNode: AVLNode?) { + val replacementNode = curNode.left + super.rotateRight(curNode, parentNode) + fixheight(curNode) + fixheight(replacementNode) + } + + override fun rotateLeft(curNode: AVLNode, parentNode: AVLNode?) { + val replacementNode = curNode.right + super.rotateLeft(curNode, parentNode) + fixheight(replacementNode) + fixheight(curNode) + } + override fun insert(curNode: AVLNode?, obj: T): AVLNode? { return super.insertNode(curNode, AVLNode(obj)) } From 0d33cbd6ae8a00b18d3f37444933bdc5104a0a92 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 31 Mar 2023 22:32:47 +0300 Subject: [PATCH 037/296] feat(AVLTree): Add balance() method and related method balanceNode() --- .../kotlin/org/tree/binaryTree/AVLTree.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index fd33afd5..901a340c 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -39,7 +39,34 @@ class AVLTree> : TemplateBalanceBSTree>() { return super.insertNode(curNode, AVLNode(obj)) } + private fun balanceNode(curNode: AVLNode, parentNode: AVLNode?) { + fixheight(curNode) + if (bfactor(curNode) == 2) { + if (bfactor(curNode.right) < 0) { + curNode.right?.let { rotateRight(it, curNode) } + } + rotateLeft(curNode, parentNode) + } + + if (bfactor(curNode) == -2) { + if (bfactor(curNode.left) > 0) { + curNode.left?.let { rotateLeft(it, curNode) } + } + rotateRight(curNode, parentNode) + } + } + override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { - TODO("Not yet implemented") + when (operationType) { + BalanceCase.OpType.REMOVE_0 -> {} + else -> { + curNode?.right?.let { balanceNode(it, curNode) } + curNode?.left?.let { balanceNode(it, curNode) } + } + } + + if (curNode === null) { + root?.let { balanceNode(it, curNode) } + } } } \ No newline at end of file From e6bd22eafb504f3a144f027ad24ef9384ad67032 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 1 Apr 2023 00:41:45 +0300 Subject: [PATCH 038/296] refactor:(AVLTree): Refactor fixheight() and balance() methods --- .../main/kotlin/org/tree/binaryTree/AVLTree.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index 901a340c..4cd84956 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -1,5 +1,7 @@ package org.tree.binaryTree +import kotlin.math.max + class AVLTree> : TemplateBalanceBSTree>() { private fun height(avlNode: AVLNode?): Int { return avlNode?.height ?: 0 @@ -17,7 +19,7 @@ class AVLTree> : TemplateBalanceBSTree>() { if (avlNode != null) { val hl = height(avlNode.left) val hr = height(avlNode.right) - avlNode.height = (if (hl > hr) hl else hr) + 1 + avlNode.height = max(hl, hr) + 1 } } @@ -57,16 +59,12 @@ class AVLTree> : TemplateBalanceBSTree>() { } override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { - when (operationType) { - BalanceCase.OpType.REMOVE_0 -> {} - else -> { - curNode?.right?.let { balanceNode(it, curNode) } - curNode?.left?.let { balanceNode(it, curNode) } - } - } - if (curNode === null) { root?.let { balanceNode(it, curNode) } + return } + + curNode.right?.let { balanceNode(it, curNode) } + curNode.right?.let { balanceNode(it, curNode) } } -} \ No newline at end of file +} From 9bebaab9af602bf504dd6aead9219ccf8c206263 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 1 Apr 2023 00:46:54 +0300 Subject: [PATCH 039/296] fix:(AVLTree): Fix in balance() method --- binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index 4cd84956..b47d605a 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -59,7 +59,7 @@ class AVLTree> : TemplateBalanceBSTree>() { } override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { - if (curNode === null) { + if (curNode == null) { root?.let { balanceNode(it, curNode) } return } From 56380303eb5813d2e56863469bfcb14bd65f4804 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 1 Apr 2023 13:03:38 +0300 Subject: [PATCH 040/296] fix:(AVLTree): Fix balance() method --- .../main/kotlin/org/tree/binaryTree/AVLTree.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index b47d605a..02599889 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -59,12 +59,17 @@ class AVLTree> : TemplateBalanceBSTree>() { } override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { - if (curNode == null) { - root?.let { balanceNode(it, curNode) } - return - } + when (operationType) { + BalanceCase.OpType.REMOVE_0 -> {} + else -> { + if (curNode == null) { + root?.let { balanceNode(it, curNode) } + return + } - curNode.right?.let { balanceNode(it, curNode) } - curNode.right?.let { balanceNode(it, curNode) } + curNode.right?.let { balanceNode(it, curNode) } + curNode.right?.let { balanceNode(it, curNode) } + } + } } } From 783febf50d27cd959d5a05b4cd4cc4aefaac609f Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 1 Apr 2023 23:40:51 +0300 Subject: [PATCH 041/296] fix:(AVLTree): Fix typo --- binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index 02599889..e07c9242 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -68,7 +68,7 @@ class AVLTree> : TemplateBalanceBSTree>() { } curNode.right?.let { balanceNode(it, curNode) } - curNode.right?.let { balanceNode(it, curNode) } + curNode.left?.let { balanceNode(it, curNode) } } } } From 8c6a85a6abe17fa77d2767e648f01510a8192cad Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 31 Mar 2023 12:15:25 +0300 Subject: [PATCH 042/296] test: Add some logging for future tests --- .../org.tree.kotlin-common-conventions.gradle.kts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts index f743af2e..9822be7c 100644 --- a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.api.tasks.testing.logging.TestLogEvent + /* * This file was generated by the Gradle 'init' task. */ @@ -26,3 +28,14 @@ tasks.named("test") { // Use JUnit Platform for unit tests. useJUnitPlatform() } + +tasks { + test { + testLogging { + showStandardStreams=true + events.add(TestLogEvent.FAILED) + events.add(TestLogEvent.PASSED) + events.add(TestLogEvent.SKIPPED) + } + } +} From 0d206cfd28458f53c7fdf68c3b346cc519fa2abe Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 31 Mar 2023 12:28:29 +0300 Subject: [PATCH 043/296] test: Add rootInsertTest() --- .../kotlin/org/tree/binaryTree/BinSearchTreeTest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt new file mode 100644 index 00000000..855d473f --- /dev/null +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -0,0 +1,13 @@ +package org.tree.binaryTree + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class BinSearchTreeTest { + @Test + fun rootInsertTest() { + val testTree = BinSearchTree() + testTree.insert(3) + assertEquals(testTree.root?.elem, 3) + } +} From e6d14aaacbf4b81343349a2f5bbc2f7587aa9a2b Mon Sep 17 00:00:00 2001 From: Efremov Alexey <66139162+osogi@users.noreply.github.com> Date: Fri, 31 Mar 2023 13:12:45 +0300 Subject: [PATCH 044/296] ci: Add github actions --- .github/workflows/actions.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/actions.yml diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 00000000..685d233c --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,28 @@ +name: Build project + +on: + push: +jobs: + build-gradle-project: + runs-on: ubuntu-latest + steps: + - name: Checkout project sources + uses: actions/checkout@v3 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Run build with Gradle Wrapper + run: ./gradlew build + + - name: Upload test report for library + uses: actions/upload-artifact@v3 + with: + name: library-test-report + path: binaryTree/build/reports/tests/test/ + if-no-files-found: ignore + + - name: Upload test report for app + uses: actions/upload-artifact@v3 + with: + name: app-test-report + path: app/build/reports/tests/test/ + if-no-files-found: ignore From 1e57efaf907727f1ae5e117768bfe89791a48d72 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 2 Apr 2023 11:44:48 +0300 Subject: [PATCH 045/296] feat: Add and setup jacoco plugin --- .../org.tree.kotlin-common-conventions.gradle.kts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts index 9822be7c..86fe6c9d 100644 --- a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts @@ -7,6 +7,8 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent plugins { // Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin. id("org.jetbrains.kotlin.jvm") + + id("jacoco") } repositories { @@ -39,3 +41,13 @@ tasks { } } } + +//Jacoco +tasks{ + test { + finalizedBy(jacocoTestReport) + } + jacocoTestReport { + dependsOn(test) + } +} From ebf0a9d74edc05df3ea3aabb0043f7a812e40bfc Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 2 Apr 2023 11:48:47 +0300 Subject: [PATCH 046/296] ci: Expand uploaded reports --- .github/workflows/actions.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 685d233c..42a83eb5 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -16,13 +16,13 @@ jobs: - name: Upload test report for library uses: actions/upload-artifact@v3 with: - name: library-test-report - path: binaryTree/build/reports/tests/test/ + name: library-reports + path: binaryTree/build/reports/ if-no-files-found: ignore - + - name: Upload test report for app uses: actions/upload-artifact@v3 with: - name: app-test-report - path: app/build/reports/tests/test/ + name: app-report + path: app/build/reports/ if-no-files-found: ignore From c32fe9d19c67ec84680ad82f8807ba4fad0a98de Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 2 Apr 2023 11:53:49 +0300 Subject: [PATCH 047/296] ci: Refactor actions.yml --- .github/workflows/actions.yml | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 42a83eb5..f189deec 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -6,23 +6,23 @@ jobs: build-gradle-project: runs-on: ubuntu-latest steps: - - name: Checkout project sources - uses: actions/checkout@v3 - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 - - name: Run build with Gradle Wrapper - run: ./gradlew build + - name: Checkout project sources + uses: actions/checkout@v3 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Run build with Gradle Wrapper + run: ./gradlew build - - name: Upload test report for library - uses: actions/upload-artifact@v3 - with: - name: library-reports - path: binaryTree/build/reports/ - if-no-files-found: ignore + - name: Upload reports for library + uses: actions/upload-artifact@v3 + with: + name: library-reports + path: binaryTree/build/reports/ + if-no-files-found: ignore - - name: Upload test report for app - uses: actions/upload-artifact@v3 - with: - name: app-report - path: app/build/reports/ - if-no-files-found: ignore + - name: Upload reports for app + uses: actions/upload-artifact@v3 + with: + name: app-reports + path: app/build/reports/ + if-no-files-found: ignore From 5bf3a7a92ab7af540b66b66f84caf8a257b39802 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 2 Apr 2023 13:18:42 +0300 Subject: [PATCH 048/296] ci: Fix upload reports on fail --- .github/workflows/actions.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index f189deec..6b913763 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -14,6 +14,7 @@ jobs: run: ./gradlew build - name: Upload reports for library + if: success() || failure() uses: actions/upload-artifact@v3 with: name: library-reports @@ -21,6 +22,7 @@ jobs: if-no-files-found: ignore - name: Upload reports for app + if: success() || failure() uses: actions/upload-artifact@v3 with: name: app-reports From 92b0af28ca8971401fe5910741b6257760b2885f Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 3 Apr 2023 21:12:02 +0300 Subject: [PATCH 049/296] ci: Init mergeable.yml --- .github/mergeable.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/mergeable.yml diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 00000000..b5b81013 --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,34 @@ +version: 2 +mergeable: + - when: pull_request.*, pull_request_review.* + filter: + # ignore 'Feedback' PR + - do: payload + pull_request: + title: + must_exclude: + regex: ^Feedback$ + + validate: + # Work in progress + - do: title + must_exclude: + regex: ^\[WIP\] + - do: label + must_exclude: + regex: 'wip' + + # No empty description + - do: description + no_empty: + enabled: true + message: Description matter and should not be empty. + + # Some approve + - do: approvals + min: + count: 1 + required: + assignees: true + + From ec0d6c0ce9f005b6540d7fe581f79acfc75f28a6 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 3 Apr 2023 21:22:16 +0300 Subject: [PATCH 050/296] ci(mergeable): Add git flow pattern --- .github/mergeable.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/mergeable.yml b/.github/mergeable.yml index b5b81013..a202f496 100644 --- a/.github/mergeable.yml +++ b/.github/mergeable.yml @@ -31,4 +31,21 @@ mergeable: required: assignees: true - + # Git flow + # Pull request to main only from release and hotfix branches + - when: pull_request.* + filter: + - do: payload + pull_request: + baseRef: + must_include: + regex: 'main' + # ignore 'Feedback' PR + title: + must_exclude: + regex: ^Feedback$ + validate: + - do: headRef + must_include: + regex: '^(release|hotfix)\/.+$' + message: "Create PR to main only from release and hotfix branches" From 3a249cb94f6d3b7eff148589e7f1717f073934e5 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 3 Apr 2023 22:45:07 +0300 Subject: [PATCH 051/296] ci(mergeable): Fix git flow pattern --- .github/mergeable.yml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/mergeable.yml b/.github/mergeable.yml index a202f496..1817f8db 100644 --- a/.github/mergeable.yml +++ b/.github/mergeable.yml @@ -7,13 +7,13 @@ mergeable: pull_request: title: must_exclude: - regex: ^Feedback$ + regex: '^Feedback$' validate: # Work in progress - do: title must_exclude: - regex: ^\[WIP\] + regex: '^\[WIP\]' - do: label must_exclude: regex: 'wip' @@ -31,21 +31,14 @@ mergeable: required: assignees: true - # Git flow - # Pull request to main only from release and hotfix branches - - when: pull_request.* - filter: - - do: payload - pull_request: - baseRef: - must_include: - regex: 'main' - # ignore 'Feedback' PR - title: + + # Pull request to main only from release and hotfix branches + - do: or + validate: + - do: baseRef must_exclude: - regex: ^Feedback$ - validate: - - do: headRef - must_include: - regex: '^(release|hotfix)\/.+$' - message: "Create PR to main only from release and hotfix branches" + regex: '^main$' + - do: headRef + must_include: + regex: '^(release|hotfix)\/.+$' + message: "Create PR to main only from release and hotfix branches" From d20a0decbf1769171fe380e56a77d07935c98ba7 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 3 Apr 2023 20:56:52 +0300 Subject: [PATCH 052/296] feat(RBTree): Init RBTree class --- .../src/main/kotlin/org/tree/binaryTree/RBTree.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt new file mode 100644 index 00000000..94109daa --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -0,0 +1,11 @@ +package org.tree.binaryTree + +class RBTree> : TemplateBalanceBSTree>(){ + override fun insert(curNode: RBNode?, obj: T): RBNode? { + TODO("Not yet implemented") + } + + override fun balance(curNode: RBNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { + TODO("Not yet implemented") + } +} \ No newline at end of file From 8b4ee7d7168b989458d6a9aaa2141806f47b0665 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 3 Apr 2023 20:58:48 +0300 Subject: [PATCH 053/296] feat(RBTree): Add a method to find the parent for the new node --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 94109daa..7b2e2e59 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -1,6 +1,24 @@ package org.tree.binaryTree class RBTree> : TemplateBalanceBSTree>(){ + private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { + if (curNode == null) { // impossible, but check for null is needed + return null + } + if (obj > curNode.elem) { + if (curNode.right == null) { + return curNode + } + return findParentForNewNode(curNode.right, obj) + } else if (obj < curNode.elem) { + if (curNode.left == null) { + return curNode + } + return findParentForNewNode(curNode.left, obj) + } else { // the element already exist + return null + } + } override fun insert(curNode: RBNode?, obj: T): RBNode? { TODO("Not yet implemented") } From 324bce3454c00fc84d36a697e5f5d0335e368237 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 4 Apr 2023 14:51:44 +0300 Subject: [PATCH 054/296] feat(RBTree): Override insert method --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 7b2e2e59..a0857f38 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -20,7 +20,12 @@ class RBTree> : TemplateBalanceBSTree>(){ } } override fun insert(curNode: RBNode?, obj: T): RBNode? { - TODO("Not yet implemented") + val parentForObj = findParentForNewNode(curNode, obj) + val newNode = RBNode(parentForObj, obj) + if (parentForObj == null) { // in case of root insert | node already exist (nothing will be changed) + newNode.col = RBNode.Colour.BLACK + } + return insertNode(parentForObj, newNode) } override fun balance(curNode: RBNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { From b07796b02fd47b2e6149c555b0519b9217a4ae15 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 4 Apr 2023 14:54:16 +0300 Subject: [PATCH 055/296] feat(TemplateBSTree): Open replaceNode method for legacy --- .../src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index e5382730..ec785ce9 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -114,7 +114,7 @@ abstract class TemplateBSTree, NODE_T : TemplateNode Date: Tue, 4 Apr 2023 14:57:56 +0300 Subject: [PATCH 056/296] feat(RBTree): Override rotates, replaceNode methods for balance --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index a0857f38..a4a9d282 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -31,4 +31,28 @@ class RBTree> : TemplateBalanceBSTree>(){ override fun balance(curNode: RBNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { TODO("Not yet implemented") } + override fun replaceNode(replacedNode: RBNode, parentNode: RBNode?, newNode: RBNode?) { + newNode?.parent = parentNode + super.replaceNode(replacedNode, parentNode, newNode) + } + + override fun rotateRight(curNode: RBNode, parentNode: RBNode?) { + curNode.parent = curNode.left + curNode.left?.parent = parentNode + curNode.left?.right?.parent = curNode + if (curNode === root) { + curNode.left?.col = RBNode.Colour.BLACK + } + super.rotateRight(curNode, parentNode) + } + + override fun rotateLeft(curNode: RBNode, parentNode: RBNode?) { + curNode.parent = curNode.right + curNode.right?.parent = parentNode + curNode.right?.left?.parent = curNode + if (curNode === root) { + curNode.right?.col = RBNode.Colour.BLACK + } + super.rotateLeft(curNode, parentNode) + } } \ No newline at end of file From 16dd611043ebffacef885b8775a6d9b881dc9002 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 4 Apr 2023 15:02:55 +0300 Subject: [PATCH 057/296] feat(RBTree): Add balance for insert method and some cases in balance --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 90 ++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index a4a9d282..fc872149 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -1,6 +1,6 @@ package org.tree.binaryTree -class RBTree> : TemplateBalanceBSTree>(){ +class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode == null) { // impossible, but check for null is needed return null @@ -19,6 +19,7 @@ class RBTree> : TemplateBalanceBSTree>(){ return null } } + override fun insert(curNode: RBNode?, obj: T): RBNode? { val parentForObj = findParentForNewNode(curNode, obj) val newNode = RBNode(parentForObj, obj) @@ -29,8 +30,93 @@ class RBTree> : TemplateBalanceBSTree>(){ } override fun balance(curNode: RBNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { - TODO("Not yet implemented") + if (operationType == BalanceCase.OpType.INSERT) { + if (curNode != null) { + if (recursive == BalanceCase.Recursive.END) { // curNode is parent Node of inserted Node + if (curNode.col == RBNode.Colour.RED) { + val grandParent = curNode.parent + if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree + val uncle_position: balancePosition + val uncle = if (curNode.elem < grandParent.elem) { + uncle_position = balancePosition.RIGHT_UNCLE + grandParent.right + } else { + uncle_position = balancePosition.LEFT_UNCLE + grandParent.left + } + if (uncle != null) { + if (uncle.col == RBNode.Colour.RED) { + balanceInsertCaseOfRedUncle(curNode, grandParent, uncle) + } else { + balanceInsertCaseOfBLackUncle(curNode, grandParent, uncle_position) + } + } else { // null uncle means that he is black + balanceInsertCaseOfBLackUncle(curNode, grandParent, uncle_position) + } + } + } + } + } + } + } + + enum class balancePosition { + LEFT_UNCLE, RIGHT_UNCLE } + + private fun balanceInsertCaseOfRedUncle(curNode: RBNode, grandParent: RBNode, uncle: RBNode) { + val grandGrandParent = grandParent.parent + uncle.col = RBNode.Colour.BLACK + curNode.col = RBNode.Colour.BLACK + if (grandGrandParent != null) { + grandParent.col = RBNode.Colour.RED + // https://skr.sh/sJ9LBQU2IGg, when y is curNode + if (grandGrandParent.col == RBNode.Colour.RED) { + balance(grandGrandParent, BalanceCase.OpType.INSERT, BalanceCase.Recursive.END) + } + } + } + + private fun balanceInsertCaseOfBLackUncle( + curNode: RBNode, + grandParent: RBNode, + position: balancePosition + ) { // can be null uncle + if (position == balancePosition.LEFT_UNCLE) { + val leftSon = curNode.left + if (leftSon != null) { + if (leftSon.col == RBNode.Colour.RED) { + rotateRight(curNode, grandParent) + balance(curNode.parent, BalanceCase.OpType.INSERT, BalanceCase.Recursive.END) + } + } else { + val rightSon = curNode.right + if (rightSon != null) { + curNode.col = RBNode.Colour.BLACK + grandParent.col = RBNode.Colour.RED + rotateLeft(grandParent, grandParent.parent) + } + } + } else { + val leftSon = curNode.left + if (leftSon != null) { + if (leftSon.col == RBNode.Colour.RED) { + curNode.col = RBNode.Colour.BLACK + grandParent.col = RBNode.Colour.RED + rotateRight(grandParent, grandParent.parent) + } + } else { + val rightSon = curNode.right + if (rightSon != null) { + if (rightSon.col == RBNode.Colour.RED) { + rotateLeft(curNode, grandParent) + balance(curNode.parent, BalanceCase.OpType.INSERT, BalanceCase.Recursive.END) + } + } + } + } + } + override fun replaceNode(replacedNode: RBNode, parentNode: RBNode?, newNode: RBNode?) { newNode?.parent = parentNode super.replaceNode(replacedNode, parentNode, newNode) From 21cfec4be0d27cda355df6a7e2e321ef2881b8ad Mon Sep 17 00:00:00 2001 From: David Date: Tue, 4 Apr 2023 21:59:33 +0300 Subject: [PATCH 058/296] refactor(RBTree): Improve code structure and change some names --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 93 ++++++++++--------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index fc872149..a46e1d70 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -3,7 +3,7 @@ package org.tree.binaryTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode == null) { // impossible, but check for null is needed - return null + throw IllegalArgumentException("Received null as root") } if (obj > curNode.elem) { if (curNode.right == null) { @@ -30,87 +30,96 @@ class RBTree> : TemplateBalanceBSTree>() { } override fun balance(curNode: RBNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { - if (operationType == BalanceCase.OpType.INSERT) { + if (recursive == BalanceCase.Recursive.END) { if (curNode != null) { - if (recursive == BalanceCase.Recursive.END) { // curNode is parent Node of inserted Node - if (curNode.col == RBNode.Colour.RED) { - val grandParent = curNode.parent - if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree - val uncle_position: balancePosition - val uncle = if (curNode.elem < grandParent.elem) { - uncle_position = balancePosition.RIGHT_UNCLE - grandParent.right - } else { - uncle_position = balancePosition.LEFT_UNCLE - grandParent.left - } - if (uncle != null) { - if (uncle.col == RBNode.Colour.RED) { - balanceInsertCaseOfRedUncle(curNode, grandParent, uncle) - } else { - balanceInsertCaseOfBLackUncle(curNode, grandParent, uncle_position) - } - } else { // null uncle means that he is black - balanceInsertCaseOfBLackUncle(curNode, grandParent, uncle_position) - } - } + when (operationType) { + BalanceCase.OpType.INSERT -> { // curNode is parent Node of inserted Node + balanceInsert(curNode) } + BalanceCase.OpType.REMOVE_0 -> TODO() + BalanceCase.OpType.REMOVE_1 -> TODO() + BalanceCase.OpType.REMOVE_2 -> TODO() } } } } - enum class balancePosition { + enum class BalancePosition { LEFT_UNCLE, RIGHT_UNCLE } - private fun balanceInsertCaseOfRedUncle(curNode: RBNode, grandParent: RBNode, uncle: RBNode) { + private fun balanceInsert(parentNode: RBNode) { + if (parentNode.col == RBNode.Colour.RED) { + val grandParent = parentNode.parent + if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree + val unclePosition: BalancePosition + val uncle = if (parentNode.elem < grandParent.elem) { + unclePosition = BalancePosition.RIGHT_UNCLE + grandParent.right + } else { + unclePosition = BalancePosition.LEFT_UNCLE + grandParent.left + } + if (uncle != null) { + if (uncle.col == RBNode.Colour.RED) { + balanceInsertCaseOfRedUncle(parentNode, grandParent, uncle) + } else { + balanceInsertCaseOfBLackUncle(parentNode, grandParent, unclePosition) + } + } else { // null uncle means that he is black + balanceInsertCaseOfBLackUncle(parentNode, grandParent, unclePosition) + } + } + } + } + + private fun balanceInsertCaseOfRedUncle(parentNode: RBNode, grandParent: RBNode, uncle: RBNode) { val grandGrandParent = grandParent.parent uncle.col = RBNode.Colour.BLACK - curNode.col = RBNode.Colour.BLACK + parentNode.col = RBNode.Colour.BLACK if (grandGrandParent != null) { grandParent.col = RBNode.Colour.RED // https://skr.sh/sJ9LBQU2IGg, when y is curNode if (grandGrandParent.col == RBNode.Colour.RED) { - balance(grandGrandParent, BalanceCase.OpType.INSERT, BalanceCase.Recursive.END) + balanceInsert(grandGrandParent) } } } private fun balanceInsertCaseOfBLackUncle( - curNode: RBNode, + parentNode: RBNode, grandParent: RBNode, - position: balancePosition + position: BalancePosition ) { // can be null uncle - if (position == balancePosition.LEFT_UNCLE) { - val leftSon = curNode.left + if (position == BalancePosition.LEFT_UNCLE) { + val leftSon = parentNode.left if (leftSon != null) { if (leftSon.col == RBNode.Colour.RED) { - rotateRight(curNode, grandParent) - balance(curNode.parent, BalanceCase.OpType.INSERT, BalanceCase.Recursive.END) + rotateRight(parentNode, grandParent) + parentNode.parent?.let { balanceInsert(it) } } } else { - val rightSon = curNode.right + val rightSon = parentNode.right if (rightSon != null) { - curNode.col = RBNode.Colour.BLACK + parentNode.col = RBNode.Colour.BLACK grandParent.col = RBNode.Colour.RED rotateLeft(grandParent, grandParent.parent) } } } else { - val leftSon = curNode.left + val leftSon = parentNode.left if (leftSon != null) { if (leftSon.col == RBNode.Colour.RED) { - curNode.col = RBNode.Colour.BLACK + parentNode.col = RBNode.Colour.BLACK grandParent.col = RBNode.Colour.RED rotateRight(grandParent, grandParent.parent) } } else { - val rightSon = curNode.right + val rightSon = parentNode.right if (rightSon != null) { if (rightSon.col == RBNode.Colour.RED) { - rotateLeft(curNode, grandParent) - balance(curNode.parent, BalanceCase.OpType.INSERT, BalanceCase.Recursive.END) + rotateLeft(parentNode, grandParent) + parentNode.parent?.let { balanceInsert(it) } } } } @@ -141,4 +150,4 @@ class RBTree> : TemplateBalanceBSTree>() { } super.rotateLeft(curNode, parentNode) } -} \ No newline at end of file +} From 94482b558ba6a44b26b0e7b709da832bc1458665 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 4 Apr 2023 22:20:24 +0300 Subject: [PATCH 059/296] fix(RBTree): Fix error in case of pasting an existing element --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index a46e1d70..282dc8ef 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -2,29 +2,31 @@ package org.tree.binaryTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { - if (curNode == null) { // impossible, but check for null is needed - throw IllegalArgumentException("Received null as root") - } - if (obj > curNode.elem) { - if (curNode.right == null) { - return curNode - } - return findParentForNewNode(curNode.right, obj) - } else if (obj < curNode.elem) { - if (curNode.left == null) { - return curNode + if (curNode != null) { + if (obj > curNode.elem) { + if (curNode.right == null) { + return curNode + } + return findParentForNewNode(curNode.right, obj) + } else if (obj < curNode.elem) { + if (curNode.left == null) { + return curNode + } + return findParentForNewNode(curNode.left, obj) } - return findParentForNewNode(curNode.left, obj) - } else { // the element already exist - return null } + return null } override fun insert(curNode: RBNode?, obj: T): RBNode? { val parentForObj = findParentForNewNode(curNode, obj) val newNode = RBNode(parentForObj, obj) if (parentForObj == null) { // in case of root insert | node already exist (nothing will be changed) - newNode.col = RBNode.Colour.BLACK + if (root == null) { + newNode.col = RBNode.Colour.BLACK + } else { + return null + } } return insertNode(parentForObj, newNode) } @@ -36,6 +38,7 @@ class RBTree> : TemplateBalanceBSTree>() { BalanceCase.OpType.INSERT -> { // curNode is parent Node of inserted Node balanceInsert(curNode) } + BalanceCase.OpType.REMOVE_0 -> TODO() BalanceCase.OpType.REMOVE_1 -> TODO() BalanceCase.OpType.REMOVE_2 -> TODO() From af832a59d260e3ff76b9eb3f4fcd1b24e9523720 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 5 Apr 2023 18:57:57 +0300 Subject: [PATCH 060/296] feat(TemplateBSTree): Add return value for insert() and remove() methods --- .../main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index ec785ce9..da30b1a8 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -35,8 +35,10 @@ abstract class TemplateBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode Date: Wed, 5 Apr 2023 19:46:03 +0300 Subject: [PATCH 061/296] feat(BalanceCase): Add ChangedChild case --- .../main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 743b57f7..2242f0ae 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -6,6 +6,11 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Wed, 5 Apr 2023 20:09:30 +0300 Subject: [PATCH 062/296] feat(BalanceTree): Add function for finding what child was changed --- .../org/tree/binaryTree/TemplateBalanceBSTree.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index 2242f0ae..dc2da0eb 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -28,6 +28,16 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode changed node = root protected abstract fun balance( From e1b4c4d4bc4f3d19b92cdbef7af2775080baf986 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 5 Apr 2023 20:10:54 +0300 Subject: [PATCH 063/296] feat: Edit balance() arguments --- .../kotlin/org/tree/binaryTree/AVLTree.kt | 9 +++++-- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 7 +++++- .../tree/binaryTree/TemplateBalanceBSTree.kt | 24 +++++++++++++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt index e07c9242..c47414b9 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt @@ -9,7 +9,7 @@ class AVLTree> : TemplateBalanceBSTree>() { private fun bfactor(avlNode: AVLNode?): Int { return if (avlNode != null) { - height(avlNode.right)-height(avlNode.left) + height(avlNode.right) - height(avlNode.left) } else { 0 } @@ -58,7 +58,12 @@ class AVLTree> : TemplateBalanceBSTree>() { } } - override fun balance(curNode: AVLNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { + override fun balance( + curNode: AVLNode?, + changedChild: BalanceCase.ChangedChild, + operationType: BalanceCase.OpType, + recursive: BalanceCase.Recursive + ) { when (operationType) { BalanceCase.OpType.REMOVE_0 -> {} else -> { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 282dc8ef..6cec707a 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -31,7 +31,12 @@ class RBTree> : TemplateBalanceBSTree>() { return insertNode(parentForObj, newNode) } - override fun balance(curNode: RBNode?, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive) { + override fun balance( + curNode: RBNode?, + changedChild: BalanceCase.ChangedChild, + operationType: BalanceCase.OpType, + recursive: BalanceCase.Recursive + ) { if (recursive == BalanceCase.Recursive.END) { if (curNode != null) { when (operationType) { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index dc2da0eb..db2db3fe 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -42,6 +42,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode changed node = root protected abstract fun balance( curNode: NODE_T?, + changedChild: BalanceCase.ChangedChild, operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive ) @@ -50,11 +51,26 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode Date: Thu, 6 Apr 2023 13:58:44 +0300 Subject: [PATCH 064/296] fix(BalanceTree): Fix typo in getDirectionChangedChild() fun --- .../main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt index db2db3fe..022eb6e3 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt @@ -31,7 +31,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Tue, 4 Apr 2023 22:13:49 +0300 Subject: [PATCH 065/296] feat: Setup hamcrest --- .../main/kotlin/org.tree.kotlin-common-conventions.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts index 86fe6c9d..b171874a 100644 --- a/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/org.tree.kotlin-common-conventions.gradle.kts @@ -24,6 +24,8 @@ dependencies { // Use JUnit Jupiter for testing. testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") + // Use hamcrest for convenient testing + testImplementation("org.hamcrest:hamcrest:2.2") } tasks.named("test") { From 71ac3029d7486897ce88c524d6e6896131d05a89 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 21:12:59 +0300 Subject: [PATCH 066/296] fix(TemplateBSTree): Change return of traversal() in null-root case --- .../src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt index da30b1a8..9e53af0f 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt @@ -134,7 +134,7 @@ abstract class TemplateBSTree, NODE_T : TemplateNode? { - return root?.traversal(order) + fun traversal(order: TemplateNode.Traversal): MutableList { + return root?.traversal(order) ?: mutableListOf() } } From ce93b1289e7d86ffd3a0043035359e50489271fe Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 8 Apr 2023 00:02:18 +0300 Subject: [PATCH 067/296] fix(RBTree): Fix bug in balanceInsertCaseOfBLackUncle() --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 6cec707a..33a6b745 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -101,11 +101,9 @@ class RBTree> : TemplateBalanceBSTree>() { ) { // can be null uncle if (position == BalancePosition.LEFT_UNCLE) { val leftSon = parentNode.left - if (leftSon != null) { - if (leftSon.col == RBNode.Colour.RED) { - rotateRight(parentNode, grandParent) - parentNode.parent?.let { balanceInsert(it) } - } + if (leftSon?.col == RBNode.Colour.RED) { + rotateRight(parentNode, grandParent) + parentNode.parent?.let { balanceInsert(it) } } else { val rightSon = parentNode.right if (rightSon != null) { @@ -116,12 +114,10 @@ class RBTree> : TemplateBalanceBSTree>() { } } else { val leftSon = parentNode.left - if (leftSon != null) { - if (leftSon.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK - grandParent.col = RBNode.Colour.RED - rotateRight(grandParent, grandParent.parent) - } + if (leftSon?.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK + grandParent.col = RBNode.Colour.RED + rotateRight(grandParent, grandParent.parent) } else { val rightSon = parentNode.right if (rightSon != null) { From 2bf6050e591f075b64bcc1bbc5244cc14fdd57bb Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 8 Apr 2023 11:49:28 +0300 Subject: [PATCH 068/296] refactor(RBTree): Change name "Son" to "Child" --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 33a6b745..fd8102c2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -100,28 +100,28 @@ class RBTree> : TemplateBalanceBSTree>() { position: BalancePosition ) { // can be null uncle if (position == BalancePosition.LEFT_UNCLE) { - val leftSon = parentNode.left - if (leftSon?.col == RBNode.Colour.RED) { + val leftChild = parentNode.left + if (leftChild?.col == RBNode.Colour.RED) { rotateRight(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } else { - val rightSon = parentNode.right - if (rightSon != null) { + val rightChild = parentNode.right + if (rightChild != null) { parentNode.col = RBNode.Colour.BLACK grandParent.col = RBNode.Colour.RED rotateLeft(grandParent, grandParent.parent) } } } else { - val leftSon = parentNode.left - if (leftSon?.col == RBNode.Colour.RED) { + val leftChild = parentNode.left + if (leftChild?.col == RBNode.Colour.RED) { parentNode.col = RBNode.Colour.BLACK grandParent.col = RBNode.Colour.RED rotateRight(grandParent, grandParent.parent) } else { - val rightSon = parentNode.right - if (rightSon != null) { - if (rightSon.col == RBNode.Colour.RED) { + val rightChild = parentNode.right + if (rightChild != null) { + if (rightChild.col == RBNode.Colour.RED) { rotateLeft(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } From 8b0f5e9c7f86c731cfb899a2f0e3d3ff83486cad Mon Sep 17 00:00:00 2001 From: David Date: Fri, 7 Apr 2023 11:31:44 +0300 Subject: [PATCH 069/296] feat(RBTree): Add balance for remove --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 263 +++++++++++++++++- 1 file changed, 260 insertions(+), 3 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index fd8102c2..6dcadde8 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -44,9 +44,18 @@ class RBTree> : TemplateBalanceBSTree>() { balanceInsert(curNode) } - BalanceCase.OpType.REMOVE_0 -> TODO() - BalanceCase.OpType.REMOVE_1 -> TODO() - BalanceCase.OpType.REMOVE_2 -> TODO() + BalanceCase.OpType.REMOVE_0 -> { + // does nothing + } + + BalanceCase.OpType.REMOVE_1 -> { + balanceRemove1(curNode, changedChild) + + } + + BalanceCase.OpType.REMOVE_2 -> { + balanceRemove2(curNode, changedChild) + } } } } @@ -81,6 +90,7 @@ class RBTree> : TemplateBalanceBSTree>() { } } + private fun balanceInsertCaseOfRedUncle(parentNode: RBNode, grandParent: RBNode, uncle: RBNode) { val grandGrandParent = grandParent.parent uncle.col = RBNode.Colour.BLACK @@ -130,6 +140,253 @@ class RBTree> : TemplateBalanceBSTree>() { } } + private fun balanceRemove1(parentNode: RBNode?, removedChild: BalanceCase.ChangedChild) { + if (removedChild == BalanceCase.ChangedChild.RIGHT) { + val rightChild = parentNode?.right + if (rightChild != null) { + rightChild.col = RBNode.Colour.BLACK + } + } else if (removedChild == BalanceCase.ChangedChild.LEFT) { + val leftChild = parentNode?.left + if (leftChild != null) { + leftChild.col = RBNode.Colour.BLACK + } + } else { + val rbRoot = root + if (rbRoot != null) { + rbRoot.col = RBNode.Colour.BLACK + } + } + } + + private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { + if (getColourOfRemovedNode(parentNode) == RBNode.Colour.BLACK) { // red => no need to balance + if (parentNode.col == RBNode.Colour.RED) { // then other child is black + if (removedChild == BalanceCase.ChangedChild.RIGHT) { + balanceRemove2InRightChildWithRedParent(parentNode) + } else if (removedChild == BalanceCase.ChangedChild.LEFT) { + balanceRemove2InLeftChildWithRedParent(parentNode) + } + } else { + if (removedChild == BalanceCase.ChangedChild.RIGHT) { + balanceRemove2InRightChildWithBlackParent(parentNode) + } else if (removedChild == BalanceCase.ChangedChild.LEFT) { + balanceRemove2InLeftChildWithBlackParent(parentNode) + } + } + } + } + + private fun balanceRemove2InRightChildWithRedParent(parentNode: RBNode): Int { + val otherChild = parentNode.left + if (otherChild != null) { + val leftChildOfOtherChild = otherChild.left + if (leftChildOfOtherChild != null) { + if (leftChildOfOtherChild.col == RBNode.Colour.RED) { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK + leftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(parentNode, parentNode.parent) + return 0 + } + } + val rightChildOfOtherChild = otherChild.right + if (rightChildOfOtherChild != null) { + if (rightChildOfOtherChild.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK + rotateLeft(otherChild, parentNode) + rotateRight(rightChildOfOtherChild, parentNode) + } + } + } + return 0 + } + + private fun balanceRemove2InLeftChildWithRedParent(parentNode: RBNode): Int { + val otherChild = parentNode.right + if (otherChild != null) { + val leftChildOfOtherChild = otherChild.left + if (leftChildOfOtherChild != null) { + if (leftChildOfOtherChild.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK + rotateRight(otherChild, parentNode) + rotateLeft(leftChildOfOtherChild, parentNode) + return 0 + } + } + val rightChildOfOtherChild = otherChild.right + if (rightChildOfOtherChild != null) { + if (rightChildOfOtherChild.col == RBNode.Colour.RED) { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK + rightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(parentNode, parentNode.parent) + } + } + } + return 0 + } + + private fun balanceRemove2InRightChildWithBlackParent(parentNode: RBNode) { + val otherChild = parentNode.left + if (otherChild != null) { + if (otherChild.col == RBNode.Colour.RED) { + balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) + } else { + balanceRemove2InRightChildWithBlackParentBlackOtherChild(parentNode, otherChild) + } + } + } + + private fun balanceRemove2InLeftChildWithBlackParent(parentNode: RBNode) { + val otherChild = parentNode.right + if (otherChild != null) { + if (otherChild.col == RBNode.Colour.RED) { + balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) + } else { + balanceRemove2InLeftChildWithBlackParentBlackOtherChild(parentNode, otherChild) + } + } + } + + private fun balanceRemove2InRightChildWithBlackParentRedOtherChild( + parentNode: RBNode, + otherChild: RBNode + ): Int { + val rightChildOfOtherChild = otherChild.right + if (rightChildOfOtherChild != null) { + val leftChildOfRightChildOfOtherChild = + rightChildOfOtherChild.left // https://skr.sh/sJD6DQ2ML5B + if (leftChildOfRightChildOfOtherChild != null) { + if (leftChildOfRightChildOfOtherChild.col == RBNode.Colour.RED) { + leftChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(otherChild, parentNode) + rotateRight(parentNode, parentNode.parent) + return 0 + } + } + val rightChildOfRightChildOfOtherChild = rightChildOfOtherChild.right + if (rightChildOfRightChildOfOtherChild == null) { + otherChild.col = RBNode.Colour.BLACK + rightChildOfOtherChild.col = RBNode.Colour.RED + rotateRight(parentNode, parentNode.parent) + } + } + return 0 + } + + private fun balanceRemove2InLeftChildWithBlackParentRedOtherChild( + parentNode: RBNode, + otherChild: RBNode + ): Int { + val leftChildOfOtherChild = otherChild.left + if (leftChildOfOtherChild != null) { + val rightChildOfLeftChildOfOtherChild = + leftChildOfOtherChild.right // https://skr.sh/sJD6DQ2ML5B (inverted) + if (rightChildOfLeftChildOfOtherChild != null) { + if (rightChildOfLeftChildOfOtherChild.col == RBNode.Colour.RED) { + rightChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(otherChild, parentNode) + rotateLeft(parentNode, parentNode.parent) + return 0 + } + } + val leftChildOfLeftChildOfOtherChild = leftChildOfOtherChild.left + if (leftChildOfLeftChildOfOtherChild == null) { + otherChild.col = RBNode.Colour.BLACK + leftChildOfOtherChild.col = RBNode.Colour.RED + rotateLeft(parentNode, parentNode.parent) + } + } + return 0 + } + + private fun balanceRemove2InRightChildWithBlackParentBlackOtherChild( + parentNode: RBNode, + otherChild: RBNode + ): Int { + val rightChildOfOtherChild = otherChild.right + if (rightChildOfOtherChild != null) { + if (rightChildOfOtherChild.col == RBNode.Colour.RED) { + rightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(otherChild, parentNode) + rotateRight(parentNode, parentNode.parent) + return 0 + } + } + val leftChildOfOtherChild = otherChild.left + if (leftChildOfOtherChild != null) { + if (leftChildOfOtherChild.col == RBNode.Colour.RED) { + leftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(parentNode, parentNode.parent) + return 0 + } + } + otherChild.col = RBNode.Colour.RED + val grandParent = parentNode.parent + if (grandParent != null) { + if (parentNode.elem < grandParent.elem) { + balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) + } else { + balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) + } + } + return 0 + } + + + private fun balanceRemove2InLeftChildWithBlackParentBlackOtherChild( + parentNode: RBNode, + otherChild: RBNode + ): Int { + val leftChildOfOtherChild = otherChild.left + if (leftChildOfOtherChild != null) { + if (leftChildOfOtherChild.col == RBNode.Colour.RED) { + leftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(otherChild, parentNode) + rotateLeft(parentNode, parentNode.parent) + return 0 + } + } + val rightChildOfOtherChild = otherChild.right + if (rightChildOfOtherChild != null) { + if (rightChildOfOtherChild.col == RBNode.Colour.RED) { + rightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(parentNode, parentNode.parent) + return 0 + } + } + otherChild.col = RBNode.Colour.RED + val grandParent = parentNode.parent + if (grandParent != null) { + if (parentNode.elem < grandParent.elem) { + balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) + } else { + balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) + } + } + return 0 + } + + private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Colour { + return if (getBlackHeight(parentNode.left) != getBlackHeight(parentNode.right)) { + RBNode.Colour.BLACK + } else { + RBNode.Colour.RED + } + } + + private fun getBlackHeight(curNode: RBNode?, blackHeight: Int = 0): Int { + var blackHeightVar = blackHeight + if (curNode != null) { + if (curNode.col == RBNode.Colour.BLACK) { + blackHeightVar += 1 + return getBlackHeight(curNode.left, blackHeightVar) + } + } + return blackHeightVar + } + override fun replaceNode(replacedNode: RBNode, parentNode: RBNode?, newNode: RBNode?) { newNode?.parent = parentNode super.replaceNode(replacedNode, parentNode, newNode) From 8bb247fd221c78e1585cefa9efac4b365b22711a Mon Sep 17 00:00:00 2001 From: David Date: Fri, 7 Apr 2023 11:43:46 +0300 Subject: [PATCH 070/296] feat(RBTree): Remove impossible scenario --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 6dcadde8..e67d656c 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -151,11 +151,6 @@ class RBTree> : TemplateBalanceBSTree>() { if (leftChild != null) { leftChild.col = RBNode.Colour.BLACK } - } else { - val rbRoot = root - if (rbRoot != null) { - rbRoot.col = RBNode.Colour.BLACK - } } } From b86ef4ea4fd9cb503d46b0d031c5ab95f05b044b Mon Sep 17 00:00:00 2001 From: David Date: Fri, 7 Apr 2023 12:29:09 +0300 Subject: [PATCH 071/296] fix(RBTree): Fix case of root remove with one child --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index e67d656c..7261e2d1 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -384,6 +384,11 @@ class RBTree> : TemplateBalanceBSTree>() { override fun replaceNode(replacedNode: RBNode, parentNode: RBNode?, newNode: RBNode?) { newNode?.parent = parentNode + if (parentNode == null) { + if (newNode != null) { + newNode.col = RBNode.Colour.BLACK + } + } super.replaceNode(replacedNode, parentNode, newNode) } From 41fcc7adf13550117fef1e1c0a322955614bb16b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 8 Apr 2023 22:58:16 +0300 Subject: [PATCH 072/296] fix(RBTree/remove): Fix rotates in redParent case --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 7261e2d1..271c9400 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -190,7 +190,7 @@ class RBTree> : TemplateBalanceBSTree>() { if (rightChildOfOtherChild.col == RBNode.Colour.RED) { parentNode.col = RBNode.Colour.BLACK rotateLeft(otherChild, parentNode) - rotateRight(rightChildOfOtherChild, parentNode) + rotateRight(parentNode, parentNode.parent) } } } @@ -205,7 +205,7 @@ class RBTree> : TemplateBalanceBSTree>() { if (leftChildOfOtherChild.col == RBNode.Colour.RED) { parentNode.col = RBNode.Colour.BLACK rotateRight(otherChild, parentNode) - rotateLeft(leftChildOfOtherChild, parentNode) + rotateLeft(parentNode, parentNode.parent) return 0 } } From 26f0aa5af77c0e85183f9d9f04d0b1e44ae1cb6e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 8 Apr 2023 23:10:03 +0300 Subject: [PATCH 073/296] fix(RBTree/remove): Add no child case in redParent case --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 271c9400..f5540398 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -176,6 +176,7 @@ class RBTree> : TemplateBalanceBSTree>() { val otherChild = parentNode.left if (otherChild != null) { val leftChildOfOtherChild = otherChild.left + val rightChildOfOtherChild = otherChild.right if (leftChildOfOtherChild != null) { if (leftChildOfOtherChild.col == RBNode.Colour.RED) { otherChild.col = RBNode.Colour.RED @@ -184,14 +185,15 @@ class RBTree> : TemplateBalanceBSTree>() { rotateRight(parentNode, parentNode.parent) return 0 } - } - val rightChildOfOtherChild = otherChild.right - if (rightChildOfOtherChild != null) { + } else if (rightChildOfOtherChild != null) { if (rightChildOfOtherChild.col == RBNode.Colour.RED) { parentNode.col = RBNode.Colour.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) } + } else { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK } } return 0 @@ -201,6 +203,7 @@ class RBTree> : TemplateBalanceBSTree>() { val otherChild = parentNode.right if (otherChild != null) { val leftChildOfOtherChild = otherChild.left + val rightChildOfOtherChild = otherChild.right if (leftChildOfOtherChild != null) { if (leftChildOfOtherChild.col == RBNode.Colour.RED) { parentNode.col = RBNode.Colour.BLACK @@ -208,15 +211,16 @@ class RBTree> : TemplateBalanceBSTree>() { rotateLeft(parentNode, parentNode.parent) return 0 } - } - val rightChildOfOtherChild = otherChild.right - if (rightChildOfOtherChild != null) { + } else if (rightChildOfOtherChild != null) { if (rightChildOfOtherChild.col == RBNode.Colour.RED) { otherChild.col = RBNode.Colour.RED parentNode.col = RBNode.Colour.BLACK rightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(parentNode, parentNode.parent) } + } else { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK } } return 0 From 48057e386ac5caa4a52cce15f8afdba2a14f05ea Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 00:13:31 +0300 Subject: [PATCH 074/296] fix(RBTree/remove): Fix getBlackHeight() function --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index f5540398..21c12ffe 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -380,8 +380,8 @@ class RBTree> : TemplateBalanceBSTree>() { if (curNode != null) { if (curNode.col == RBNode.Colour.BLACK) { blackHeightVar += 1 - return getBlackHeight(curNode.left, blackHeightVar) } + return getBlackHeight(curNode.left, blackHeightVar) } return blackHeightVar } From b0ec256aa9f20b7d3d14692d60714109c8f20f89 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 00:54:07 +0300 Subject: [PATCH 075/296] fix(RBTree/remove): Fix left, right typo --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 21c12ffe..38c46c33 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -325,9 +325,9 @@ class RBTree> : TemplateBalanceBSTree>() { val grandParent = parentNode.parent if (grandParent != null) { if (parentNode.elem < grandParent.elem) { - balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) - } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) + } else { + balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) } } return 0 @@ -359,9 +359,9 @@ class RBTree> : TemplateBalanceBSTree>() { val grandParent = parentNode.parent if (grandParent != null) { if (parentNode.elem < grandParent.elem) { - balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) - } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) + } else { + balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) } } return 0 From 307dd73349bb458d76f6c6d9b18116e15f6035da Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 10:10:48 +0300 Subject: [PATCH 076/296] fix(RBTree/remove): Fix red parent case --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 38c46c33..2868e49d 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -172,58 +172,46 @@ class RBTree> : TemplateBalanceBSTree>() { } } - private fun balanceRemove2InRightChildWithRedParent(parentNode: RBNode): Int { + private fun balanceRemove2InRightChildWithRedParent(parentNode: RBNode) { val otherChild = parentNode.left if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.col == RBNode.Colour.RED) { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK - leftChildOfOtherChild.col = RBNode.Colour.BLACK - rotateRight(parentNode, parentNode.parent) - return 0 - } - } else if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK - rotateLeft(otherChild, parentNode) - rotateRight(parentNode, parentNode.parent) - } + if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK + leftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(parentNode, parentNode.parent) + } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK + rotateLeft(otherChild, parentNode) + rotateRight(parentNode, parentNode.parent) } else { otherChild.col = RBNode.Colour.RED parentNode.col = RBNode.Colour.BLACK } } - return 0 } - private fun balanceRemove2InLeftChildWithRedParent(parentNode: RBNode): Int { + private fun balanceRemove2InLeftChildWithRedParent(parentNode: RBNode) { val otherChild = parentNode.right if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK - rotateRight(otherChild, parentNode) - rotateLeft(parentNode, parentNode.parent) - return 0 - } - } else if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.col == RBNode.Colour.RED) { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK - rightChildOfOtherChild.col = RBNode.Colour.BLACK - rotateLeft(parentNode, parentNode.parent) - } + if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK + rotateRight(otherChild, parentNode) + rotateLeft(parentNode, parentNode.parent) + } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK + rightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(parentNode, parentNode.parent) } else { otherChild.col = RBNode.Colour.RED parentNode.col = RBNode.Colour.BLACK } } - return 0 } private fun balanceRemove2InRightChildWithBlackParent(parentNode: RBNode) { From eedd7f5899861a60556bcfeeb5ab5e6195674436 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 10:23:51 +0300 Subject: [PATCH 077/296] fix(RBTree/remove): Fix black parent red other child case --- binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index 2868e49d..fdea90af 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -253,7 +253,7 @@ class RBTree> : TemplateBalanceBSTree>() { } } val rightChildOfRightChildOfOtherChild = rightChildOfOtherChild.right - if (rightChildOfRightChildOfOtherChild == null) { + if (rightChildOfRightChildOfOtherChild?.col != RBNode.Colour.RED) { otherChild.col = RBNode.Colour.BLACK rightChildOfOtherChild.col = RBNode.Colour.RED rotateRight(parentNode, parentNode.parent) @@ -279,7 +279,7 @@ class RBTree> : TemplateBalanceBSTree>() { } } val leftChildOfLeftChildOfOtherChild = leftChildOfOtherChild.left - if (leftChildOfLeftChildOfOtherChild == null) { + if (leftChildOfLeftChildOfOtherChild?.col != RBNode.Colour.RED) { otherChild.col = RBNode.Colour.BLACK leftChildOfOtherChild.col = RBNode.Colour.RED rotateLeft(parentNode, parentNode.parent) From f9812ab317d768095667f3f1770383cb21cddcdd Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 11:34:28 +0300 Subject: [PATCH 078/296] fix(RBTree/remove): Add case to black parent red other child case --- .../main/kotlin/org/tree/binaryTree/RBTree.kt | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt index fdea90af..0b854138 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt @@ -239,53 +239,57 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InRightChildWithBlackParentRedOtherChild( parentNode: RBNode, otherChild: RBNode - ): Int { + ) { val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { val leftChildOfRightChildOfOtherChild = rightChildOfOtherChild.left // https://skr.sh/sJD6DQ2ML5B - if (leftChildOfRightChildOfOtherChild != null) { - if (leftChildOfRightChildOfOtherChild.col == RBNode.Colour.RED) { - leftChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK - rotateLeft(otherChild, parentNode) - rotateRight(parentNode, parentNode.parent) - return 0 - } - } val rightChildOfRightChildOfOtherChild = rightChildOfOtherChild.right - if (rightChildOfRightChildOfOtherChild?.col != RBNode.Colour.RED) { + + if (leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { + leftChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(otherChild, parentNode) + rotateRight(parentNode, parentNode.parent) + } else if (rightChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { + rightChildOfOtherChild.col = RBNode.Colour.RED + rightChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + rotateLeft(rightChildOfOtherChild, otherChild) + balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) + // case: leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED + } else { otherChild.col = RBNode.Colour.BLACK rightChildOfOtherChild.col = RBNode.Colour.RED rotateRight(parentNode, parentNode.parent) } } - return 0 } private fun balanceRemove2InLeftChildWithBlackParentRedOtherChild( parentNode: RBNode, otherChild: RBNode - ): Int { + ) { val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { val rightChildOfLeftChildOfOtherChild = leftChildOfOtherChild.right // https://skr.sh/sJD6DQ2ML5B (inverted) - if (rightChildOfLeftChildOfOtherChild != null) { - if (rightChildOfLeftChildOfOtherChild.col == RBNode.Colour.RED) { - rightChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK - rotateRight(otherChild, parentNode) - rotateLeft(parentNode, parentNode.parent) - return 0 - } - } val leftChildOfLeftChildOfOtherChild = leftChildOfOtherChild.left - if (leftChildOfLeftChildOfOtherChild?.col != RBNode.Colour.RED) { + + if (rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { + rightChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(otherChild, parentNode) + rotateLeft(parentNode, parentNode.parent) + } else if (leftChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { + leftChildOfOtherChild.col = RBNode.Colour.RED + leftChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + rotateRight(leftChildOfOtherChild, otherChild) + balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) + // case: rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED + } else { otherChild.col = RBNode.Colour.BLACK leftChildOfOtherChild.col = RBNode.Colour.RED rotateLeft(parentNode, parentNode.parent) } } - return 0 } private fun balanceRemove2InRightChildWithBlackParentBlackOtherChild( From 1feef24576595a92e655401c5629452001c0fdb4 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 12 Apr 2023 18:11:26 +0300 Subject: [PATCH 079/296] struct: Move trees to separate package(dir) --- .../main/kotlin/org/tree/binaryTree/{ => trees}/AVLTree.kt | 4 +++- .../kotlin/org/tree/binaryTree/{ => trees}/BinSearchTree.kt | 5 ++++- .../main/kotlin/org/tree/binaryTree/{ => trees}/RBTree.kt | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) rename binaryTree/src/main/kotlin/org/tree/binaryTree/{ => trees}/AVLTree.kt (95%) rename binaryTree/src/main/kotlin/org/tree/binaryTree/{ => trees}/BinSearchTree.kt (63%) rename binaryTree/src/main/kotlin/org/tree/binaryTree/{ => trees}/RBTree.kt (99%) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt similarity index 95% rename from binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt rename to binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt index c47414b9..c6f53080 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt @@ -1,5 +1,7 @@ -package org.tree.binaryTree +package org.tree.binaryTree.trees +import org.tree.binaryTree.AVLNode +import org.tree.binaryTree.TemplateBalanceBSTree import kotlin.math.max class AVLTree> : TemplateBalanceBSTree>() { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt similarity index 63% rename from binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt rename to binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt index 44427ad7..65edacec 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/BinSearchTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt @@ -1,4 +1,7 @@ -package org.tree.binaryTree +package org.tree.binaryTree.trees + +import org.tree.binaryTree.Node +import org.tree.binaryTree.TemplateBSTree class BinSearchTree> : TemplateBSTree>() { override fun insert(curNode: Node?, obj: T): Node? { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt similarity index 99% rename from binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt rename to binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 0b854138..c389d2a2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -1,4 +1,7 @@ -package org.tree.binaryTree +package org.tree.binaryTree.trees + +import org.tree.binaryTree.RBNode +import org.tree.binaryTree.TemplateBalanceBSTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { From 65e2df2354db7bb1a3698992ed0d2460cf80dc44 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 12 Apr 2023 18:14:03 +0300 Subject: [PATCH 080/296] struct: Extract TemplateNode class to separate file --- .../main/kotlin/org/tree/binaryTree/Nodes.kt | 55 ------------------ .../org/tree/binaryTree/TemplateNode.kt | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 55 deletions(-) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index 61e27b26..a37e29d3 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -1,60 +1,5 @@ package org.tree.binaryTree -abstract class TemplateNode, NODE_T : TemplateNode>(v: T) { - var elem: T = v - var left: NODE_T? = null - var right: NODE_T? = null - - fun getNonNullChild(): NODE_T? { - val res = if (left != null) { - left - } else if (right != null) { - right - } else { - null - } - return res - } - - fun countNullChildren(): Int { - var res = 0 - if (left == null) { - res += 1 - } - if (right == null) { - res += 1 - } - return res - } - - //Traversal - enum class Traversal { - INORDER, - PREORDER, - POSTORDER - } - - private fun traversal(res: MutableList, cs: Traversal) { - if (cs == Traversal.PREORDER) { - res.add(elem) - } - left?.traversal(res, cs) - if (cs == Traversal.INORDER) { - res.add(elem) - } - right?.traversal(res, cs) - if (cs == Traversal.POSTORDER) { - res.add(elem) - } - } - - fun traversal(order: Traversal): MutableList { - val res: MutableList = mutableListOf() - traversal(res, order) - return res - } -} - class Node>(v: T) : TemplateNode>(v) class RBNode>(p: RBNode?, v: T) : TemplateNode>(v) { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt new file mode 100644 index 00000000..cb9751e1 --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt @@ -0,0 +1,56 @@ +package org.tree.binaryTree + +abstract class TemplateNode, NODE_T : TemplateNode>(v: T) { + var elem: T = v + var left: NODE_T? = null + var right: NODE_T? = null + + fun getNonNullChild(): NODE_T? { + val res = if (left != null) { + left + } else if (right != null) { + right + } else { + null + } + return res + } + + fun countNullChildren(): Int { + var res = 0 + if (left == null) { + res += 1 + } + if (right == null) { + res += 1 + } + return res + } + + //Traversal + enum class Traversal { + INORDER, + PREORDER, + POSTORDER + } + + private fun traversal(res: MutableList, cs: Traversal) { + if (cs == Traversal.PREORDER) { + res.add(elem) + } + left?.traversal(res, cs) + if (cs == Traversal.INORDER) { + res.add(elem) + } + right?.traversal(res, cs) + if (cs == Traversal.POSTORDER) { + res.add(elem) + } + } + + fun traversal(order: Traversal): MutableList { + val res: MutableList = mutableListOf() + traversal(res, order) + return res + } +} From a375d1e607f0339a0e5a3b0789296828769b1035 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 12 Apr 2023 18:15:28 +0300 Subject: [PATCH 081/296] struct: Move templates to separate package(dir) --- binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt | 2 ++ .../org/tree/binaryTree/{ => templates}/TemplateBSTree.kt | 2 +- .../tree/binaryTree/{ => templates}/TemplateBalanceBSTree.kt | 2 +- .../kotlin/org/tree/binaryTree/{ => templates}/TemplateNode.kt | 2 +- binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt | 2 +- .../src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt | 2 +- binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) rename binaryTree/src/main/kotlin/org/tree/binaryTree/{ => templates}/TemplateBSTree.kt (99%) rename binaryTree/src/main/kotlin/org/tree/binaryTree/{ => templates}/TemplateBalanceBSTree.kt (99%) rename binaryTree/src/main/kotlin/org/tree/binaryTree/{ => templates}/TemplateNode.kt (96%) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index a37e29d3..62167092 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -1,5 +1,7 @@ package org.tree.binaryTree +import org.tree.binaryTree.templates.TemplateNode + class Node>(v: T) : TemplateNode>(v) class RBNode>(p: RBNode?, v: T) : TemplateNode>(v) { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt similarity index 99% rename from binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt rename to binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index 9e53af0f..d1f26426 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -1,4 +1,4 @@ -package org.tree.binaryTree +package org.tree.binaryTree.templates abstract class TemplateBSTree, NODE_T : TemplateNode> { var root: NODE_T? = null diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt similarity index 99% rename from binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt rename to binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index 022eb6e3..d35c4a5b 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -1,4 +1,4 @@ -package org.tree.binaryTree +package org.tree.binaryTree.templates abstract class TemplateBalanceBSTree, NODE_T : TemplateNode> : diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt similarity index 96% rename from binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt rename to binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt index cb9751e1..716ed620 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/TemplateNode.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt @@ -1,4 +1,4 @@ -package org.tree.binaryTree +package org.tree.binaryTree.templates abstract class TemplateNode, NODE_T : TemplateNode>(v: T) { var elem: T = v diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt index c6f53080..310b0956 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt @@ -1,7 +1,7 @@ package org.tree.binaryTree.trees import org.tree.binaryTree.AVLNode -import org.tree.binaryTree.TemplateBalanceBSTree +import org.tree.binaryTree.templates.TemplateBalanceBSTree import kotlin.math.max class AVLTree> : TemplateBalanceBSTree>() { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt index 65edacec..5303d180 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt @@ -1,7 +1,7 @@ package org.tree.binaryTree.trees import org.tree.binaryTree.Node -import org.tree.binaryTree.TemplateBSTree +import org.tree.binaryTree.templates.TemplateBSTree class BinSearchTree> : TemplateBSTree>() { override fun insert(curNode: Node?, obj: T): Node? { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index c389d2a2..4a6b9184 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -1,7 +1,7 @@ package org.tree.binaryTree.trees import org.tree.binaryTree.RBNode -import org.tree.binaryTree.TemplateBalanceBSTree +import org.tree.binaryTree.templates.TemplateBalanceBSTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { From b4963481c491e9270bc546779d5a3809c51beeb0 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 12 Apr 2023 18:22:59 +0300 Subject: [PATCH 082/296] fix: Fix importing of BinSearchTree in test --- .../src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 855d473f..1e93b6fd 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -2,6 +2,7 @@ package org.tree.binaryTree import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.tree.binaryTree.trees.BinSearchTree class BinSearchTreeTest { @Test From 4f80d531579318617912704f86d18e9f12ffc1db Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 20:45:53 +0300 Subject: [PATCH 083/296] test: Add tree generator --- .../test/kotlin/org/tree/binaryTree/Util.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt new file mode 100644 index 00000000..68edbbf3 --- /dev/null +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -0,0 +1,41 @@ +package org.tree.binaryTree + +import java.util.* +import kotlin.math.log2 + + +// Generate +fun > generateTree(nodes: List): NODE_T? { + val fheight = log2((nodes.size + 1).toFloat()) + val height = fheight.toInt() + if (fheight - height > 0.0) { + throw IllegalArgumentException("Received an invalid array to create a tree. Size of array should be equal (2**N)-1") + } + + val q: Queue = LinkedList() + val root = nodes[0] + q.add(root) + var i = 1 + while (i < nodes.size) { + val cur = q.poll() + cur?.left = nodes[i] + i++ + q.add(cur?.left) + cur?.right = nodes[i] + i++ + q.add(cur?.right) + } + return root +} + +fun > generateNodeTree(objects: List): Node? { + val new = objects.map { + if (it != null) { + Node(it) + } else { + null + } + } + return generateTree(new) +} + From ee47b205367ec3d1220957924028d15eacd8f62c Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 20:47:18 +0300 Subject: [PATCH 084/296] test: Add tree check fun --- .../test/kotlin/org/tree/binaryTree/Util.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt index 68edbbf3..28500eeb 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -1,5 +1,7 @@ package org.tree.binaryTree +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.* import java.util.* import kotlin.math.log2 @@ -39,3 +41,27 @@ fun > generateNodeTree(objects: List): Node? { return generateTree(new) } +// Check +fun , NODE_T : TemplateNode> checkTreeNode(curNode: NODE_T?) { + if (curNode != null) { + val l = curNode.left + if (l != null) { + assertThat(curNode.elem, greaterThanOrEqualTo(l.elem)) + checkTreeNode(l) + } + + val r = curNode.right + if (r != null) { + assertThat(curNode.elem, lessThanOrEqualTo(r.elem)) + checkTreeNode(r) + } + } +} + +fun , NODE_T : TemplateNode> checkBinSearchTree( + tree: TemplateBSTree, + contain: Array +) { + assertThat(tree.traversal(TemplateNode.Traversal.INORDER), containsInAnyOrder(*contain)) + checkTreeNode(tree.root) +} From f443e0330d039ebb7885d5365c87910022636c84 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 20:48:05 +0300 Subject: [PATCH 085/296] test(BinSearchTree): Add insert() tests --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 1e93b6fd..c8954364 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -1,14 +1,77 @@ package org.tree.binaryTree -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.tree.binaryTree.trees.BinSearchTree + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.* +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 class BinSearchTreeTest { - @Test - fun rootInsertTest() { - val testTree = BinSearchTree() - testTree.insert(3) - assertEquals(testTree.root?.elem, 3) + @DisplayName("insert() tests") + class InsertTests { + @ParameterizedTest(name = "[{index}]: tree = {0}, insert = {1}") + @MethodSource("testInsertArgs") + fun testInsert(values: List, insVal: Int) { + val tree = BinSearchTree() + tree.root = generateNodeTree(values) + val valuesSet = values.filterNotNull().toMutableSet() + val exp = valuesSet.add(insVal) + val act = tree.insert(insVal) + + assertThat(act, equalTo(exp)) + checkBinSearchTree(tree, valuesSet.toTypedArray()) + } + + companion object { + @JvmStatic + fun testInsertArgs(): Stream { + return Stream.of( + //[1] root insert + Arguments.of(listOf(null), 40), + //[2] left insert + Arguments.of( + listOf( + 40, + ), + 20 + ), + //[3] right insert + Arguments.of( + listOf( + 40, + ), + 60 + ), + //[4] left rec insert + Arguments.of( + listOf( + 40, + 20, 60 + ), + 21 + ), + //[5] right rec insert + Arguments.of( + listOf( + 40, + 20, 60 + ), + 55 + ), + //[6] same insert + Arguments.of( + listOf( + 40, + null, 60, + null, null, 45, null, + ), + 45 + ), + ) + } + } } } From 7660907f425ee9e954505ade92001b3282ad767e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 20:56:30 +0300 Subject: [PATCH 086/296] test(BinSearchTree): Add remove() tests --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index c8954364..eec79e85 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -74,4 +74,56 @@ class BinSearchTreeTest { } } } + + @DisplayName("remove() tests") + class RemoveTests { + @ParameterizedTest(name = "[{index}]: tree = {0}, remove = {1}") + @MethodSource("testRemoveArgs") + fun testRemove(values: List, remVal: Int) { + val tree = BinSearchTree() + tree.root = generateNodeTree(values) + val valuesSet = values.filterNotNull().toMutableSet() + val exp = valuesSet.remove(remVal) + val act = tree.remove(remVal) + + assertThat(act, equalTo(exp)) + checkBinSearchTree(tree, valuesSet.toTypedArray()) + } + + companion object { + @JvmStatic + fun testRemoveArgs(): Stream { + return Stream.of( + //[1] root remove + Arguments.of( + listOf( + 40, + ), 40 + ), + //[2] left remove + Arguments.of( + listOf( + 40, + 20, 60, + ), 20 + ), + //[3] right remove + Arguments.of( + listOf( + 40, + 20, 60, + ), 40 + ), + //[4] empty remove + Arguments.of( + listOf( + 40, + 20, 60, + null, null, null, null, + ), 42 + ), + ) + } + } + } } From 14cb4a2e52ef14757548803be8069d3a8a8f57b0 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 21:10:13 +0300 Subject: [PATCH 087/296] test(BinSearchTree): Add find() tests --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index eec79e85..ba8e36f3 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -126,4 +126,58 @@ class BinSearchTreeTest { } } } + + @DisplayName("find() tests") + class FindTests { + @ParameterizedTest(name = "[{index}]: tree = {0}, find = {1}") + @MethodSource("testFindArgs") + fun testFind(values: List, fndVal: Int) { + val tree = BinSearchTree() + tree.root = generateNodeTree(values) + val valuesSet = values.filterNotNull().toMutableSet() + val exp = valuesSet.contains(fndVal) + val act = tree.find(fndVal) + + assertThat(act != null, equalTo(exp)) + if (act != null) { + assertThat(act.elem, equalTo(fndVal)) + } + } + + companion object { + @JvmStatic + fun testFindArgs(): Stream { + return Stream.of( + //[1] root find + Arguments.of( + listOf( + 40, + 20, 60 + ), 40 + ), + //[2] left find + Arguments.of( + listOf( + 40, + 20, 60 + ), 20 + ), + //[3] right find + Arguments.of( + listOf( + 40, + 20, 60, + ), 60 + ), + //[4] impossible find + Arguments.of( + listOf( + 40, + 20, 60 + ), 42 + ), + ) + } + } + } } From 2b9073cad2534329720abc8b83d89b0076ba1aed Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 21:51:26 +0300 Subject: [PATCH 088/296] test(BinSearchTree): Add big insert test --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index ba8e36f3..181b7539 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -8,6 +8,7 @@ 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 class BinSearchTreeTest { @DisplayName("insert() tests") @@ -73,6 +74,23 @@ class BinSearchTreeTest { ) } } + + @Test + fun bigInsertTest() { + val tree = BinSearchTree() + val values = mutableSetOf() + val randomizer = Random(0xdeadbeef) + + for (i in 0..100) { + val newVal = randomizer.nextInt() + val exp = values.add(newVal) + val act = tree.insert(newVal) + //println("Insert $newVal") + + assertThat(act, equalTo(exp)) + checkBinSearchTree(tree, values.toTypedArray()) + } + } } @DisplayName("remove() tests") From a2846294e75352797e173c3b01b7c591d0b160e3 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 22:02:25 +0300 Subject: [PATCH 089/296] test(BinSearchTree): Add big remove test --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 181b7539..6ea7ff43 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -143,6 +143,32 @@ class BinSearchTreeTest { ) } } + + @Test + fun bigRemoveTest() { + val tree = BinSearchTree() + val values = mutableSetOf() + val randomizer = Random(0xdeadbeef) + + for (i in 0..100) { + val newVal = randomizer.nextInt() + values.add(newVal) + tree.insert(newVal) + } + + for (i in 0..150) { + val curVal = if ((i % 2 == 0) and (values.isNotEmpty())) { + values.first() + } else { + randomizer.nextInt() + } + val exp = values.remove(curVal) + val act = tree.remove(curVal) + + assertThat(act, equalTo(exp)) + checkBinSearchTree(tree, values.toTypedArray()) + } + } } @DisplayName("find() tests") From 7044b1dbe9211b6713fda18439a22f23e7f2ef07 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 22:30:38 +0300 Subject: [PATCH 090/296] test(BinSearchTree): Add big find test --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 6ea7ff43..053b0453 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -223,5 +223,33 @@ class BinSearchTreeTest { ) } } + + @Test + fun bigFindTest() { + val tree = BinSearchTree() + val values = mutableSetOf() + val randomizer = Random(0xdeadbeef) + + for (i in 0..100) { + val newVal = randomizer.nextInt() + values.add(newVal) + tree.insert(newVal) + } + + for (i in 0..150) { + val curVal = if ((i % 2 == 0) and (values.isNotEmpty())) { + values.random(randomizer) + } else { + randomizer.nextInt() + } + val exp = values.contains(curVal) + val act = tree.find(curVal) + + assertThat(act != null, equalTo(exp)) + if (act != null) { + assertThat(act.elem, equalTo(curVal)) + } + } + } } } From 4f4e56dc61b086205c9b5350b8b5780355e44bf3 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 7 Apr 2023 22:39:41 +0300 Subject: [PATCH 091/296] test(BinSearchTree): Add traversal() tests --- .../org/tree/binaryTree/BinSearchTreeTest.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 053b0453..6382cb86 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -252,4 +252,49 @@ class BinSearchTreeTest { } } } + + @DisplayName("traversal() tests") + class TraversalTests { + private var tree = BinSearchTree() + + @BeforeEach + fun init() { + tree.root = generateNodeTree( + listOf( + 40, + 20, 60, + 15, null, null, 67, + ) + ) + } + + @Test + fun preorderTraversalTest() { + val act = tree.traversal(TemplateNode.Traversal.PREORDER) + val exp = listOf(40, 20, 15, 60, 67) + assertThat(act, equalTo(exp)) + } + + @Test + fun inorderTraversalTest() { + val act = tree.traversal(TemplateNode.Traversal.INORDER) + val exp = listOf(15, 20, 40, 60, 67) + assertThat(act, equalTo(exp)) + } + + @Test + fun postorderTraversalTest() { + val act = tree.traversal(TemplateNode.Traversal.POSTORDER) + val exp = listOf(15, 20, 67, 60, 40) + assertThat(act, equalTo(exp)) + } + + @Test + fun emptyTraversalTest() { + tree.root = null + val act = tree.traversal(TemplateNode.Traversal.PREORDER) + val exp = listOf() + assertThat(act, equalTo(exp)) + } + } } From c563758ece6021fa902c24880c1fe64c41d60f0f Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 12 Apr 2023 14:07:05 +0300 Subject: [PATCH 092/296] test(BinSearchTree): Remove debug comment --- .../src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 6382cb86..724af6d5 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -85,7 +85,6 @@ class BinSearchTreeTest { val newVal = randomizer.nextInt() val exp = values.add(newVal) val act = tree.insert(newVal) - //println("Insert $newVal") assertThat(act, equalTo(exp)) checkBinSearchTree(tree, values.toTypedArray()) From d2ea43991b34b2e7af9de7776e22459e239c5cdc Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 10:11:30 +0300 Subject: [PATCH 093/296] test(RBTree): Add RB tree check --- .../test/kotlin/org/tree/binaryTree/Util.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt index 28500eeb..c199ad3b 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -65,3 +65,49 @@ fun , NODE_T : TemplateNode> checkBinSearchTree( assertThat(tree.traversal(TemplateNode.Traversal.INORDER), containsInAnyOrder(*contain)) checkTreeNode(tree.root) } + +fun > checkRBTreeNode(curNode: RBNode?, parNode: RBNode?): Int { + var blackHeight = 0 + if (curNode != null) { + // two red nodes in row + if (parNode?.col == RBNode.Colour.RED) { + assertThat(curNode.col, equalTo(RBNode.Colour.BLACK)) + } + + // right parent + assertThat(curNode.parent, equalTo(parNode)) + + var lBlackHeight = 0 + val l = curNode.left + if (l != null) { + assertThat(curNode.elem, greaterThanOrEqualTo(l.elem)) + lBlackHeight = checkRBTreeNode(l, curNode) + } + + var rBlackHeight = 0 + val r = curNode.right + if (r != null) { + assertThat(curNode.elem, lessThanOrEqualTo(r.elem)) + rBlackHeight = checkRBTreeNode(r, curNode) + } + + // same black height + assertThat(lBlackHeight, equalTo(rBlackHeight)) + blackHeight = lBlackHeight + + if (curNode.col == RBNode.Colour.BLACK) { + blackHeight += 1 + } + } + + return blackHeight +} + +fun > checkRBTree( + tree: RBTree, + contain: Array +) { + assertThat(tree.traversal(TemplateNode.Traversal.INORDER), containsInAnyOrder(*contain)) + checkRBTreeNode(tree.root, null) +} + From b79179fe036205be2f33b8c886ce28aff5cb2555 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 9 Apr 2023 10:12:16 +0300 Subject: [PATCH 094/296] test(RBTree): Add remove() tests --- .../kotlin/org/tree/binaryTree/RBTreeTest.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt new file mode 100644 index 00000000..f27a387e --- /dev/null +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt @@ -0,0 +1,64 @@ +package org.tree.binaryTree + +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers +import org.junit.jupiter.api.DisplayName +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 + +class RBTreeTest { + @DisplayName("remove() tests") + class RemoveTests { + @ParameterizedTest(name = "[{index}]: treeSize = {1}, removeCount = {2}, seed = {0}") + @MethodSource("testRemoveArgs") + fun testRemove(seed: Long, treeSize: Int, removeCount: Int) { + val tree = RBTree() + val values = mutableSetOf() + val randomizer = Random(seed) + for (i in 1..treeSize) { + val newVal = randomizer.nextInt() + values.add(newVal) + tree.insert(newVal) + } + + for (i in 1..removeCount * 2) { + val curVal = if ((i % 2 != 0) and (values.isNotEmpty())) { + values.random(randomizer) + } else { + randomizer.nextInt() + } + val exp = values.remove(curVal) + val act = tree.remove(curVal) + + + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) + checkRBTree(tree, values.toTypedArray()) + } + } + + companion object { + @JvmStatic + fun testRemoveArgs(): Stream { + return Stream.of( + genArguments(0xdeadbeef), + genArguments(0xdeadbeef, 1000, 1050), + genArguments(0xdeadbeef, 1050, 1000), + genArguments(0xdeadbeef, 10, 5), + genArguments(0xdeadbeef, 0, 1), + genArguments(42), + genArguments(13), + genArguments(0xcafe), + genArguments(1337), + ) + } + + private fun genArguments(seed: Long, treeSize: Int = 1000, removeCount: Int = 1000): Arguments { + return Arguments.of(seed, treeSize, removeCount) + } + } + + } +} From dfd364de348345131b0f8407f596da9d12f1ede7 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 10 Apr 2023 14:59:47 +0300 Subject: [PATCH 095/296] test(RBTree): Add insert() tests --- .../kotlin/org/tree/binaryTree/RBTreeTest.kt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt index f27a387e..9f9d6dd2 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt @@ -10,6 +10,49 @@ import java.util.stream.Stream import kotlin.random.Random class RBTreeTest { + @DisplayName("insert() tests") + class InsertTests { + @ParameterizedTest(name = "[{index}]: insertCount = {1}, seed = {0}") + @MethodSource("testInsertArgs") + fun testInsert(seed: Long, insertCount: Int) { + val tree = RBTree() + val values = mutableSetOf() + val randomizer = Random(seed) + + for (i in 1..insertCount) { + val newVal = randomizer.nextInt() + val exp = values.add(newVal) + val act = tree.insert(newVal) + //println("Insert $newVal") + + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) + checkRBTree(tree, values.toTypedArray()) + } + + + } + + companion object { + @JvmStatic + fun testInsertArgs(): Stream { + return Stream.of( + genArguments(0xdeadbeef), + genArguments(0xabacaba, 10), + genArguments(42), + genArguments(13), + genArguments(0xcafe), + genArguments(1337), + ) + } + + private fun genArguments(seed: Long, insertCount: Int = 1000): Arguments { + return Arguments.of(seed, insertCount) + } + } + + } + + @DisplayName("remove() tests") class RemoveTests { @ParameterizedTest(name = "[{index}]: treeSize = {1}, removeCount = {2}, seed = {0}") From 8139b224eec285965fe961fa0d73f1355a5bc13d Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 10 Apr 2023 15:07:40 +0300 Subject: [PATCH 096/296] test: Change tests DisplayName --- .../test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt | 8 ++++---- .../src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 724af6d5..39a1d8fc 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -11,7 +11,7 @@ import java.util.stream.Stream import kotlin.random.Random class BinSearchTreeTest { - @DisplayName("insert() tests") + @DisplayName("BinSearchTree.insert() tests") class InsertTests { @ParameterizedTest(name = "[{index}]: tree = {0}, insert = {1}") @MethodSource("testInsertArgs") @@ -92,7 +92,7 @@ class BinSearchTreeTest { } } - @DisplayName("remove() tests") + @DisplayName("BinSearchTree.remove() tests") class RemoveTests { @ParameterizedTest(name = "[{index}]: tree = {0}, remove = {1}") @MethodSource("testRemoveArgs") @@ -170,7 +170,7 @@ class BinSearchTreeTest { } } - @DisplayName("find() tests") + @DisplayName("BinSearchTree.find() tests") class FindTests { @ParameterizedTest(name = "[{index}]: tree = {0}, find = {1}") @MethodSource("testFindArgs") @@ -252,7 +252,7 @@ class BinSearchTreeTest { } } - @DisplayName("traversal() tests") + @DisplayName("BinSearchTree.traversal() tests") class TraversalTests { private var tree = BinSearchTree() diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt index 9f9d6dd2..aa405f9a 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt @@ -10,7 +10,7 @@ import java.util.stream.Stream import kotlin.random.Random class RBTreeTest { - @DisplayName("insert() tests") + @DisplayName("RBTree.insert() tests") class InsertTests { @ParameterizedTest(name = "[{index}]: insertCount = {1}, seed = {0}") @MethodSource("testInsertArgs") @@ -53,7 +53,7 @@ class RBTreeTest { } - @DisplayName("remove() tests") + @DisplayName("RBTree.remove() tests") class RemoveTests { @ParameterizedTest(name = "[{index}]: treeSize = {1}, removeCount = {2}, seed = {0}") @MethodSource("testRemoveArgs") From 9aca66b00fa56851a6957342f1d199c710d829c9 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 15 Apr 2023 13:52:26 +0300 Subject: [PATCH 097/296] refactor: Update imports for tests --- .../src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt | 2 ++ binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt | 1 + binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt | 3 +++ 3 files changed, 6 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 39a1d8fc..152c05bb 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -7,6 +7,8 @@ import org.junit.jupiter.api.* import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import org.tree.binaryTree.templates.TemplateNode +import org.tree.binaryTree.trees.BinSearchTree import java.util.stream.Stream import kotlin.random.Random diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt index aa405f9a..fef42349 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt @@ -6,6 +6,7 @@ import org.junit.jupiter.api.DisplayName import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import org.tree.binaryTree.trees.RBTree import java.util.stream.Stream import kotlin.random.Random diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt index c199ad3b..dc7e8803 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -2,6 +2,9 @@ package org.tree.binaryTree import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.* +import org.tree.binaryTree.templates.TemplateBSTree +import org.tree.binaryTree.templates.TemplateNode +import org.tree.binaryTree.trees.RBTree import java.util.* import kotlin.math.log2 From 96da1fed852244b422583f2348f32a9038282d49 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 15 Apr 2023 14:25:17 +0300 Subject: [PATCH 098/296] feat: Add KeyValuePair class --- .../src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt new file mode 100644 index 00000000..a19f9124 --- /dev/null +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt @@ -0,0 +1,7 @@ +package org.tree.binaryTree + +class KVP, V>(val key: K, var v: V?) : Comparable> { + override fun compareTo(other: KVP): Int { + return key.compareTo(other.key) + } +} From 09ff718ecd30617bf3816396e4180b3368104217 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 6 Apr 2023 14:07:09 +0300 Subject: [PATCH 099/296] test: Init NodeTest --- binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt new file mode 100644 index 00000000..082ed0c4 --- /dev/null +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -0,0 +1,4 @@ +package org.tree.binaryTree + +class NodeTest { +} From b2b879e06d4c4ef0d10b5cc29d16c062afce6788 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 6 Apr 2023 14:08:00 +0300 Subject: [PATCH 100/296] test(Node): Add traversal() tests --- .../kotlin/org/tree/binaryTree/NodeTest.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index 082ed0c4..deb337ff 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -1,4 +1,44 @@ package org.tree.binaryTree +import org.junit.jupiter.api.* + class NodeTest { + var root: Node? = null + + @DisplayName("Traversal tests") + @Nested + inner class TraversalTests { + @BeforeEach + fun init() { + root = generateNodeTree( + listOf( + 40, + 20, 60, + 15, null, null, 67, + ) + ) + } + + @Test + fun preorderTraversalTest() { + val act = root?.traversal(TemplateNode.Traversal.PREORDER) + val exp = listOf(40, 20, 15, 60, 67) + Assertions.assertEquals(exp, act) + } + + @Test + fun inorderTraversalTest() { + val act = root?.traversal(TemplateNode.Traversal.INORDER) + val exp = listOf(15, 20, 40, 60, 67) + Assertions.assertEquals(exp, act) + } + + @Test + fun postorderTraversalTest() { + val act = root?.traversal(TemplateNode.Traversal.POSTORDER) + val exp = listOf(15, 20, 67, 60, 40) + Assertions.assertEquals(exp, act) + } + + } } From 6b37b45631c5729c5bc72705362fd79c2c876950 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 6 Apr 2023 20:46:36 +0300 Subject: [PATCH 101/296] test(Node): Add getNonNullChild() tests --- .../kotlin/org/tree/binaryTree/NodeTest.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index deb337ff..d9863987 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -41,4 +41,58 @@ class NodeTest { } } + + @DisplayName("Get non null child tests") + @Nested + inner class GetNonNullChildTests { + + @Test + fun leftChildrenTest() { + root = generateNodeTree( + listOf( + 40, + 20, null + ) + ) + val act = root?.getNonNullChild() + Assertions.assertEquals(root?.left, act) + } + + @Test + fun rightChildrenTest() { + root = generateNodeTree( + listOf( + 40, + null, 60 + ) + ) + val act = root?.getNonNullChild() + Assertions.assertEquals(root?.right, act) + } + + @Test + fun noChildrenTest() { + root = generateNodeTree( + listOf( + 40, + null, null + ) + ) + val act = root?.getNonNullChild() + Assertions.assertEquals(null, act) + } + + @Test + fun twoChildrenTest() { + root = generateNodeTree( + listOf( + 40, + 20, 60 + ) + ) + val act = root?.getNonNullChild() + Assertions.assertNotEquals(null, act) + } + + } } From 10710c320f8cf8335a203c5be4a0a4047c52d612 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 6 Apr 2023 20:47:42 +0300 Subject: [PATCH 102/296] test(Node): Add countNullChildren() tests --- .../kotlin/org/tree/binaryTree/NodeTest.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index d9863987..7d5cc180 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -95,4 +95,58 @@ class NodeTest { } } + + @DisplayName("Count null children tests") + @Nested + inner class CountNullChildrenTests { + + @Test + fun leftChildrenTest() { + root = generateNodeTree( + listOf( + 40, + 20, null + ) + ) + val act = root?.countNullChildren() + Assertions.assertEquals(1, act) + } + + @Test + fun rightChildrenTest() { + root = generateNodeTree( + listOf( + 40, + null, 60 + ) + ) + val act = root?.countNullChildren() + Assertions.assertEquals(1, act) + } + + @Test + fun noChildrenTest() { + root = generateNodeTree( + listOf( + 40, + null, null + ) + ) + val act = root?.countNullChildren() + Assertions.assertEquals(2, act) + } + + @Test + fun twoChildrenTest() { + root = generateNodeTree( + listOf( + 40, + 20, 60 + ) + ) + val act = root?.countNullChildren() + Assertions.assertEquals(0, act) + } + + } } From c93f4743033650bb4a0c4e9358569f207679e264 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 15 Apr 2023 12:31:18 +0300 Subject: [PATCH 103/296] refactor: Move NodeTest to hamcrest --- .../kotlin/org/tree/binaryTree/NodeTest.kt | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index 7d5cc180..b6d2425c 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -1,5 +1,7 @@ package org.tree.binaryTree +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers import org.junit.jupiter.api.* class NodeTest { @@ -23,21 +25,21 @@ class NodeTest { fun preorderTraversalTest() { val act = root?.traversal(TemplateNode.Traversal.PREORDER) val exp = listOf(40, 20, 15, 60, 67) - Assertions.assertEquals(exp, act) + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test fun inorderTraversalTest() { val act = root?.traversal(TemplateNode.Traversal.INORDER) val exp = listOf(15, 20, 40, 60, 67) - Assertions.assertEquals(exp, act) + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test fun postorderTraversalTest() { val act = root?.traversal(TemplateNode.Traversal.POSTORDER) val exp = listOf(15, 20, 67, 60, 40) - Assertions.assertEquals(exp, act) + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } } @@ -55,7 +57,8 @@ class NodeTest { ) ) val act = root?.getNonNullChild() - Assertions.assertEquals(root?.left, act) + val exp = root?.left + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test @@ -67,7 +70,8 @@ class NodeTest { ) ) val act = root?.getNonNullChild() - Assertions.assertEquals(root?.right, act) + val exp = root?.right + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test @@ -79,7 +83,8 @@ class NodeTest { ) ) val act = root?.getNonNullChild() - Assertions.assertEquals(null, act) + val exp = null + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test @@ -91,7 +96,7 @@ class NodeTest { ) ) val act = root?.getNonNullChild() - Assertions.assertNotEquals(null, act) + MatcherAssert.assertThat(act, Matchers.anyOf(Matchers.equalTo(root?.left), Matchers.equalTo(root?.right))) } } @@ -109,7 +114,8 @@ class NodeTest { ) ) val act = root?.countNullChildren() - Assertions.assertEquals(1, act) + val exp = 1 + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test @@ -121,7 +127,8 @@ class NodeTest { ) ) val act = root?.countNullChildren() - Assertions.assertEquals(1, act) + val exp = 1 + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test @@ -133,7 +140,8 @@ class NodeTest { ) ) val act = root?.countNullChildren() - Assertions.assertEquals(2, act) + val exp = 2 + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test @@ -145,7 +153,8 @@ class NodeTest { ) ) val act = root?.countNullChildren() - Assertions.assertEquals(0, act) + val exp = 0 + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } } From 9a20cf3c82e5f36615954a4508cb9dcf129edea1 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 15 Apr 2023 14:19:38 +0300 Subject: [PATCH 104/296] test: Update import in NodeTest --- binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index b6d2425c..1c502dc2 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -3,6 +3,7 @@ package org.tree.binaryTree import org.hamcrest.MatcherAssert import org.hamcrest.Matchers import org.junit.jupiter.api.* +import org.tree.binaryTree.templates.TemplateNode class NodeTest { var root: Node? = null From f5f9e4f8f992d79d83d9de6cf9a6641d5f4d2fc2 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Mon, 17 Apr 2023 10:04:03 +0300 Subject: [PATCH 105/296] test(AVLTree): Add AVL tree check --- .../test/kotlin/org/tree/binaryTree/Util.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt index dc7e8803..9d343cb1 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -4,9 +4,12 @@ import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.* import org.tree.binaryTree.templates.TemplateBSTree import org.tree.binaryTree.templates.TemplateNode +import org.tree.binaryTree.trees.AVLTree import org.tree.binaryTree.trees.RBTree import java.util.* +import kotlin.math.abs import kotlin.math.log2 +import kotlin.math.max // Generate @@ -114,3 +117,34 @@ fun > checkRBTree( checkRBTreeNode(tree.root, null) } +private fun > checkHeight(curNode: AVLNode?): Int{ + if (curNode == null) { + return 0 + } + val leftHeight = checkHeight(curNode.left) + val rightHeight = checkHeight(curNode.right) + return 1 + max(leftHeight, rightHeight) +} + +private fun > checkAVLTreeNode(node: AVLNode?) { + if (node == null) { + return + } + + val leftHeight = checkHeight(node.left) + val rightHeight = checkHeight(node.right) + + assertThat(abs(leftHeight - rightHeight), lessThanOrEqualTo(1)) + + checkAVLTreeNode(node.left) + checkAVLTreeNode(node.right) + +} + +fun > checkAVLTree( + tree: AVLTree, + contain: Array +) { + assertThat(tree.traversal(TemplateNode.Traversal.INORDER), containsInAnyOrder(*contain)) + checkAVLTreeNode(tree.root) +} From 1cf99bb9e1833f2a337da1a2ba145fdb9e1443cd Mon Sep 17 00:00:00 2001 From: Anna-er Date: Mon, 17 Apr 2023 10:28:07 +0300 Subject: [PATCH 106/296] test(AVLTree): Add remove() and insert() tests --- .../kotlin/org/tree/binaryTree/AVLTreeTest.kt | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt new file mode 100644 index 00000000..c9bdf4e0 --- /dev/null +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt @@ -0,0 +1,108 @@ +package org.tree.binaryTree + +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.tree.binaryTree.trees.AVLTree +import java.util.stream.Stream +import kotlin.random.Random + +class AVLTreeTest { + @DisplayName("AVLTree.insert() tests") + class InsertTests { + @ParameterizedTest(name = "[{index}]: insertCount = {1}, seed = {0}") + @MethodSource("testInsertArgs") + fun testInsert(seed: Long, insertCount: Int) { + val tree = AVLTree() + val values = mutableSetOf() + val randomizer = Random(seed) + + for (i in 1..insertCount) { + val newVal = randomizer.nextInt() + val exp = values.add(newVal) + val act = tree.insert(newVal) + + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) + checkAVLTree(tree, values.toTypedArray()) + } + + + } + + companion object { + @JvmStatic + fun testInsertArgs(): Stream { + return Stream.of( + genArguments(0xdeadbeef), + genArguments(0xabacaba, 10), + genArguments(42), + genArguments(13), + genArguments(0xcafe), + genArguments(1337), + ) + } + + private fun genArguments(seed: Long, insertCount: Int = 1000): Arguments { + return Arguments.of(seed, insertCount) + } + } + + } + + + @DisplayName("AVLTree.remove() tests") + class RemoveTests { + @ParameterizedTest(name = "[{index}]: treeSize = {1}, removeCount = {2}, seed = {0}") + @MethodSource("testRemoveArgs") + fun testRemove(seed: Long, treeSize: Int, removeCount: Int) { + val tree = AVLTree() + val values = mutableSetOf() + val randomizer = Random(seed) + for (i in 1..treeSize) { + val newVal = randomizer.nextInt() + values.add(newVal) + tree.insert(newVal) + } + + for (i in 1..removeCount * 2) { + val curVal = if ((i % 2 != 0) and (values.isNotEmpty())) { + values.random(randomizer) + } else { + randomizer.nextInt() + } + val exp = values.remove(curVal) + val act = tree.remove(curVal) + + + MatcherAssert.assertThat(act, Matchers.equalTo(exp)) + checkAVLTree(tree, values.toTypedArray()) + } + } + + companion object { + @JvmStatic + fun testRemoveArgs(): Stream { + return Stream.of( + genArguments(100), + genArguments(0xdeadbeef, 1000, 1050), + genArguments(0xdeadbeef, 1050, 1000), + genArguments(0xdeadbeef, 10, 5), + genArguments(0xdeadbeef, 0, 1), + genArguments(42), + genArguments(13), + genArguments(0xcafe), + genArguments(1337), + ) + } + + private fun genArguments(seed: Long, treeSize: Int = 1000, removeCount: Int = 1000): Arguments { + return Arguments.of(seed, treeSize, removeCount) + } + } + + } + +} From fcc9c419f9e39d9217aeeace05e7dd2c7db4fe4a Mon Sep 17 00:00:00 2001 From: Anna-er Date: Mon, 17 Apr 2023 10:30:48 +0300 Subject: [PATCH 107/296] fix(TemplateBalanceBSTree): Fix remove() method --- .../org/tree/binaryTree/templates/TemplateBalanceBSTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index d35c4a5b..d5b30095 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -90,11 +90,11 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode Date: Mon, 17 Apr 2023 10:33:49 +0300 Subject: [PATCH 108/296] fix(AVLTree): Fix balance() and bfactor() methods --- .../src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt index 310b0956..670b8ccc 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt @@ -10,6 +10,9 @@ class AVLTree> : TemplateBalanceBSTree>() { } private fun bfactor(avlNode: AVLNode?): Int { + fixheight(avlNode) + fixheight(avlNode?.left) + fixheight(avlNode?.right) return if (avlNode != null) { height(avlNode.right) - height(avlNode.left) } else { @@ -44,12 +47,12 @@ class AVLTree> : TemplateBalanceBSTree>() { } private fun balanceNode(curNode: AVLNode, parentNode: AVLNode?) { - fixheight(curNode) if (bfactor(curNode) == 2) { if (bfactor(curNode.right) < 0) { curNode.right?.let { rotateRight(it, curNode) } } rotateLeft(curNode, parentNode) + return } if (bfactor(curNode) == -2) { @@ -57,6 +60,7 @@ class AVLTree> : TemplateBalanceBSTree>() { curNode.left?.let { rotateLeft(it, curNode) } } rotateRight(curNode, parentNode) + return } } From 30e39c450235b1b4eea218ca53334d09facd9210 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 18 Apr 2023 00:53:53 +0300 Subject: [PATCH 109/296] ci: add additional os for build --- .github/workflows/actions.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 6b913763..92daa342 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -4,7 +4,10 @@ on: push: jobs: build-gradle-project: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + runs-on: ${{ matrix.os }} steps: - name: Checkout project sources uses: actions/checkout@v3 From fc527329d2202b21106dd0620af0e5d85e1e9cc9 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 18 Apr 2023 01:08:46 +0300 Subject: [PATCH 110/296] ci: Add os name to reports --- .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 92daa342..01222545 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -20,7 +20,7 @@ jobs: if: success() || failure() uses: actions/upload-artifact@v3 with: - name: library-reports + name: library-reports (${{ matrix.os }}) path: binaryTree/build/reports/ if-no-files-found: ignore @@ -28,6 +28,6 @@ jobs: if: success() || failure() uses: actions/upload-artifact@v3 with: - name: app-reports + name: app-reports (${{ matrix.os }}) path: app/build/reports/ if-no-files-found: ignore From 8eaf918bc763bf700416c27bd75b20f906e4c517 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:37:54 +0300 Subject: [PATCH 111/296] feat: Add neo4j in gradle --- app/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 37cd884b..af88893b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,8 @@ plugins { dependencies { implementation(project(":binaryTree")) + + implementation("org.neo4j.driver", "neo4j-java-driver", "5.7.0") } application { From 584ee533406e0cb0103b75bb2d1840041c3f994f Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:38:45 +0300 Subject: [PATCH 112/296] feat: Add sketch of NodeView class --- .../main/kotlin/org/tree/app/view/NodeView.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/view/NodeView.kt diff --git a/app/src/main/kotlin/org/tree/app/view/NodeView.kt b/app/src/main/kotlin/org/tree/app/view/NodeView.kt new file mode 100644 index 00000000..41e0cdaf --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/NodeView.kt @@ -0,0 +1,23 @@ +package org.tree.app.view + +import org.tree.binaryTree.KVP +import org.tree.binaryTree.templates.TemplateNode + +class NodeView, NODE_T>> { // it is just sketch for import/export + var node: NODE_T + var x: Double = 0.0 + var y: Double = 0.0 + var l: NodeView? = null + var r: NodeView? = null + //var colour: String = "#FFFFFF" + + constructor(nd: NODE_T) { + node = nd + node.left?.let { + l = NodeView(it) + } + node.right?.let { + r = NodeView(it) + } + } +} From 161a43dfcc56bb1e6ae86a5efb7dc7b8b3ca6ce2 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:41:50 +0300 Subject: [PATCH 113/296] feat: Init Neo4jIO class --- .../main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt new file mode 100644 index 00000000..511e66d0 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -0,0 +1,11 @@ +package org.tree.app.controller.io + +import org.neo4j.driver.Driver +import java.io.Closeable + +class Neo4jIO() : Closeable { + private var driver: Driver? = null + override fun close() { + driver?.close() + } +} From 6af308362047526181e210942f1a9c65d9b10567 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:42:53 +0300 Subject: [PATCH 114/296] feat(Neo4jIO): Add open() fun --- .../kotlin/org/tree/app/controller/io/Neo4jIO.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 511e66d0..2df001b1 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -1,10 +1,25 @@ package org.tree.app.controller.io +import org.neo4j.driver.AuthTokens import org.neo4j.driver.Driver +import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.exceptions.SessionExpiredException import java.io.Closeable +import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null + + fun open(uri: String, username: String, password: String) { + try { + driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password)) + } catch (ex: IllegalArgumentException) { + throw IOException("Wrong URI", ex) + } catch (ex: SessionExpiredException) { + throw IOException("Session failed, try restarting the app", ex) + } + } + override fun close() { driver?.close() } From ea8fe79ebd3a9d53513cdddda00d927d82f40cc5 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:44:34 +0300 Subject: [PATCH 115/296] feat(Neo4jIO): Add exportRBNode() fun --- .../org/tree/app/controller/io/Neo4jIO.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 2df001b1..28a9525d 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -3,13 +3,55 @@ package org.tree.app.controller.io import org.neo4j.driver.AuthTokens import org.neo4j.driver.Driver import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.TransactionContext import org.neo4j.driver.exceptions.SessionExpiredException +import org.tree.app.view.NodeView +import org.tree.binaryTree.KVP +import org.tree.binaryTree.RBNode import java.io.Closeable import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null + private fun exportRBNode( + nodeView: NodeView>>, + tx: TransactionContext, + isRoot: Boolean = false + ) { + val rootCase = if (isRoot) { + ":ROOT" + } else { + "" + } + with(nodeView) { + tx.run( + "MERGE (n: RBNode" + rootCase + "{key : \"${node.elem.key}\", " + + "value: \"${node.elem.v ?: ""}\", " + + "x: $x, y: $y, " + + "isBlack: ${node.col == RBNode.Colour.BLACK}})" + ) + l?.let { + exportRBNode(it, tx) + tx.run( + "MATCH" + + " (p:RBNode {key: \"${node.elem.key}\"}), " + + " (c:RBNode {key: \"${it.node.elem.key}\"}) " + + "MERGE (p)-[:LEFT_CHILD]->(c)" + ) + } + r?.let { + exportRBNode(it, tx) + tx.run( + "MATCH" + + " (p:RBNode {key: \"${node.elem.key}\"}), " + + " (c:RBNode {key: \"${it.node.elem.key}\"}) " + + "MERGE (p)-[:RIGHT_CHILD]->(c)" + ) + } + } + } + fun open(uri: String, username: String, password: String) { try { driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password)) From ffdae6ba498098afa9399ca6e6dbe1cba9e5786c Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:45:25 +0300 Subject: [PATCH 116/296] feat(Neo4jIO): Add importRBNode() fun --- .../org/tree/app/controller/io/Neo4jIO.kt | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 28a9525d..0925efa6 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -1,10 +1,8 @@ package org.tree.app.controller.io -import org.neo4j.driver.AuthTokens -import org.neo4j.driver.Driver -import org.neo4j.driver.GraphDatabase -import org.neo4j.driver.TransactionContext +import org.neo4j.driver.* import org.neo4j.driver.exceptions.SessionExpiredException +import org.neo4j.driver.exceptions.value.Uncoercible import org.tree.app.view.NodeView import org.tree.binaryTree.KVP import org.tree.binaryTree.RBNode @@ -52,6 +50,73 @@ class Neo4jIO() : Closeable { } } + private fun importRBNode(key: String?, tx: TransactionContext): NodeView>> { + val matchStr = if (key == null) { + "MATCH (p: ROOT) " + } else { + "MATCH (p: RBNode {key: \"${key}\"})" + } + val ret = tx.run( + matchStr + + "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + + "OPTIONAL MATCH (p)-[:RIGHT_CHILD]->(r: RBNode) " + + "RETURN p.x AS x, p.y AS y, p.isBlack AS isBlack, p.key AS key, p.value AS value, " + + " l.key AS lKey, r.key AS rKey" + ) + val rec = ret.next() + val res = parseRBNode(rec) + if (!(rec["lKey"].isNull)) { + val lKey: String + try { + lKey = rec["lKey"].asString() + } catch (ex: Uncoercible) { + throw IOException("Invalid nodes label in the database", ex) + } + res.l = importRBNode(lKey, tx) + res.l?.let { + res.node.left = it.node + it.node.parent = res.node + } + } + if (!(rec["rKey"].isNull)) { + val rKey: String + try { + rKey = rec["rKey"].asString() + } catch (ex: Uncoercible) { + throw IOException("Invalid nodes label in the database", ex) + } + res.r = importRBNode(rKey, tx) + res.r?.let { + res.node.right = it.node + it.node.parent = res.node + } + } + return res + } + + private fun parseRBNode(rec: Record): NodeView>> { + val res: NodeView>> + + try { + val key = rec["key"].asString() + val value = rec["value"].asString() + res = NodeView(RBNode(null, KVP(key, value))) + + res.x = rec["x"].asDouble() + res.y = rec["y"].asDouble() + + val isBlack = rec["isBlack"].asBoolean() + res.node.col = if (isBlack) { + RBNode.Colour.BLACK + } else { + RBNode.Colour.RED + } + } catch (ex: Uncoercible) { + throw IOException("Invalid nodes label in the database", ex) + } + return res + } + fun open(uri: String, username: String, password: String) { try { driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password)) From dae577fa5cc0b5a80f60f6e19804ad0a805d8b09 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:48:55 +0300 Subject: [PATCH 117/296] feat(Neo4jIO): Add sketch of exportRBTree() --- .../kotlin/org/tree/app/controller/io/Neo4jIO.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 0925efa6..5f8d8ee8 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -12,6 +12,19 @@ import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null + fun exportRBTree(root: NodeView>>) { // when we have treeView, fun will be rewritten + val session = driver?.session() ?: throw IOException("Driver is not open") + session.executeWrite { tx -> + cleanDataBase(tx) + exportRBNode(root, tx, true) + } + session.close() + } + + private fun cleanDataBase(tx: TransactionContext) { + tx.run("MATCH (n: RBNode) DETACH DELETE n") + } + private fun exportRBNode( nodeView: NodeView>>, tx: TransactionContext, From 28c2b2511f2dddc21f61984fef32dafeb2434698 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 16 Apr 2023 10:50:09 +0300 Subject: [PATCH 118/296] feat(Neo4jIO): Add sketch of importRBTree() --- .../main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 5f8d8ee8..70878c2d 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -21,6 +21,15 @@ class Neo4jIO() : Closeable { session.close() } + fun importRBTree(): NodeView>> { // when we have treeView, fun will be rewritten + val session = driver?.session() ?: throw IOException("Driver is not open") + var res: NodeView>> = session.executeRead { tx -> + importRBNode(null, tx) + } + session.close() + return res + } + private fun cleanDataBase(tx: TransactionContext) { tx.run("MATCH (n: RBNode) DETACH DELETE n") } From e010e3630e567f9bb9e363429d7c3d8501e9fa13 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 17 Apr 2023 21:34:49 +0300 Subject: [PATCH 119/296] feat(Neo4jIO): Improve exportRBTree() --- .../org/tree/app/controller/io/Neo4jIO.kt | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 70878c2d..b7e51bee 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -16,11 +16,22 @@ class Neo4jIO() : Closeable { val session = driver?.session() ?: throw IOException("Driver is not open") session.executeWrite { tx -> cleanDataBase(tx) - exportRBNode(root, tx, true) + tx.run(genExportRBNodes(root)) + tx.run( + "MATCH (p: RBNode) " + + "MATCH (l: RBNode {key: p.lkey}) " + + "CREATE (p)-[:LEFT_CHILD]->(l) " + ) // connect parent and left child + tx.run( + "MATCH (p: RBNode) " + + "MATCH (r: RBNode {key: p.rkey}) " + + "CREATE (p)-[:RIGHT_CHILD]->(r) " + )// connect parent and right child } session.close() } + fun importRBTree(): NodeView>> { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") var res: NodeView>> = session.executeRead { tx -> @@ -34,9 +45,15 @@ class Neo4jIO() : Closeable { tx.run("MATCH (n: RBNode) DETACH DELETE n") } - private fun exportRBNode( + private fun genExportRBNodes(root: NodeView>>): String { + val sb = StringBuilder() + traverseExportRBNode(sb, root, true) + return sb.toString() + } + + private fun traverseExportRBNode( + sb: StringBuilder, nodeView: NodeView>>, - tx: TransactionContext, isRoot: Boolean = false ) { val rootCase = if (isRoot) { @@ -45,29 +62,19 @@ class Neo4jIO() : Closeable { "" } with(nodeView) { - tx.run( - "MERGE (n: RBNode" + rootCase + "{key : \"${node.elem.key}\", " + + sb.append( + "CREATE (:RBNode" + rootCase + "{key : \"${node.elem.key}\", " + "value: \"${node.elem.v ?: ""}\", " + "x: $x, y: $y, " + - "isBlack: ${node.col == RBNode.Colour.BLACK}})" + "isBlack: ${node.col == RBNode.Colour.BLACK}, " + + "lkey: \"${l?.node?.elem?.key ?: ""}\", " + + "rkey: \"${r?.node?.elem?.key ?: ""}\"}) " ) l?.let { - exportRBNode(it, tx) - tx.run( - "MATCH" + - " (p:RBNode {key: \"${node.elem.key}\"}), " + - " (c:RBNode {key: \"${it.node.elem.key}\"}) " + - "MERGE (p)-[:LEFT_CHILD]->(c)" - ) + traverseExportRBNode(sb, it) } r?.let { - exportRBNode(it, tx) - tx.run( - "MATCH" + - " (p:RBNode {key: \"${node.elem.key}\"}), " + - " (c:RBNode {key: \"${it.node.elem.key}\"}) " + - "MERGE (p)-[:RIGHT_CHILD]->(c)" - ) + traverseExportRBNode(sb, it) } } } From 7b42d71bf19d188346db805edc77a80bb4913bf1 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 17 Apr 2023 23:32:04 +0300 Subject: [PATCH 120/296] feat(Neo4jIO/export): Remove useless data --- .../kotlin/org/tree/app/controller/io/Neo4jIO.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index b7e51bee..4d16a7e8 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -20,12 +20,14 @@ class Neo4jIO() : Closeable { tx.run( "MATCH (p: RBNode) " + "MATCH (l: RBNode {key: p.lkey}) " + - "CREATE (p)-[:LEFT_CHILD]->(l) " + "CREATE (p)-[:LEFT_CHILD]->(l) " + + "REMOVE p.lkey" ) // connect parent and left child tx.run( "MATCH (p: RBNode) " + "MATCH (r: RBNode {key: p.rkey}) " + - "CREATE (p)-[:RIGHT_CHILD]->(r) " + "CREATE (p)-[:RIGHT_CHILD]->(r) " + + "REMOVE p.rkey" )// connect parent and right child } session.close() @@ -47,23 +49,17 @@ class Neo4jIO() : Closeable { private fun genExportRBNodes(root: NodeView>>): String { val sb = StringBuilder() - traverseExportRBNode(sb, root, true) + traverseExportRBNode(sb, root) return sb.toString() } private fun traverseExportRBNode( sb: StringBuilder, nodeView: NodeView>>, - isRoot: Boolean = false ) { - val rootCase = if (isRoot) { - ":ROOT" - } else { - "" - } with(nodeView) { sb.append( - "CREATE (:RBNode" + rootCase + "{key : \"${node.elem.key}\", " + + "CREATE (:RBNode {key : \"${node.elem.key}\", " + "value: \"${node.elem.v ?: ""}\", " + "x: $x, y: $y, " + "isBlack: ${node.col == RBNode.Colour.BLACK}, " + From c7a046e97b1983e70745598da6b3d4931de743e6 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 17 Apr 2023 23:34:43 +0300 Subject: [PATCH 121/296] feat(Neo4jIO): Improve importRBTree() --- .../org/tree/app/controller/io/Neo4jIO.kt | 116 ++++++++++-------- 1 file changed, 65 insertions(+), 51 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 4d16a7e8..98181c8f 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -36,8 +36,8 @@ class Neo4jIO() : Closeable { fun importRBTree(): NodeView>> { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") - var res: NodeView>> = session.executeRead { tx -> - importRBNode(null, tx) + val res: NodeView>> = session.executeRead { tx -> + importRBNode(tx) } session.close() return res @@ -75,71 +75,85 @@ class Neo4jIO() : Closeable { } } - private fun importRBNode(key: String?, tx: TransactionContext): NodeView>> { - val matchStr = if (key == null) { - "MATCH (p: ROOT) " - } else { - "MATCH (p: RBNode {key: \"${key}\"})" - } + private fun importRBNode(tx: TransactionContext): NodeView>> { + val ret = tx.run( - matchStr + + "MATCH (p: RBNode)" + "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + "OPTIONAL MATCH (p)-[:RIGHT_CHILD]->(r: RBNode) " + "RETURN p.x AS x, p.y AS y, p.isBlack AS isBlack, p.key AS key, p.value AS value, " + " l.key AS lKey, r.key AS rKey" ) - val rec = ret.next() - val res = parseRBNode(rec) - if (!(rec["lKey"].isNull)) { - val lKey: String + + return parseRBNode(ret) + } + + private class NodeAndKeys( + val nv: NodeView>>, + val lkey: String?, + val rkey: String? + ) + + private fun parseRBNode(ret: Result): NodeView>> { + val st = mutableMapOf() + for (rec in ret) { try { - lKey = rec["lKey"].asString() + val key = rec["key"].asString() + val value = rec["value"].asString() + val nv = NodeView(RBNode(null, KVP(key, value))) + + nv.x = rec["x"].asDouble() + nv.y = rec["y"].asDouble() + + val isBlack = rec["isBlack"].asBoolean() + nv.node.col = if (isBlack) { + RBNode.Colour.BLACK + } else { + RBNode.Colour.RED + } + + val lkey = if (rec["lKey"].isNull) { + null + } else { + rec["lKey"].asString() + } + val rkey = if (rec["rKey"].isNull) { + null + } else { + rec["rKey"].asString() + } + + st[key] = NodeAndKeys(nv, lkey, rkey) } catch (ex: Uncoercible) { throw IOException("Invalid nodes label in the database", ex) } - res.l = importRBNode(lKey, tx) - res.l?.let { - res.node.left = it.node - it.node.parent = res.node - } } - if (!(rec["rKey"].isNull)) { - val rKey: String - try { - rKey = rec["rKey"].asString() - } catch (ex: Uncoercible) { - throw IOException("Invalid nodes label in the database", ex) + val a = st.values.toTypedArray() + for (nk in a) { + nk.lkey?.let { + nk.nv.l = st[it]?.nv + st.remove(it) } - res.r = importRBNode(rKey, tx) - res.r?.let { - res.node.right = it.node - it.node.parent = res.node + nk.nv.l?.let { + nk.nv.node.left = it.node + it.node.parent = nk.nv.node } - } - return res - } - private fun parseRBNode(rec: Record): NodeView>> { - val res: NodeView>> - - try { - val key = rec["key"].asString() - val value = rec["value"].asString() - res = NodeView(RBNode(null, KVP(key, value))) - - res.x = rec["x"].asDouble() - res.y = rec["y"].asDouble() - - val isBlack = rec["isBlack"].asBoolean() - res.node.col = if (isBlack) { - RBNode.Colour.BLACK - } else { - RBNode.Colour.RED + nk.rkey?.let { + nk.nv.r = st[it]?.nv + st.remove(it) + } + nk.nv.r?.let { + nk.nv.node.right = it.node + it.node.parent = nk.nv.node } - } catch (ex: Uncoercible) { - throw IOException("Invalid nodes label in the database", ex) } - return res + if (st.values.size != 1) { + throw IOException("Found ${st.values.size} nodes without parents in database, expected 1") + } + val root = st.values.first().nv + println(root.node.elem.key) + return root } fun open(uri: String, username: String, password: String) { From 373f71d1a8a44bc281a1b66c4a7f724d502229a5 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 18 Apr 2023 00:09:28 +0300 Subject: [PATCH 122/296] refactor(Neo4jIO): Change names, add comments --- .../org/tree/app/controller/io/Neo4jIO.kt | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 98181c8f..b133d8f7 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -37,7 +37,7 @@ class Neo4jIO() : Closeable { fun importRBTree(): NodeView>> { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") val res: NodeView>> = session.executeRead { tx -> - importRBNode(tx) + importRBNodes(tx) } session.close() return res @@ -65,7 +65,7 @@ class Neo4jIO() : Closeable { "isBlack: ${node.col == RBNode.Colour.BLACK}, " + "lkey: \"${l?.node?.elem?.key ?: ""}\", " + "rkey: \"${r?.node?.elem?.key ?: ""}\"}) " - ) + ) // save node (lkey and rkey are needed for connection later) l?.let { traverseExportRBNode(sb, it) } @@ -75,17 +75,15 @@ class Neo4jIO() : Closeable { } } - private fun importRBNode(tx: TransactionContext): NodeView>> { - - val ret = tx.run( + private fun importRBNodes(tx: TransactionContext): NodeView>> { + val nodeAndKeysRecords = tx.run( "MATCH (p: RBNode)" + "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + "OPTIONAL MATCH (p)-[:RIGHT_CHILD]->(r: RBNode) " + "RETURN p.x AS x, p.y AS y, p.isBlack AS isBlack, p.key AS key, p.value AS value, " + " l.key AS lKey, r.key AS rKey" - ) - - return parseRBNode(ret) + ) // for all nodes get their properties + keys of their children + return parseRBNodes(nodeAndKeysRecords) } private class NodeAndKeys( @@ -94,45 +92,45 @@ class Neo4jIO() : Closeable { val rkey: String? ) - private fun parseRBNode(ret: Result): NodeView>> { - val st = mutableMapOf() - for (rec in ret) { + private fun parseRBNodes(nodeAndKeysRecords: Result): NodeView>> { + val key2nk = mutableMapOf() + for (nkRecord in nodeAndKeysRecords) { try { - val key = rec["key"].asString() - val value = rec["value"].asString() + val key = nkRecord["key"].asString() + val value = nkRecord["value"].asString() val nv = NodeView(RBNode(null, KVP(key, value))) - nv.x = rec["x"].asDouble() - nv.y = rec["y"].asDouble() + nv.x = nkRecord["x"].asDouble() + nv.y = nkRecord["y"].asDouble() - val isBlack = rec["isBlack"].asBoolean() + val isBlack = nkRecord["isBlack"].asBoolean() nv.node.col = if (isBlack) { RBNode.Colour.BLACK } else { RBNode.Colour.RED } - val lkey = if (rec["lKey"].isNull) { + val lkey = if (nkRecord["lKey"].isNull) { null } else { - rec["lKey"].asString() + nkRecord["lKey"].asString() } - val rkey = if (rec["rKey"].isNull) { + val rkey = if (nkRecord["rKey"].isNull) { null } else { - rec["rKey"].asString() + nkRecord["rKey"].asString() } - st[key] = NodeAndKeys(nv, lkey, rkey) + key2nk[key] = NodeAndKeys(nv, lkey, rkey) } catch (ex: Uncoercible) { throw IOException("Invalid nodes label in the database", ex) } } - val a = st.values.toTypedArray() - for (nk in a) { + val nks = key2nk.values.toTypedArray() + for (nk in nks) { nk.lkey?.let { - nk.nv.l = st[it]?.nv - st.remove(it) + nk.nv.l = key2nk[it]?.nv + key2nk.remove(it) } nk.nv.l?.let { nk.nv.node.left = it.node @@ -140,19 +138,18 @@ class Neo4jIO() : Closeable { } nk.rkey?.let { - nk.nv.r = st[it]?.nv - st.remove(it) + nk.nv.r = key2nk[it]?.nv + key2nk.remove(it) } nk.nv.r?.let { nk.nv.node.right = it.node it.node.parent = nk.nv.node } } - if (st.values.size != 1) { - throw IOException("Found ${st.values.size} nodes without parents in database, expected 1") + if (key2nk.values.size != 1) { + throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") } - val root = st.values.first().nv - println(root.node.elem.key) + val root = key2nk.values.first().nv return root } From 46fbe031467e21ce9071d8fd2dfdb245864d9686 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 18 Apr 2023 00:25:59 +0300 Subject: [PATCH 123/296] fix(Neo4jIO): Add case of empty db --- .../kotlin/org/tree/app/controller/io/Neo4jIO.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index b133d8f7..e6f34489 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -34,9 +34,9 @@ class Neo4jIO() : Closeable { } - fun importRBTree(): NodeView>> { // when we have treeView, fun will be rewritten + fun importRBTree(): NodeView>>? { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") - val res: NodeView>> = session.executeRead { tx -> + val res: NodeView>>? = session.executeRead { tx -> importRBNodes(tx) } session.close() @@ -75,7 +75,7 @@ class Neo4jIO() : Closeable { } } - private fun importRBNodes(tx: TransactionContext): NodeView>> { + private fun importRBNodes(tx: TransactionContext): NodeView>>? { val nodeAndKeysRecords = tx.run( "MATCH (p: RBNode)" + "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + @@ -92,7 +92,7 @@ class Neo4jIO() : Closeable { val rkey: String? ) - private fun parseRBNodes(nodeAndKeysRecords: Result): NodeView>> { + private fun parseRBNodes(nodeAndKeysRecords: Result): NodeView>>? { val key2nk = mutableMapOf() for (nkRecord in nodeAndKeysRecords) { try { @@ -127,6 +127,10 @@ class Neo4jIO() : Closeable { } } val nks = key2nk.values.toTypedArray() + if (nks.isEmpty()) { // if nodeAndKeysRecords was empty + return null + } + for (nk in nks) { nk.lkey?.let { nk.nv.l = key2nk[it]?.nv From 07df2d967950115cf875fff19a8194528f7897a8 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 01:22:17 +0300 Subject: [PATCH 124/296] fix: Change key type for NodeView --- app/src/main/kotlin/org/tree/app/view/NodeView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/view/NodeView.kt b/app/src/main/kotlin/org/tree/app/view/NodeView.kt index 41e0cdaf..53e788f7 100644 --- a/app/src/main/kotlin/org/tree/app/view/NodeView.kt +++ b/app/src/main/kotlin/org/tree/app/view/NodeView.kt @@ -3,7 +3,7 @@ package org.tree.app.view import org.tree.binaryTree.KVP import org.tree.binaryTree.templates.TemplateNode -class NodeView, NODE_T>> { // it is just sketch for import/export +class NodeView, NODE_T>> { // it is just sketch for import/export var node: NODE_T var x: Double = 0.0 var y: Double = 0.0 From 0fa07fd4b5c10696d77e22dd2e1b6650f6e46584 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 01:23:46 +0300 Subject: [PATCH 125/296] fix: Update Neo4jIO to new NodeView --- .../org/tree/app/controller/io/Neo4jIO.kt | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index e6f34489..93c0a990 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -12,7 +12,7 @@ import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null - fun exportRBTree(root: NodeView>>) { // when we have treeView, fun will be rewritten + fun exportRBTree(root: NodeView>>) { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") session.executeWrite { tx -> cleanDataBase(tx) @@ -34,9 +34,9 @@ class Neo4jIO() : Closeable { } - fun importRBTree(): NodeView>>? { // when we have treeView, fun will be rewritten + fun importRBTree(): NodeView>>? { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") - val res: NodeView>>? = session.executeRead { tx -> + val res: NodeView>>? = session.executeRead { tx -> importRBNodes(tx) } session.close() @@ -47,7 +47,7 @@ class Neo4jIO() : Closeable { tx.run("MATCH (n: RBNode) DETACH DELETE n") } - private fun genExportRBNodes(root: NodeView>>): String { + private fun genExportRBNodes(root: NodeView>>): String { val sb = StringBuilder() traverseExportRBNode(sb, root) return sb.toString() @@ -55,16 +55,28 @@ class Neo4jIO() : Closeable { private fun traverseExportRBNode( sb: StringBuilder, - nodeView: NodeView>>, + nodeView: NodeView>>, ) { with(nodeView) { + val lkey = l?.node?.elem?.key + val lkeyString = if (lkey != null) { + ", lkey: ${lkey} " + } else { + "" + } + val rkey = r?.node?.elem?.key + val rkeyString = if (rkey != null) { + ", rkey: ${r?.node?.elem?.key}" + } else { + "" + } sb.append( - "CREATE (:RBNode {key : \"${node.elem.key}\", " + + "CREATE (:RBNode {key : ${node.elem.key}, " + "value: \"${node.elem.v ?: ""}\", " + "x: $x, y: $y, " + - "isBlack: ${node.col == RBNode.Colour.BLACK}, " + - "lkey: \"${l?.node?.elem?.key ?: ""}\", " + - "rkey: \"${r?.node?.elem?.key ?: ""}\"}) " + "isBlack: ${node.col == RBNode.Colour.BLACK}" + + lkeyString + rkeyString + + "})" ) // save node (lkey and rkey are needed for connection later) l?.let { traverseExportRBNode(sb, it) @@ -75,7 +87,7 @@ class Neo4jIO() : Closeable { } } - private fun importRBNodes(tx: TransactionContext): NodeView>>? { + private fun importRBNodes(tx: TransactionContext): NodeView>>? { val nodeAndKeysRecords = tx.run( "MATCH (p: RBNode)" + "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + @@ -87,16 +99,16 @@ class Neo4jIO() : Closeable { } private class NodeAndKeys( - val nv: NodeView>>, - val lkey: String?, - val rkey: String? + val nv: NodeView>>, + val lkey: Int?, + val rkey: Int? ) - private fun parseRBNodes(nodeAndKeysRecords: Result): NodeView>>? { - val key2nk = mutableMapOf() + private fun parseRBNodes(nodeAndKeysRecords: Result): NodeView>>? { + val key2nk = mutableMapOf() for (nkRecord in nodeAndKeysRecords) { try { - val key = nkRecord["key"].asString() + val key = nkRecord["key"].asInt() val value = nkRecord["value"].asString() val nv = NodeView(RBNode(null, KVP(key, value))) @@ -113,12 +125,12 @@ class Neo4jIO() : Closeable { val lkey = if (nkRecord["lKey"].isNull) { null } else { - nkRecord["lKey"].asString() + nkRecord["lKey"].asInt() } val rkey = if (nkRecord["rKey"].isNull) { null } else { - nkRecord["rKey"].asString() + nkRecord["rKey"].asInt() } key2nk[key] = NodeAndKeys(nv, lkey, rkey) From 1438937459b91650cf483e471b328fc9bdbc4d1b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 02:12:48 +0300 Subject: [PATCH 126/296] feat(Neo4jIO): Add name arg to exportRBTree and deleteTree --- .../org/tree/app/controller/io/Neo4jIO.kt | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 93c0a990..c4d3d1f3 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -12,23 +12,32 @@ import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null - fun exportRBTree(root: NodeView>>) { // when we have treeView, fun will be rewritten + fun exportRBTree( + root: NodeView>>, + treeName: String = "Tree" + ) { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") session.executeWrite { tx -> - cleanDataBase(tx) + deleteTree(tx, treeName) tx.run(genExportRBNodes(root)) tx.run( "MATCH (p: RBNode) " + - "MATCH (l: RBNode {key: p.lkey}) " + + "MATCH (l: NewNode {key: p.lkey}) " + "CREATE (p)-[:LEFT_CHILD]->(l) " + - "REMOVE p.lkey" + "REMOVE p.lkey, l:NewNode" ) // connect parent and left child tx.run( "MATCH (p: RBNode) " + - "MATCH (r: RBNode {key: p.rkey}) " + + "MATCH (r: NewNode {key: p.rkey}) " + "CREATE (p)-[:RIGHT_CHILD]->(r) " + - "REMOVE p.rkey" + "REMOVE p.rkey, r:NewNode" )// connect parent and right child + + tx.run( + "MATCH (r: NewNode) " + + "CREATE (t: Tree {name: \"$treeName\"})-[:ROOT]->(r) " + + "REMOVE r:NewNode" + ) } session.close() } @@ -43,8 +52,12 @@ class Neo4jIO() : Closeable { return res } - private fun cleanDataBase(tx: TransactionContext) { - tx.run("MATCH (n: RBNode) DETACH DELETE n") + private fun deleteTree(tx: TransactionContext, treeName: String) { + tx.run( + "MATCH (t: Tree {name: \"$treeName\"})" + + "MATCH (t)-[*]->(n:RBNode) " + + "DETACH DELETE t, n" + ) } private fun genExportRBNodes(root: NodeView>>): String { @@ -71,7 +84,7 @@ class Neo4jIO() : Closeable { "" } sb.append( - "CREATE (:RBNode {key : ${node.elem.key}, " + + "CREATE (:RBNode:NewNode {key : ${node.elem.key}, " + "value: \"${node.elem.v ?: ""}\", " + "x: $x, y: $y, " + "isBlack: ${node.col == RBNode.Colour.BLACK}" + From 64028c337c8141717d164acd1c3bab788ef03efc Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 02:19:46 +0300 Subject: [PATCH 127/296] feat(Neo4jIO): Add name arg to importRBTree --- app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index c4d3d1f3..094f2163 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -43,10 +43,10 @@ class Neo4jIO() : Closeable { } - fun importRBTree(): NodeView>>? { // when we have treeView, fun will be rewritten + fun importRBTree(treeName: String = "Tree"): NodeView>>? { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") val res: NodeView>>? = session.executeRead { tx -> - importRBNodes(tx) + importRBNodes(tx, treeName) } session.close() return res @@ -100,9 +100,9 @@ class Neo4jIO() : Closeable { } } - private fun importRBNodes(tx: TransactionContext): NodeView>>? { + private fun importRBNodes(tx: TransactionContext, treeName: String): NodeView>>? { val nodeAndKeysRecords = tx.run( - "MATCH (p: RBNode)" + + "MATCH (:Tree {name: \"$treeName\"})-[*]->(p: RBNode)" + "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + "OPTIONAL MATCH (p)-[:RIGHT_CHILD]->(r: RBNode) " + "RETURN p.x AS x, p.y AS y, p.isBlack AS isBlack, p.key AS key, p.value AS value, " + From 80aea9046dab9b7b955a0f69a4e0b59a374dac85 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 02:22:24 +0300 Subject: [PATCH 128/296] feat(Neo4jIO): Add removeTree fun --- app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 094f2163..a98cdad2 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -52,6 +52,14 @@ class Neo4jIO() : Closeable { return res } + fun removeTree(treeName: String = "Tree") { + val session = driver?.session() ?: throw IOException("Driver is not open") + session.executeWrite { tx -> + deleteTree(tx, treeName) + } + session.close() + } + private fun deleteTree(tx: TransactionContext, treeName: String) { tx.run( "MATCH (t: Tree {name: \"$treeName\"})" + From a03d63b07d7bb5a1f336fd727c6c3d2043bba15d Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 02:36:07 +0300 Subject: [PATCH 129/296] feat(Neo4jIO): Add getTreesNames fun --- .../org/tree/app/controller/io/Neo4jIO.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index a98cdad2..8172b7fd 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -60,6 +60,16 @@ class Neo4jIO() : Closeable { session.close() } + fun getTreesNames(): MutableList { + val session = driver?.session() ?: throw IOException("Driver is not open") + val res: MutableList = session.executeRead { tx -> + val nameRecords = tx.run("MATCH (t: Tree) RETURN t.name AS name") + parseNames(nameRecords) + } + session.close() + return res + } + private fun deleteTree(tx: TransactionContext, treeName: String) { tx.run( "MATCH (t: Tree {name: \"$treeName\"})" + @@ -190,6 +200,19 @@ class Neo4jIO() : Closeable { return root } + private fun parseNames(nameRecords: Result): MutableList { + val res = mutableListOf() + for (nmRecord in nameRecords) { + try { + val name = nmRecord["name"].asString() + res.add(name) + } catch (ex: Uncoercible) { + throw IOException("Invalid tree label in the database", ex) + } + } + return res + } + fun open(uri: String, username: String, password: String) { try { driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password)) From bea6f7672d51488a0f0985501e797e33b5285ce4 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 00:34:43 +0300 Subject: [PATCH 130/296] struct: Add app icon --- app/src/main/resources/icon.png | Bin 0 -> 77661 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/src/main/resources/icon.png diff --git a/app/src/main/resources/icon.png b/app/src/main/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..47e9c3b93ef8591bd4fb4a4399e10dbe689164a3 GIT binary patch literal 77661 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 Date: Wed, 19 Apr 2023 00:35:22 +0300 Subject: [PATCH 131/296] struct(README): Update README --- README.md | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6ca779f9..aef8e5cd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,191 @@ -Trees-2 -============ +logo + +# Trees-2 + +Program for binary tree visualization. + +--- + +## Features + +You can: + +- **Create** 3 different types of search trees: **AVL**, **RedBlack** and **Common binary tree** +- **Save** it with 3 different ways: **Neo4j**, **Plain text** and **SQLite** +- Or **load** your own tree from these "databases" +- Use our [library](#Library) to **implement** binary search trees in your own project + +--- + +## Get started + +### Build + +To build and run this application locally, you'll need Git, Gradle and JDK installed on your computer. From your command +line: + +```bash +# Clone this repository +git clone https://github.com/spbu-coding-2022/trees-2.git + +# Go into the repository +cd trees-2 + +# Build +./gradlew assemble +# use `./gradlew build` if you want to test app + +# Run the app +./gradlew run +``` + +### Neo4j database + +Since to save or download from neo4j, the application needs to connect to an instance of this database. +We will tell you how it can be started quickly. + +- download + archive [\[linux/mac\]](https://neo4j.com/download-thanks/?edition=community&release=5.6.0&flavour=unix) [\[windows\]](https://neo4j.com/download-thanks/?edition=community&release=5.6.0&flavour=winzip) +- extract files from it to a dir `~/neo4j-dir/` +- run `~/neo4j-dir/bin/neo4j console` +- now you can visit http://localhost:7474 in your web browser. +- connect using the username 'neo4j' with default password 'neo4j'. You'll then be prompted to change the password. +- to save/load tree enter this url `bolt://localhost:7687` with correct login and password to app + +If you got trouble during installation or for more +information visit https://neo4j.com/docs/operations-manual/current/installation/ + +--- + +## Databases format specifications + +### Plain text + +TODO: write + +### SQLite + +TODO: write + +### Neo4j + +TODO: write + +--- + +## Library + +[Our library](binaryTree) gives you the opportunity to work with already written +trees (`BinSearchTree`, `AVLTree`, `RBTree`), and write your own based on written templates. + +### Gradle + +Since our library is a separate gradle module, it is enough to import it: + +- copy the module folder to your gradle project +- specify it in the dependencies + +```kotlin +dependencies { + implementation(project(":binaryTree")) +} +``` + +### Examples + +#### Trees + +```kotlin +fun example() { + // create RBTree with int elements + val intTree = RBTree() + + // create AVLTree with string elements + val stringTree = AVLTree() + + // create common BinSearchTree with int keys and string values + val keyValuePairTree = BinSearchTree>() + + intTree.insert(3) // insert new node + intTree.find(3) // find node + intTree.remove(3) // remove node if it exists + + for (i in 0..10) { + stringTree.insert(i.toString()) + } + val traverseList = stringTree.traversal(TemplateNode.Traversal.INORDER) + // get list of nodes' elements at INORDER traverse +} +``` + +#### Implement your tree + +```kotlin +class CoolNode>(v: T, var coolness: Double) : TemplateNode>(v) { + fun sayYourCoolness() { + println("My coolness equal to $coolness cats") + } +} + +class MyCoolTree> : TemplateBSTree>() { + override fun insert(curNode: CoolNode?, obj: T): CoolNode? { + val newNode = CoolNode(obj, 0.0) + val parentNode = insertNode(curNode, newNode) + parentNode?.let { + newNode.coolness = it.coolness * 1.3 + } + return parentNode + } +} +``` + +#### Implement your balance tree + +```kotlin +class MyCoolTree> : TemplateBalanceBSTree>() { + override fun insert(curNode: CoolNode?, obj: T): CoolNode? { + return insertNode(curNode, CoolNode(obj, 0.0)) + } + + override fun balance( + curNode: CoolNode?, + changedChild: BalanceCase.ChangedChild, + operationType: BalanceCase.OpType, + recursive: BalanceCase.Recursive + ) { + when (changedChild) { + BalanceCase.ChangedChild.ROOT -> println("root was changed") + BalanceCase.ChangedChild.LEFT -> println("left child of curNode was changed") + BalanceCase.ChangedChild.RIGHT -> println("right child of curNode was changed") + } + + when (operationType) { + BalanceCase.OpType.REMOVE_0 -> println("remove node with 0 null children") + BalanceCase.OpType.REMOVE_1 -> println("remove node with 1 null child") + BalanceCase.OpType.REMOVE_2 -> println("remove node with 2 null children") + BalanceCase.OpType.INSERT -> println("insert new node") + } + + when (recursive) { + BalanceCase.Recursive.RECURSIVE_CALL -> println("just returning from traverse") + BalanceCase.Recursive.END -> println("this was last iteration, so it can change something") + } + + if (curNode != null) { + curNode.left?.let { + rotateLeft(it, curNode) + // do left rotate on the curNode.left with curNode as parent + } + } + } +} + +``` + +#### More examples + +For more examples you can watch code of our [app](app/src/main/kotlin/org/tree/app) +or [library](binaryTree/src/main/kotlin/org/tree/binaryTree). --- @@ -18,6 +204,4 @@ For more details, see [CONTRIBUTING.md](CONTRIBUTING.md) ## License -> You can check out the full license [here](LICENSE.md) - -This project is licensed under the terms of the **MIT** license. +This project is licensed under the terms of the **MIT** license. See the [LICENSE](LICENSE.md) for more information. From 3fbb9f5117e30d76285a47d56892177f0fe36d12 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 03:40:28 +0300 Subject: [PATCH 132/296] struct(README): Fix typos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aef8e5cd..00075c2c 100644 --- a/README.md +++ b/README.md @@ -160,10 +160,10 @@ class MyCoolTree> : TemplateBalanceBSTree>() { } when (operationType) { - BalanceCase.OpType.REMOVE_0 -> println("remove node with 0 null children") - BalanceCase.OpType.REMOVE_1 -> println("remove node with 1 null child") - BalanceCase.OpType.REMOVE_2 -> println("remove node with 2 null children") - BalanceCase.OpType.INSERT -> println("insert new node") + BalanceCase.OpType.REMOVE_0 -> println("removed node with 0 null children") + BalanceCase.OpType.REMOVE_1 -> println("removed node with 1 null child") + BalanceCase.OpType.REMOVE_2 -> println("removed node with 2 null children") + BalanceCase.OpType.INSERT -> println("inserted new node") } when (recursive) { From 14fa8fc95cfd6661652e2dcf59d8e79606f219a6 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 04:10:51 +0300 Subject: [PATCH 133/296] struct(README): Add neo4j format specifications --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00075c2c..bf81bb32 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,26 @@ TODO: write ### Neo4j -TODO: write +At neo4j database we store **RBTree**. There are 2 types of labels with the following struct: + +- `Tree` + - `name` *string* +- `RBNode` + - `key` *int* + - `value` *string* + - `isBlack` *boolean* - node colour + - `x` *double* - node position at ui + - `y` *double* - node position at ui + +Also, there are 3 types of links: + +- `ROOT`: from **tree**(`Tree`) to its **root**(`RBNode`) +- `LEFT_CHILD`: from **parent**(`RBNode`) to its **left-child**(`RBNode`) +- `RIGHT_CHILD`: from **parent**(`RBNode`) to its **right-child**(`RBNode`) + +Using the `Tree` labels, we can store several trees in the database at once. But their nodes should not intersect +(shouldn't have links between each other). +![neo4j-example](https://user-images.githubusercontent.com/66139162/232936241-963c1213-e1ad-46fd-b134-3c137fa04b45.png) --- From ad474e0c08c0b16f5e77078c5f7ea51a3ba09464 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Wed, 19 Apr 2023 04:19:23 +0300 Subject: [PATCH 134/296] feat: Add json in gradle --- app/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index af88893b..e05a37d1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,12 +4,14 @@ plugins { id("org.tree.kotlin-application-conventions") + kotlin("plugin.serialization") version "1.8.20" } dependencies { implementation(project(":binaryTree")) implementation("org.neo4j.driver", "neo4j-java-driver", "5.7.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") } application { From cfebd77dc4467da32ba85f3288e9f9fc8d3f1340 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Wed, 19 Apr 2023 04:20:54 +0300 Subject: [PATCH 135/296] feat: Init Json class --- app/src/main/kotlin/org/tree/app/controller/io/Json.kt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/controller/io/Json.kt diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt new file mode 100644 index 00000000..98d49473 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -0,0 +1,4 @@ +package org.tree.app.controller.io + +class Json { +} From 7456bac4673df0a985c3eecef0a1865b131642e5 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Wed, 19 Apr 2023 04:24:55 +0300 Subject: [PATCH 136/296] feat(Json): Add JsonAVLNode and JsonAVLTree data class --- .../kotlin/org/tree/app/controller/io/Json.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index 98d49473..d2da12d5 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -1,4 +1,21 @@ package org.tree.app.controller.io +import kotlinx.serialization.Serializable + +@Serializable +private data class JsonAVLNode( + val key: Int, + val value: String?, + val x: Double, + val y: Double, + val height: Int, + val lkey: Int?, + val rkey: Int? +) + +@Serializable +private data class JsonAVLTree( + val AVLTree: Array +) class Json { } From a02b486c7b6cf6df8985b776c4d4232b08d1a23d Mon Sep 17 00:00:00 2001 From: Anna-er Date: Wed, 19 Apr 2023 04:41:58 +0300 Subject: [PATCH 137/296] feat(Json): Add sketch of exportAVLTree() --- .../kotlin/org/tree/app/controller/io/Json.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index d2da12d5..dbc78f54 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -1,6 +1,16 @@ package org.tree.app.controller.io import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.neo4j.driver.exceptions.value.Uncoercible +import org.tree.app.view.NodeView +import org.tree.binaryTree.AVLNode +import org.tree.binaryTree.KVP +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException @Serializable private data class JsonAVLNode( @@ -18,4 +28,47 @@ private data class JsonAVLTree( val AVLTree: Array ) class Json { + val dirPath = "json-saved" + + fun exportAVLTree(root: NodeView>>) { + val sb = StringBuilder() + sb.append("{\"AVLTree\":[") + traverseExportAVLNode(sb, root) + sb.append("]}") + File(dirPath).mkdirs() + File(dirPath, "output.json").run { + createNewFile() + writeText(sb.toString()) + } + } + + private fun traverseExportAVLNode( + sb: StringBuilder, + nodeView: NodeView>>, + ) { + + with(nodeView) { + val json = JsonAVLNode( + key = node.elem.key, + value = node.elem.v, + x = x, + y = y, + height = node.height, + lkey = l?.node?.elem?.key, + rkey = r?.node?.elem?.key + ) + + sb.append(Json.encodeToString(json)) + + l?.let { + sb.append(",") + traverseExportAVLNode(sb, it) + } + r?.let { + sb.append(",") + traverseExportAVLNode(sb, it) + } + } + + } } From c09dd24b3ea337579935904b81e875298c2b0c9b Mon Sep 17 00:00:00 2001 From: Anna-er Date: Wed, 19 Apr 2023 04:56:22 +0300 Subject: [PATCH 138/296] feat(Json): Add sketch of importAVLTree() --- .../kotlin/org/tree/app/controller/io/Json.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index dbc78f54..182a4290 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -69,6 +69,74 @@ class Json { traverseExportAVLNode(sb, it) } } + } + + fun importAVLTree(fileName: String): NodeView>>? { // when we have treeView, fun will be rewritten + val json = try { + File(dirPath, fileName).readText() + } catch (_: FileNotFoundException) { + return null + } + + val jsonTree = Json.decodeFromString(json) + return parseAVLNodes(jsonTree) + } + + private class NodeAndKeys( + val nv: NodeView>>, + val lkey: Int?, + val rkey: Int? + ) + + private fun parseAVLNodes(nodeAndKeysRecords: JsonAVLTree): NodeView>>? { + val key2nk = mutableMapOf() + for (nkRecord in nodeAndKeysRecords.AVLTree) { + try { + val key = nkRecord?.key ?: throw IOException("Invalid nodes label in the database") + val value = nkRecord.value + val nv = NodeView(AVLNode(KVP(key, value))) + + nv.x = nkRecord.x + nv.y = nkRecord.y + nv.node.height = nkRecord.height + + val lkey = nkRecord.lkey + val rkey = nkRecord.rkey + key2nk[key] = NodeAndKeys(nv, lkey, rkey) + } catch (ex: Uncoercible) { + throw IOException("Invalid nodes label in the database", ex) + } + } + val nks = key2nk.values.toTypedArray() + if (nks.isEmpty()) { // if nodeAndKeysRecords was empty + return null + } + + for (nk in nks) { + nk.lkey?.let { + nk.nv.l = key2nk[it]?.nv + key2nk.remove(it) + } + nk.nv.l?.let { + nk.nv.node.left = it.node + } + + nk.rkey?.let { + nk.nv.r = key2nk[it]?.nv + key2nk.remove(it) + } + nk.nv.r?.let { + nk.nv.node.right = it.node + } + } + if (key2nk.values.size != 1) { + throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") + } + val root = key2nk.values.first().nv + return root + } + fun cleanDataBase(fileName: String) { + File(dirPath, "$fileName.json").delete() } } From c025e097f8461fa4f8f11c421092042b6061d34c Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 09:09:01 +0300 Subject: [PATCH 139/296] feat(SQLite): Add SQLite to gradle --- app/build.gradle.kts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e05a37d1..97e61130 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,14 +4,22 @@ plugins { id("org.tree.kotlin-application-conventions") - kotlin("plugin.serialization") version "1.8.20" } dependencies { implementation(project(":binaryTree")) implementation("org.neo4j.driver", "neo4j-java-driver", "5.7.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + + implementation("io.github.microutils:kotlin-logging-jvm:3.0.5") + implementation("org.slf4j:slf4j-simple:2.0.5") + + implementation("org.xerial:sqlite-jdbc:3.40.1.0") + implementation("org.jetbrains.exposed:exposed-core:0.40.1") + implementation("org.jetbrains.exposed:exposed-dao:0.40.1") + implementation("org.jetbrains.exposed:exposed-jdbc:0.40.1") } application { From a85ab04c58c71aefe917692f43d236abe68eb4f1 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 19 Apr 2023 09:09:50 +0300 Subject: [PATCH 140/296] feat(SQLite): Add SQLite realization --- .../org/tree/app/controller/io/SQLiteIO.kt | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt new file mode 100644 index 00000000..af43534d --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -0,0 +1,143 @@ +package org.tree.app.controller.io + +import org.jetbrains.exposed.exceptions.ExposedSQLException +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction +import org.tree.app.view.NodeView +import org.tree.binaryTree.KVP +import org.tree.binaryTree.Node +import java.io.File +import java.io.IOException +import java.nio.file.Files + +object Nodes : Table("Nodes") { + val key = integer("key") + val parentKey = integer("parentKey").nullable() + val value = text("value") + val x = float("x") + val y = float("y") +} + +class SQLiteIO { + fun importTree(file: File): NodeView>>? { + Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") + var root: NodeView>>? = null + try { + transaction { + addLogger(StdOutSqlLogger) + val amountOfNodes = Nodes.selectAll().count() + if (amountOfNodes > 0) { + root = parseRootForImport() + root?.let { exportTree(it, file) } + } + } + } catch (ex: ExposedSQLException) { + throw IOException("File is not a database", ex) + } + return root + } + + fun exportTree(rootOfBinTree: NodeView>>, file: File) { + try { + Files.createDirectories(file.toPath().parent) + } catch (ex: SecurityException) { + throw IOException("Directory ${file.toPath().parent} cannot be created: no access", ex) + } + + Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") + + transaction { + addLogger(StdOutSqlLogger) + try { + SchemaUtils.drop(Nodes) + SchemaUtils.create(Nodes) + } catch (ex: ExposedSQLException) { + throw IOException("File is not a database", ex) + } + parseNodesForExport(rootOfBinTree, null) + } + } + + private fun parseNodesForExport( + curNode: NodeView>>, + parNode: NodeView>>? + ) { + Nodes.insert { + it[key] = curNode.node.elem.key + it[parentKey] = parNode?.node?.elem?.key + it[value] = curNode.node.elem.v.toString() + it[x] = curNode.x.toFloat() + it[y] = curNode.y.toFloat() + } + val leftChild = curNode.l + if (leftChild != null) { + parseNodesForExport(leftChild, curNode) + } + val rightChild = curNode.r + if (rightChild != null) { + parseNodesForExport(rightChild, curNode) + } + } + + private fun parseRootForImport(): NodeView>>? { + try { + for (node in Nodes.selectAll()) { + val parsedParentKey = node[Nodes.parentKey] + if (parsedParentKey != null) { + continue + } + val parsedKey = node[Nodes.key] + val parsedValue = node[Nodes.value] + val parsedX: Double = node[Nodes.x].toDouble() + val parsedY: Double = node[Nodes.y].toDouble() + val nodeView = NodeView(Node(KVP(parsedKey, parsedValue))) + nodeView.x = parsedX + nodeView.y = parsedY + Nodes.deleteWhere { parentKey eq null } + parseNodesForImport(nodeView) + return nodeView + } + } catch (ex: NumberFormatException) { + throw IOException( + "Node keys must be integers, value must be string and coordinates must be doubles", + ex + ) + } + return null + } + + private fun parseNodesForImport( + curNode: NodeView>> + ): NodeView>> { + for (node in Nodes.selectAll()) { + val parsedKey = node[Nodes.key] + val parsedParentKey: Int = node[Nodes.parentKey] ?: throw IOException("Incorrect binary tree") + val parsedValue = node[Nodes.value] + val parsedX: Double = node[Nodes.x].toDouble() + val parsedY: Double = node[Nodes.y].toDouble() + if (parsedParentKey == curNode.node.elem.key) { + val newNode: NodeView>> = NodeView(Node(KVP(parsedKey, parsedValue))) + newNode.x = parsedX + newNode.y = parsedY + if (parsedKey < parsedParentKey) { + curNode.l = newNode + val leftChild = curNode.l + if (leftChild != null) { + parseNodesForImport(leftChild) + } + } else if (parsedKey > parsedParentKey) { + curNode.r = newNode + val rightChild = curNode.r + if (rightChild != null) { + parseNodesForImport(rightChild) + } + } else { + throw IOException("Incorrect binary tree") + } + + } + } + return curNode + } +} From a563ab7c4ea142399afb55b134bc04916ae4b49a Mon Sep 17 00:00:00 2001 From: David Date: Wed, 19 Apr 2023 13:13:24 +0300 Subject: [PATCH 141/296] fix(SQLiteIO): Fix bug (there was no removal of handled nodes). --- .../kotlin/org/tree/app/controller/io/SQLiteIO.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index af43534d..c90deb3d 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -116,7 +116,8 @@ class SQLiteIO { val parsedValue = node[Nodes.value] val parsedX: Double = node[Nodes.x].toDouble() val parsedY: Double = node[Nodes.y].toDouble() - if (parsedParentKey == curNode.node.elem.key) { + if (parsedParentKey == curNode.node.elem.key){ + Nodes.deleteWhere { key eq parsedKey } val newNode: NodeView>> = NodeView(Node(KVP(parsedKey, parsedValue))) newNode.x = parsedX newNode.y = parsedY @@ -126,16 +127,23 @@ class SQLiteIO { if (leftChild != null) { parseNodesForImport(leftChild) } + if (curNode.r == null){ + parseNodesForImport(curNode) + break + } } else if (parsedKey > parsedParentKey) { curNode.r = newNode val rightChild = curNode.r if (rightChild != null) { parseNodesForImport(rightChild) } + if (curNode.l == null){ + parseNodesForImport(curNode) + break + } } else { throw IOException("Incorrect binary tree") } - } } return curNode From e991484cc254f553b3d42555066a5b75c24b70df Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 21:18:44 +0300 Subject: [PATCH 142/296] fix(TemplateBSTree): Edit find method --- .../org/tree/binaryTree/templates/TemplateBSTree.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index d1f26426..c08a13ca 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -47,12 +47,12 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.elem) { return find(curNode.right, obj) + } else { + return curNode } } From ad9aea86771dcce7f3b41b1e0f24c13b1b5f8c69 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 21:19:12 +0300 Subject: [PATCH 143/296] fix(TemplateBSTree): Edit remove method --- .../org/tree/binaryTree/templates/TemplateBSTree.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index c08a13ca..6f10b243 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -88,12 +88,12 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.elem) { return remove(curNode.right, curNode, obj) + } else { + return deleteNode(curNode, parentNode) } } From fffb369e53b209e84e7edd6e8fc931b192ad8ab8 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 21:21:11 +0300 Subject: [PATCH 144/296] fix(TemplateBalanceTree): Edit remove method --- .../templates/TemplateBalanceBSTree.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index d5b30095..8f268f25 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -84,19 +84,20 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode curNode.elem) { + isRec = BalanceCase.Recursive.RECURSIVE_CALL + targetNode = parentNode + remove(curNode.right, curNode, obj) + } else { + isRec = BalanceCase.Recursive.END + targetNode = parentNode + deleteNode(curNode, parentNode) + } if (res != null) { balance(targetNode, getDirectionChangedChild(targetNode, obj), getBalanceRemoveType(res), isRec) From c36c57a6beed0bc1f68078cbc096df0f687c7244 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 20:18:34 +0300 Subject: [PATCH 145/296] refactor: Remove debug comment --- binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt index fef42349..5659773a 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/RBTreeTest.kt @@ -24,7 +24,6 @@ class RBTreeTest { val newVal = randomizer.nextInt() val exp = values.add(newVal) val act = tree.insert(newVal) - //println("Insert $newVal") MatcherAssert.assertThat(act, Matchers.equalTo(exp)) checkRBTree(tree, values.toTypedArray()) From b660f0b887500a2f1c52f53cc419b98469436a43 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 20:53:36 +0300 Subject: [PATCH 146/296] refactor: Rename getNonNullChild method --- .../org/tree/binaryTree/templates/TemplateBSTree.kt | 2 +- .../kotlin/org/tree/binaryTree/templates/TemplateNode.kt | 2 +- .../src/test/kotlin/org/tree/binaryTree/NodeTest.kt | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index 6f10b243..a3f35f70 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -73,7 +73,7 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { - replaceNode(curNode, parentNode, curNode.getNonNullChild()) + replaceNode(curNode, parentNode, curNode.getChild()) } else -> { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt index 716ed620..4c06b43b 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt @@ -5,7 +5,7 @@ abstract class TemplateNode, NODE_T : TemplateNode> var left: NODE_T? = null var right: NODE_T? = null - fun getNonNullChild(): NODE_T? { + fun getChild(): NODE_T? { val res = if (left != null) { left } else if (right != null) { diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index 1c502dc2..cfd34f22 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -57,7 +57,7 @@ class NodeTest { 20, null ) ) - val act = root?.getNonNullChild() + val act = root?.getChild() val exp = root?.left MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @@ -70,7 +70,7 @@ class NodeTest { null, 60 ) ) - val act = root?.getNonNullChild() + val act = root?.getChild() val exp = root?.right MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @@ -83,7 +83,7 @@ class NodeTest { null, null ) ) - val act = root?.getNonNullChild() + val act = root?.getChild() val exp = null MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @@ -96,7 +96,7 @@ class NodeTest { 20, 60 ) ) - val act = root?.getNonNullChild() + val act = root?.getChild() MatcherAssert.assertThat(act, Matchers.anyOf(Matchers.equalTo(root?.left), Matchers.equalTo(root?.right))) } From 1f9e45253985bec8b45491dec1010b8dcee8d56a Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 20:57:45 +0300 Subject: [PATCH 147/296] refactor: Edit README --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bf81bb32..3f3465aa 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ line: ```bash # Clone this repository -git clone https://github.com/spbu-coding-2022/trees-2.git +git clone https://github.com/spbu-coding-2022/trees-2.git trees-2 # Go into the repository cd trees-2 @@ -88,8 +88,7 @@ Also, there are 3 types of links: Using the `Tree` labels, we can store several trees in the database at once. But their nodes should not intersect (shouldn't have links between each other). -![neo4j-example](https://user-images.githubusercontent.com/66139162/232936241-963c1213-e1ad-46fd-b134-3c137fa04b45.png) - +![neo4j-example](https://user-images.githubusercontent.com/66139162/233449145-15476b7d-d1c9-4dfa-b4a6-bd500d3a25d4.png) --- ## Library @@ -125,9 +124,9 @@ fun example() { // create common BinSearchTree with int keys and string values val keyValuePairTree = BinSearchTree>() - intTree.insert(3) // insert new node - intTree.find(3) // find node - intTree.remove(3) // remove node if it exists + intTree.insert(3) // insert new node + intTree.remove(3) // remove node if it exists + val foundNode = intTree.find(3) // find node for (i in 0..10) { stringTree.insert(i.toString()) From fce19c3ba3eac254337a056d28a106aecfa31efc Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 21:27:54 +0300 Subject: [PATCH 148/296] feat(KVP): Add default value for constructor --- binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt index a19f9124..9dd23775 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt @@ -1,6 +1,6 @@ package org.tree.binaryTree -class KVP, V>(val key: K, var v: V?) : Comparable> { +class KVP, V>(val key: K, var v: V? = null) : Comparable> { override fun compareTo(other: KVP): Int { return key.compareTo(other.key) } From 4843d16001f1f3c08a2fb3f78c68ffbd95f2722e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 21:42:57 +0300 Subject: [PATCH 149/296] fix(Neo4jIO): Fix case of deleting empty tree --- app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 8172b7fd..2e7f6886 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -73,7 +73,7 @@ class Neo4jIO() : Closeable { private fun deleteTree(tx: TransactionContext, treeName: String) { tx.run( "MATCH (t: Tree {name: \"$treeName\"})" + - "MATCH (t)-[*]->(n:RBNode) " + + "OPTIONAL MATCH (t)-[*]->(n:RBNode) " + "DETACH DELETE t, n" ) } From 9d4dd23c0ea481ca016d77f7c7c5bf513851ec1b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 22:10:11 +0300 Subject: [PATCH 150/296] refactor(Neo4jIO): Add labels and links constants --- .../org/tree/app/controller/io/Neo4jIO.kt | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 2e7f6886..6762026b 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -12,6 +12,18 @@ import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null + private companion object { + // Labels + const val RBNODE = "RBNode" + const val TREE = "Tree" + const val NEW_NODE = "NewNode" + + // Links + const val ROOT = "ROOT" + const val LCHILD = "LEFT_CHILD" + const val RCHILD = "RIGHT_CHILD" + } + fun exportRBTree( root: NodeView>>, treeName: String = "Tree" @@ -21,22 +33,22 @@ class Neo4jIO() : Closeable { deleteTree(tx, treeName) tx.run(genExportRBNodes(root)) tx.run( - "MATCH (p: RBNode) " + - "MATCH (l: NewNode {key: p.lkey}) " + - "CREATE (p)-[:LEFT_CHILD]->(l) " + - "REMOVE p.lkey, l:NewNode" + "MATCH (p: $RBNODE) " + + "MATCH (l: $NEW_NODE {key: p.lkey}) " + + "CREATE (p)-[: $LCHILD]->(l) " + + "REMOVE p.lkey, l: $NEW_NODE" ) // connect parent and left child tx.run( - "MATCH (p: RBNode) " + - "MATCH (r: NewNode {key: p.rkey}) " + - "CREATE (p)-[:RIGHT_CHILD]->(r) " + - "REMOVE p.rkey, r:NewNode" + "MATCH (p: $RBNODE) " + + "MATCH (r: $NEW_NODE {key: p.rkey}) " + + "CREATE (p)-[: $RCHILD]->(r) " + + "REMOVE p.rkey, r: $NEW_NODE" )// connect parent and right child tx.run( - "MATCH (r: NewNode) " + - "CREATE (t: Tree {name: \"$treeName\"})-[:ROOT]->(r) " + - "REMOVE r:NewNode" + "MATCH (r: $NEW_NODE) " + + "CREATE (t: $TREE {name: \"$treeName\"})-[:$ROOT]->(r) " + + "REMOVE r:$NEW_NODE" ) } session.close() @@ -63,7 +75,7 @@ class Neo4jIO() : Closeable { fun getTreesNames(): MutableList { val session = driver?.session() ?: throw IOException("Driver is not open") val res: MutableList = session.executeRead { tx -> - val nameRecords = tx.run("MATCH (t: Tree) RETURN t.name AS name") + val nameRecords = tx.run("MATCH (t: $TREE) RETURN t.name AS name") parseNames(nameRecords) } session.close() @@ -72,8 +84,8 @@ class Neo4jIO() : Closeable { private fun deleteTree(tx: TransactionContext, treeName: String) { tx.run( - "MATCH (t: Tree {name: \"$treeName\"})" + - "OPTIONAL MATCH (t)-[*]->(n:RBNode) " + + "MATCH (t: $TREE {name: \"$treeName\"})" + + "OPTIONAL MATCH (t)-[*]->(n:$RBNODE) " + "DETACH DELETE t, n" ) } @@ -102,7 +114,7 @@ class Neo4jIO() : Closeable { "" } sb.append( - "CREATE (:RBNode:NewNode {key : ${node.elem.key}, " + + "CREATE (:$RBNODE:$NEW_NODE {key : ${node.elem.key}, " + "value: \"${node.elem.v ?: ""}\", " + "x: $x, y: $y, " + "isBlack: ${node.col == RBNode.Colour.BLACK}" + @@ -120,9 +132,9 @@ class Neo4jIO() : Closeable { private fun importRBNodes(tx: TransactionContext, treeName: String): NodeView>>? { val nodeAndKeysRecords = tx.run( - "MATCH (:Tree {name: \"$treeName\"})-[*]->(p: RBNode)" + - "OPTIONAL MATCH (p)-[:LEFT_CHILD]->(l: RBNode) " + - "OPTIONAL MATCH (p)-[:RIGHT_CHILD]->(r: RBNode) " + + "MATCH (:$TREE {name: \"$treeName\"})-[*]->(p: $RBNODE)" + + "OPTIONAL MATCH (p)-[: $LCHILD]->(l: $RBNODE) " + + "OPTIONAL MATCH (p)-[: $RCHILD]->(r: $RBNODE) " + "RETURN p.x AS x, p.y AS y, p.isBlack AS isBlack, p.key AS key, p.value AS value, " + " l.key AS lKey, r.key AS rKey" ) // for all nodes get their properties + keys of their children From b1dcb722f1bba8885d331b8e4e2e748a02e712ab Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Thu, 20 Apr 2023 23:38:38 +0300 Subject: [PATCH 151/296] refactor(Neo4jIO): Rewrite queries using parameters --- .../org/tree/app/controller/io/Neo4jIO.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 6762026b..f44874dd 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -31,7 +31,8 @@ class Neo4jIO() : Closeable { val session = driver?.session() ?: throw IOException("Driver is not open") session.executeWrite { tx -> deleteTree(tx, treeName) - tx.run(genExportRBNodes(root)) + //tx.run(genExportRBNodes(root)) + exportRBNode(tx, root) tx.run( "MATCH (p: $RBNODE) " + "MATCH (l: $NEW_NODE {key: p.lkey}) " + @@ -47,8 +48,11 @@ class Neo4jIO() : Closeable { tx.run( "MATCH (r: $NEW_NODE) " + - "CREATE (t: $TREE {name: \"$treeName\"})-[:$ROOT]->(r) " + - "REMOVE r:$NEW_NODE" + "CREATE (t: $TREE {name: \$treeName})-[:$ROOT]->(r) " + + "REMOVE r:$NEW_NODE", + mutableMapOf( + "treeName" to treeName + ) as Map? ) } session.close() @@ -84,9 +88,12 @@ class Neo4jIO() : Closeable { private fun deleteTree(tx: TransactionContext, treeName: String) { tx.run( - "MATCH (t: $TREE {name: \"$treeName\"})" + + "MATCH (t: $TREE {name: \$treeName})" + "OPTIONAL MATCH (t)-[*]->(n:$RBNODE) " + - "DETACH DELETE t, n" + "DETACH DELETE t, n", + mutableMapOf( + "treeName" to treeName + ) as Map? ) } @@ -132,11 +139,14 @@ class Neo4jIO() : Closeable { private fun importRBNodes(tx: TransactionContext, treeName: String): NodeView>>? { val nodeAndKeysRecords = tx.run( - "MATCH (:$TREE {name: \"$treeName\"})-[*]->(p: $RBNODE)" + + "MATCH (:$TREE {name: \$treeName})-[*]->(p: $RBNODE)" + "OPTIONAL MATCH (p)-[: $LCHILD]->(l: $RBNODE) " + "OPTIONAL MATCH (p)-[: $RCHILD]->(r: $RBNODE) " + "RETURN p.x AS x, p.y AS y, p.isBlack AS isBlack, p.key AS key, p.value AS value, " + - " l.key AS lKey, r.key AS rKey" + " l.key AS lKey, r.key AS rKey", + mutableMapOf( + "treeName" to treeName + ) as Map? ) // for all nodes get their properties + keys of their children return parseRBNodes(nodeAndKeysRecords) } From 2d17cea3e0898fcfa67199ab30060f1abb9fbb49 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 21 Apr 2023 00:04:07 +0300 Subject: [PATCH 152/296] refactor(Neo4jIO): Change export using parameters --- .../org/tree/app/controller/io/Neo4jIO.kt | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index f44874dd..c5b6e998 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -31,7 +31,6 @@ class Neo4jIO() : Closeable { val session = driver?.session() ?: throw IOException("Driver is not open") session.executeWrite { tx -> deleteTree(tx, treeName) - //tx.run(genExportRBNodes(root)) exportRBNode(tx, root) tx.run( "MATCH (p: $RBNODE) " + @@ -53,7 +52,7 @@ class Neo4jIO() : Closeable { mutableMapOf( "treeName" to treeName ) as Map? - ) + )// connect tree and root } session.close() } @@ -94,45 +93,39 @@ class Neo4jIO() : Closeable { mutableMapOf( "treeName" to treeName ) as Map? - ) - } - - private fun genExportRBNodes(root: NodeView>>): String { - val sb = StringBuilder() - traverseExportRBNode(sb, root) - return sb.toString() + ) // delete tree and all its nodes } - private fun traverseExportRBNode( - sb: StringBuilder, + private fun exportRBNode( + tx: TransactionContext, nodeView: NodeView>>, ) { with(nodeView) { val lkey = l?.node?.elem?.key - val lkeyString = if (lkey != null) { - ", lkey: ${lkey} " - } else { - "" - } val rkey = r?.node?.elem?.key - val rkeyString = if (rkey != null) { - ", rkey: ${r?.node?.elem?.key}" - } else { - "" - } - sb.append( - "CREATE (:$RBNODE:$NEW_NODE {key : ${node.elem.key}, " + - "value: \"${node.elem.v ?: ""}\", " + - "x: $x, y: $y, " + - "isBlack: ${node.col == RBNode.Colour.BLACK}" + - lkeyString + rkeyString + - "})" - ) // save node (lkey and rkey are needed for connection later) + + tx.run( + "CREATE (:$RBNODE:$NEW_NODE {key : \$key, " + + "value: \$value, " + + "x: \$x, y: \$y, " + + "isBlack: \$isBlack, " + + "lkey: \$lkey, " + + "rkey: \$rkey" + + "})", + mutableMapOf( + "key" to node.elem.key, + "value" to (node.elem.v ?: ""), + "x" to x, "y" to y, + "isBlack" to (node.col == RBNode.Colour.BLACK), + "lkey" to lkey, + "rkey" to rkey + ) as Map? + ) l?.let { - traverseExportRBNode(sb, it) + exportRBNode(tx, it) } r?.let { - traverseExportRBNode(sb, it) + exportRBNode(tx, it) } } } From 389a54396d4162dff3b5a80e65d0a7898806f212 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 21 Apr 2023 21:46:46 +0300 Subject: [PATCH 153/296] refactor(Json): Change JsonAVLNode and JsonAVLTree data clas --- app/src/main/kotlin/org/tree/app/controller/io/Json.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index 182a4290..d7ca29a2 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -19,13 +19,13 @@ private data class JsonAVLNode( val x: Double, val y: Double, val height: Int, - val lkey: Int?, - val rkey: Int? + val left: JsonAVLNode?, + val right: JsonAVLNode? ) @Serializable private data class JsonAVLTree( - val AVLTree: Array + val root: JsonAVLNode? ) class Json { val dirPath = "json-saved" From a8321937266c64e2e7f59de59b75f27c2a94913f Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 21 Apr 2023 21:48:19 +0300 Subject: [PATCH 154/296] feat(Json): Add toJsonNode() and deserialize() methods --- .../kotlin/org/tree/app/controller/io/Json.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index d7ca29a2..d56829b5 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -30,6 +30,27 @@ private data class JsonAVLTree( class Json { val dirPath = "json-saved" + private fun NodeView>>.toJsonNode(): JsonAVLNode = JsonAVLNode( + key = this.node.elem.key, + value = this.node.elem.v, + x = this.x, + y = this.y, + height = this.node.height, + left = l?.toJsonNode(), + right = r?.toJsonNode() + ) + + private fun JsonAVLNode.deserialize(): NodeView>> { + val nv = NodeView(AVLNode(KVP(key, value))) + nv.node.height = height + nv.x = x + nv.y = y + nv.l = left?.deserialize() + nv.r = right?.deserialize() + + return nv + } + fun exportAVLTree(root: NodeView>>) { val sb = StringBuilder() sb.append("{\"AVLTree\":[") From 65cd8fd2ad73c140857b96e6b0d550e42f258a5b Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 21 Apr 2023 21:49:42 +0300 Subject: [PATCH 155/296] perf(Json): Add exportTree() instead exportAVLTree() --- .../kotlin/org/tree/app/controller/io/Json.kt | 46 ++++--------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index d56829b5..d50cc80b 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -11,6 +11,7 @@ import org.tree.binaryTree.KVP import java.io.File import java.io.FileNotFoundException import java.io.IOException +import java.nio.file.Files @Serializable private data class JsonAVLNode( @@ -51,44 +52,17 @@ class Json { return nv } - fun exportAVLTree(root: NodeView>>) { - val sb = StringBuilder() - sb.append("{\"AVLTree\":[") - traverseExportAVLNode(sb, root) - sb.append("]}") - File(dirPath).mkdirs() - File(dirPath, "output.json").run { - createNewFile() - writeText(sb.toString()) + fun exportTree(root: NodeView>>, file: File) { + try { + Files.createDirectories(file.toPath().parent) + } catch (ex: SecurityException) { + throw IOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } - } + val jsonTree = JsonAVLTree(root.toJsonNode()) - private fun traverseExportAVLNode( - sb: StringBuilder, - nodeView: NodeView>>, - ) { - - with(nodeView) { - val json = JsonAVLNode( - key = node.elem.key, - value = node.elem.v, - x = x, - y = y, - height = node.height, - lkey = l?.node?.elem?.key, - rkey = r?.node?.elem?.key - ) - - sb.append(Json.encodeToString(json)) - - l?.let { - sb.append(",") - traverseExportAVLNode(sb, it) - } - r?.let { - sb.append(",") - traverseExportAVLNode(sb, it) - } + file.run { + createNewFile() + writeText(Json.encodeToString(jsonTree)) } } From 924245f856a8dda93c1787f77e31e0ceea67d102 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 21 Apr 2023 21:50:45 +0300 Subject: [PATCH 156/296] perf(Json): Add importTree() instead importAVLTree() --- .../kotlin/org/tree/app/controller/io/Json.kt | 60 +------------------ 1 file changed, 3 insertions(+), 57 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index d50cc80b..233578ef 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -66,69 +66,15 @@ class Json { } } - fun importAVLTree(fileName: String): NodeView>>? { // when we have treeView, fun will be rewritten + fun importTree(file: File): NodeView>>? { val json = try { - File(dirPath, fileName).readText() + file.readText() } catch (_: FileNotFoundException) { return null } val jsonTree = Json.decodeFromString(json) - return parseAVLNodes(jsonTree) - } - - private class NodeAndKeys( - val nv: NodeView>>, - val lkey: Int?, - val rkey: Int? - ) - - private fun parseAVLNodes(nodeAndKeysRecords: JsonAVLTree): NodeView>>? { - val key2nk = mutableMapOf() - for (nkRecord in nodeAndKeysRecords.AVLTree) { - try { - val key = nkRecord?.key ?: throw IOException("Invalid nodes label in the database") - val value = nkRecord.value - val nv = NodeView(AVLNode(KVP(key, value))) - - nv.x = nkRecord.x - nv.y = nkRecord.y - nv.node.height = nkRecord.height - - val lkey = nkRecord.lkey - val rkey = nkRecord.rkey - key2nk[key] = NodeAndKeys(nv, lkey, rkey) - } catch (ex: Uncoercible) { - throw IOException("Invalid nodes label in the database", ex) - } - } - val nks = key2nk.values.toTypedArray() - if (nks.isEmpty()) { // if nodeAndKeysRecords was empty - return null - } - - for (nk in nks) { - nk.lkey?.let { - nk.nv.l = key2nk[it]?.nv - key2nk.remove(it) - } - nk.nv.l?.let { - nk.nv.node.left = it.node - } - - nk.rkey?.let { - nk.nv.r = key2nk[it]?.nv - key2nk.remove(it) - } - nk.nv.r?.let { - nk.nv.node.right = it.node - } - } - if (key2nk.values.size != 1) { - throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") - } - val root = key2nk.values.first().nv - return root + return jsonTree.root?.deserialize() } fun cleanDataBase(fileName: String) { From daa9acfe5b351eb9a4bdbae9442761ad38916008 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 21 Apr 2023 21:54:26 +0300 Subject: [PATCH 157/296] refactor(Json): Change cleanDataBase() --- app/src/main/kotlin/org/tree/app/controller/io/Json.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index 233578ef..951bddf9 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -4,7 +4,6 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import org.neo4j.driver.exceptions.value.Uncoercible import org.tree.app.view.NodeView import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP @@ -29,8 +28,6 @@ private data class JsonAVLTree( val root: JsonAVLNode? ) class Json { - val dirPath = "json-saved" - private fun NodeView>>.toJsonNode(): JsonAVLNode = JsonAVLNode( key = this.node.elem.key, value = this.node.elem.v, @@ -77,7 +74,7 @@ class Json { return jsonTree.root?.deserialize() } - fun cleanDataBase(fileName: String) { - File(dirPath, "$fileName.json").delete() + fun cleanDataBase(file: File) { + file.delete() } } From a99201ef3afb243b94b3327ad13e92be48358e5d Mon Sep 17 00:00:00 2001 From: Anna-er Date: Fri, 21 Apr 2023 22:28:38 +0300 Subject: [PATCH 158/296] fix: Return serialization --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 97e61130..de8262e0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("org.tree.kotlin-application-conventions") + kotlin("plugin.serialization") version "1.8.20" } dependencies { From a014f4ab0f8d3d7e7d83abc9ffe54e737088f5ad Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 22 Apr 2023 00:12:44 +0300 Subject: [PATCH 159/296] refactor(Json): Rename toJsonNode() into serialize() --- app/src/main/kotlin/org/tree/app/controller/io/Json.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index 951bddf9..3de8b352 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -28,14 +28,14 @@ private data class JsonAVLTree( val root: JsonAVLNode? ) class Json { - private fun NodeView>>.toJsonNode(): JsonAVLNode = JsonAVLNode( + private fun NodeView>>.serialize(): JsonAVLNode = JsonAVLNode( key = this.node.elem.key, value = this.node.elem.v, x = this.x, y = this.y, height = this.node.height, - left = l?.toJsonNode(), - right = r?.toJsonNode() + left = l?.serialize(), + right = r?.serialize() ) private fun JsonAVLNode.deserialize(): NodeView>> { @@ -55,7 +55,7 @@ class Json { } catch (ex: SecurityException) { throw IOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } - val jsonTree = JsonAVLTree(root.toJsonNode()) + val jsonTree = JsonAVLTree(root.serialize()) file.run { createNewFile() From 458cc94cabebf45f538d09a12dfdb8d3c0dd55d0 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sun, 23 Apr 2023 00:29:23 +0300 Subject: [PATCH 160/296] refactor: Add plain text database into README --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f3465aa..9ebf14ea 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,25 @@ information visit https://neo4j.com/docs/operations-manual/current/installation/ ### Plain text -TODO: write +At plain text database we store AVLTree. We use json files. There are 2 types of objects with the following struct: + +- `Tree` + - `root` *AVLNode* +- `AVLNode` + - `key` *int* + - `value` *string* + - `height` *int* - node height + - `x` *double* - node position at ui + - `y` *double* - node position at ui + - `left` *AVLNode* - node left child + - `right` *AVLNode* - node right child + +We have functions: + +- `exportTree(root, file.json)` - writes a tree with root `root` to a file `file.json`. +- `importTree(file.json)` - reads the tree stored in the file `file.json` and returns it`s root. +- `cleanDataBase(file.json)` - deletes a file `file.json`. + ### SQLite From 65e1f73440d6eea9edd90eba074037306ff26181 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 21 Apr 2023 13:16:06 +0300 Subject: [PATCH 161/296] refactor(RBTree): Improve code readability --- .../kotlin/org/tree/binaryTree/trees/RBTree.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 4a6b9184..fdd7fd9b 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -144,6 +144,7 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun balanceRemove1(parentNode: RBNode?, removedChild: BalanceCase.ChangedChild) { + // balanced remove with 1 non-null child if (removedChild == BalanceCase.ChangedChild.RIGHT) { val rightChild = parentNode?.right if (rightChild != null) { @@ -158,6 +159,7 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { + // balanced remove with 2 null children if (getColourOfRemovedNode(parentNode) == RBNode.Colour.BLACK) { // red => no need to balance if (parentNode.col == RBNode.Colour.RED) { // then other child is black if (removedChild == BalanceCase.ChangedChild.RIGHT) { @@ -298,14 +300,14 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InRightChildWithBlackParentBlackOtherChild( parentNode: RBNode, otherChild: RBNode - ): Int { + ) { val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { if (rightChildOfOtherChild.col == RBNode.Colour.RED) { rightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) - return 0 + return } } val leftChildOfOtherChild = otherChild.left @@ -313,7 +315,7 @@ class RBTree> : TemplateBalanceBSTree>() { if (leftChildOfOtherChild.col == RBNode.Colour.RED) { leftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(parentNode, parentNode.parent) - return 0 + return } } otherChild.col = RBNode.Colour.RED @@ -325,21 +327,21 @@ class RBTree> : TemplateBalanceBSTree>() { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) } } - return 0 + return } private fun balanceRemove2InLeftChildWithBlackParentBlackOtherChild( parentNode: RBNode, otherChild: RBNode - ): Int { + ){ val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { if (leftChildOfOtherChild.col == RBNode.Colour.RED) { leftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - return 0 + return } } val rightChildOfOtherChild = otherChild.right @@ -347,7 +349,7 @@ class RBTree> : TemplateBalanceBSTree>() { if (rightChildOfOtherChild.col == RBNode.Colour.RED) { rightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(parentNode, parentNode.parent) - return 0 + return } } otherChild.col = RBNode.Colour.RED @@ -359,7 +361,7 @@ class RBTree> : TemplateBalanceBSTree>() { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) } } - return 0 + return } private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Colour { From 0e69973c8723d464bb615a4fbf8e78238207171f Mon Sep 17 00:00:00 2001 From: David Date: Sat, 22 Apr 2023 07:46:05 +0300 Subject: [PATCH 162/296] refactor(RBTree): Add algorithm source in comments --- binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index fdd7fd9b..295f3aa2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -2,7 +2,7 @@ package org.tree.binaryTree.trees import org.tree.binaryTree.RBNode import org.tree.binaryTree.templates.TemplateBalanceBSTree - +// algorithm source: https://www.youtube.com/watch?v=T70nn4EyTrs&ab_channel=%D0%9B%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B9%D0%A4%D0%9F%D0%9C%D0%98 class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode != null) { From 4dd01947b473ec49a144da10d0fa84bded291316 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 23 Apr 2023 15:46:30 +0300 Subject: [PATCH 163/296] fix(SQLiteIO): Add class InstanceOfNode, implementation of DAO --- .../org/tree/app/controller/io/SQLiteIO.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index c90deb3d..818a2c4a 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -1,5 +1,9 @@ package org.tree.app.controller.io +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq @@ -11,7 +15,7 @@ import java.io.File import java.io.IOException import java.nio.file.Files -object Nodes : Table("Nodes") { +object Nodes : IntIdTable() { val key = integer("key") val parentKey = integer("parentKey").nullable() val value = text("value") @@ -19,6 +23,16 @@ object Nodes : Table("Nodes") { val y = float("y") } +class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with data in SQLite + companion object : IntEntityClass(Nodes) + + var key by Nodes.key + var parentKey by Nodes.parentKey + var value by Nodes.value + var x by Nodes.x + var y by Nodes.y +} + class SQLiteIO { fun importTree(file: File): NodeView>>? { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") From d63e791ceac8f8b1a45a9718c6822748c7323678 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 23 Apr 2023 15:50:55 +0300 Subject: [PATCH 164/296] feat(SQLiteIO): Add method for setting coordinates in nodeView --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 818a2c4a..f6dba359 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -162,4 +162,9 @@ class SQLiteIO { } return curNode } + + private fun addCoordinatesForNodeView(nodeView: NodeView>>, x: Double, y: Double) { + nodeView.x = x + nodeView.y = y + } } From d4cf258ab6861945dac930b43cfc7614face7e12 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 23 Apr 2023 16:00:05 +0300 Subject: [PATCH 165/296] fix(SQLiteIO): Fix importTree method --- .../org/tree/app/controller/io/SQLiteIO.kt | 157 +++++++++--------- 1 file changed, 77 insertions(+), 80 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index f6dba359..d281555d 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -5,8 +5,10 @@ import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.exceptions.ExposedSQLException -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.transaction import org.tree.app.view.NodeView import org.tree.binaryTree.KVP @@ -34,16 +36,17 @@ class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with } class SQLiteIO { + private var amountOfNodesToHandle = 0 fun importTree(file: File): NodeView>>? { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") var root: NodeView>>? = null try { transaction { addLogger(StdOutSqlLogger) - val amountOfNodes = Nodes.selectAll().count() + val amountOfNodes = InstanceOfNode.all().count() if (amountOfNodes > 0) { - root = parseRootForImport() - root?.let { exportTree(it, file) } + val setOfNodes = InstanceOfNode.all().toMutableSet() + root = parseRootForImport(setOfNodes) } } } catch (ex: ExposedSQLException) { @@ -58,9 +61,7 @@ class SQLiteIO { } catch (ex: SecurityException) { throw IOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } - Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") - transaction { addLogger(StdOutSqlLogger) try { @@ -73,94 +74,90 @@ class SQLiteIO { } } - private fun parseNodesForExport( - curNode: NodeView>>, - parNode: NodeView>>? - ) { - Nodes.insert { - it[key] = curNode.node.elem.key - it[parentKey] = parNode?.node?.elem?.key - it[value] = curNode.node.elem.v.toString() - it[x] = curNode.x.toFloat() - it[y] = curNode.y.toFloat() - } - val leftChild = curNode.l - if (leftChild != null) { - parseNodesForExport(leftChild, curNode) - } - val rightChild = curNode.r - if (rightChild != null) { - parseNodesForExport(rightChild, curNode) - } - } - private fun parseRootForImport(): NodeView>>? { + private fun parseRootForImport( + setOfNodeView: MutableSet + ): NodeView>> { try { - for (node in Nodes.selectAll()) { - val parsedParentKey = node[Nodes.parentKey] - if (parsedParentKey != null) { - continue - } - val parsedKey = node[Nodes.key] - val parsedValue = node[Nodes.value] - val parsedX: Double = node[Nodes.x].toDouble() - val parsedY: Double = node[Nodes.y].toDouble() - val nodeView = NodeView(Node(KVP(parsedKey, parsedValue))) - nodeView.x = parsedX - nodeView.y = parsedY - Nodes.deleteWhere { parentKey eq null } - parseNodesForImport(nodeView) - return nodeView - } + val nodeView = setOfNodeView.elementAt(0) + setOfNodeView.remove(nodeView) + val parsedKey = nodeView.key + val parsedValue = nodeView.value + val parsedX: Double = nodeView.x.toDouble() + val parsedY: Double = nodeView.y.toDouble() + val newNodeView = NodeView(Node(KVP(parsedKey, parsedValue))) + addCoordinatesForNodeView(newNodeView, parsedX, parsedY) + newNodeView.node = Node(KVP(parsedKey, parsedValue)) + amountOfNodesToHandle = setOfNodeView.count() + parseNodesForImport(setOfNodeView, newNodeView) + return newNodeView } catch (ex: NumberFormatException) { throw IOException( "Node keys must be integers, value must be string and coordinates must be doubles", ex ) } - return null } private fun parseNodesForImport( + setOfNodeView: MutableSet, curNode: NodeView>> - ): NodeView>> { - for (node in Nodes.selectAll()) { - val parsedKey = node[Nodes.key] - val parsedParentKey: Int = node[Nodes.parentKey] ?: throw IOException("Incorrect binary tree") - val parsedValue = node[Nodes.value] - val parsedX: Double = node[Nodes.x].toDouble() - val parsedY: Double = node[Nodes.y].toDouble() - if (parsedParentKey == curNode.node.elem.key){ - Nodes.deleteWhere { key eq parsedKey } - val newNode: NodeView>> = NodeView(Node(KVP(parsedKey, parsedValue))) - newNode.x = parsedX - newNode.y = parsedY - if (parsedKey < parsedParentKey) { - curNode.l = newNode - val leftChild = curNode.l - if (leftChild != null) { - parseNodesForImport(leftChild) - } - if (curNode.r == null){ - parseNodesForImport(curNode) - break - } - } else if (parsedKey > parsedParentKey) { - curNode.r = newNode - val rightChild = curNode.r - if (rightChild != null) { - parseNodesForImport(rightChild) - } - if (curNode.l == null){ - parseNodesForImport(curNode) - break - } - } else { - throw IOException("Incorrect binary tree") + ) { + if (amountOfNodesToHandle <= 0) { + return + } + val nodeView = setOfNodeView.elementAt(0) + val parsedKey = nodeView.key + val parsedParentKey: Int = nodeView.parentKey ?: throw IOException("Incorrect binary tree") + val parsedValue = nodeView.value + val parsedX: Double = nodeView.x.toDouble() + val parsedY: Double = nodeView.y.toDouble() + if (parsedParentKey == curNode.node.elem.key) { + val newNode = Node(KVP(parsedKey, parsedValue)) + val newNodeView = NodeView(newNode) + addCoordinatesForNodeView(newNodeView, parsedX, parsedY) + setOfNodeView.remove(nodeView) + amountOfNodesToHandle-- + if (parsedKey < parsedParentKey) { + curNode.node.left = newNode + curNode.l = newNodeView + val leftChild = curNode.l + if (leftChild != null) { + parseNodesForImport(setOfNodeView, leftChild) } + parseNodesForImport(setOfNodeView, curNode) + } else if (parsedKey > parsedParentKey) { + curNode.node.right = newNode + curNode.r = newNodeView + val rightChild = curNode.r + if (rightChild != null) { + parseNodesForImport(setOfNodeView, rightChild) + } + } else { + throw IOException("Incorrect binary tree") } } - return curNode + } + + private fun parseNodesForExport( + curNode: NodeView>>, + parNode: NodeView>>? + ) { + InstanceOfNode.new { + key = curNode.node.elem.key + parentKey = parNode?.node?.elem?.key + value = curNode.node.elem.v.toString() + x = curNode.x.toFloat() + y = curNode.y.toFloat() + } + val leftChild = curNode.l + if (leftChild != null) { + parseNodesForExport(leftChild, curNode) + } + val rightChild = curNode.r + if (rightChild != null) { + parseNodesForExport(rightChild, curNode) + } } private fun addCoordinatesForNodeView(nodeView: NodeView>>, x: Double, y: Double) { From 6edb7312ddfb2554adb61095bc8ee4563d79719e Mon Sep 17 00:00:00 2001 From: David Date: Sun, 23 Apr 2023 16:01:24 +0300 Subject: [PATCH 166/296] feat(KVP): Make KVP data class --- binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt index 9dd23775..d9640bbf 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/KeyValuePair.kt @@ -1,6 +1,6 @@ package org.tree.binaryTree -class KVP, V>(val key: K, var v: V? = null) : Comparable> { +data class KVP, V>(val key: K, var v: V? = null) : Comparable> { override fun compareTo(other: KVP): Int { return key.compareTo(other.key) } From c7b2ac25888c54d06cb9520af3a4b0ff5f7dde28 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Fri, 21 Apr 2023 23:19:27 +0300 Subject: [PATCH 167/296] feat: Add compose (ui framework) in project --- .github/workflows/actions.yml | 7 +++++++ README.md | 5 +++++ app/build.gradle.kts | 9 +++++++++ app/src/main/kotlin/org/tree/app/App.kt | 21 ++++++++++++++++----- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 01222545..018d25c3 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -11,6 +11,13 @@ jobs: steps: - name: Checkout project sources uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + - name: Setup Gradle uses: gradle/gradle-build-action@v2 - name: Run build with Gradle Wrapper diff --git a/README.md b/README.md index 9ebf14ea..9a294cd6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ You can: ## Get started +### Requirements + +- Java JDK version 11 or later + ### Build To build and run this application locally, you'll need Git, Gradle and JDK installed on your computer. From your command @@ -107,6 +111,7 @@ Also, there are 3 types of links: Using the `Tree` labels, we can store several trees in the database at once. But their nodes should not intersect (shouldn't have links between each other). ![neo4j-example](https://user-images.githubusercontent.com/66139162/233449145-15476b7d-d1c9-4dfa-b4a6-bd500d3a25d4.png) + --- ## Library diff --git a/app/build.gradle.kts b/app/build.gradle.kts index de8262e0..b94b96cc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,7 +4,16 @@ plugins { id("org.tree.kotlin-application-conventions") + kotlin("plugin.serialization") version "1.8.20" + + id("org.jetbrains.compose") version "1.4.0" +} + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() } dependencies { diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 77872c3e..1c52d1fa 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -3,10 +3,21 @@ */ package org.tree.app +import androidx.compose.material.Text +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 org.tree.binaryTree.Node - -fun main() { - val nd = Node(2) - println(nd.elem) +fun main() = application { + val icon = painterResource("icon.png") + Window( + onCloseRequest = ::exitApplication, + title = "Trees", + state = rememberWindowState(width = 300.dp, height = 300.dp), + icon = icon + ) { + Text("Cats are the best!") + } } From f333574b994d68cad56ce692d66cb58fc18f27b5 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 22 Apr 2023 21:25:40 +0300 Subject: [PATCH 168/296] feat: Add some ui basics --- app/src/main/kotlin/org/tree/app/App.kt | 25 ++++- .../tree/app/controller/io/TreeController.kt | 54 +++++++++++ app/src/main/kotlin/org/tree/app/view/Line.kt | 28 ++++++ app/src/main/kotlin/org/tree/app/view/Node.kt | 93 +++++++++++++++++++ .../main/kotlin/org/tree/app/view/NodeView.kt | 23 ----- app/src/main/kotlin/org/tree/app/view/Tree.kt | 67 +++++++++++++ 6 files changed, 265 insertions(+), 25 deletions(-) create mode 100644 app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt create mode 100644 app/src/main/kotlin/org/tree/app/view/Line.kt create mode 100644 app/src/main/kotlin/org/tree/app/view/Node.kt delete mode 100644 app/src/main/kotlin/org/tree/app/view/NodeView.kt create mode 100644 app/src/main/kotlin/org/tree/app/view/Tree.kt diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 1c52d1fa..b62c1bc4 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -3,12 +3,20 @@ */ package org.tree.app -import androidx.compose.material.Text +import TreeController +import androidx.compose.foundation.layout.Box +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale 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 org.tree.app.view.Tree +import org.tree.binaryTree.KVP +import org.tree.binaryTree.trees.RBTree +import kotlin.random.Random + fun main() = application { val icon = painterResource("icon.png") @@ -18,6 +26,19 @@ fun main() = application { state = rememberWindowState(width = 300.dp, height = 300.dp), icon = icon ) { - Text("Cats are the best!") + Box(Modifier.scale(1.0F)) { + + val t = RBTree>() + val rand = Random(0x33) + for (i in 0..10) { + t.insert(KVP(rand.nextInt(100), "good - $i")) + } + + val k = TreeController(t, 50) + Tree(k) + //Line(10.5.dp, 100.dp, 150.dp, 150.dp) + + + } } } diff --git a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt new file mode 100644 index 00000000..e6404737 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt @@ -0,0 +1,54 @@ +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color +import org.tree.binaryTree.KVP +import org.tree.binaryTree.RBNode +import org.tree.binaryTree.templates.TemplateBSTree +import org.tree.binaryTree.templates.TemplateNode +import kotlin.math.pow + +data class NodeExtension(var x: MutableState, var y: MutableState, var color: Color = Color.Gray) +class TreeController, NODE_T>>( + val tree: TemplateBSTree, NODE_T>, + val nodeSize: Int = 20 +) { + val nodes = mutableMapOf() + + fun addNode(curNode: NODE_T, x: Int, y: Int, height: Int) { + val stateX = mutableStateOf(x) + val stateY = mutableStateOf(y) + val col = if (curNode is RBNode<*>) { + if (curNode.col == RBNode.Colour.BLACK) { + Color.DarkGray + } else { + Color.Red + } + } else { + Color.Gray + } + nodes[curNode] = NodeExtension(stateX, stateY, col) + val deltaX = nodeSize * (2).toDouble().pow(height.toDouble()).toInt() + curNode.left?.let { + addNode(it, x + deltaX, y + nodeSize, height - 1) + } + curNode.right?.let { + addNode(it, x - deltaX, y + nodeSize, height - 1) + } + + } + + private fun height(curNode: NODE_T?): Int { + return if (curNode == null) { + 0 + } else { + Integer.max(height(curNode.left), height(curNode.right)) + 1 + } + } + + init { + tree.root?.let { + addNode(it, 100, 100, height(it) - 2) + } + } + +} diff --git a/app/src/main/kotlin/org/tree/app/view/Line.kt b/app/src/main/kotlin/org/tree/app/view/Line.kt new file mode 100644 index 00000000..b844f242 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/Line.kt @@ -0,0 +1,28 @@ +package org.tree.app.view + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun Line(x0: MutableState, y0: MutableState, x1: MutableState, y1: MutableState) { + val x0 = x0.value.dp + val x1 = x1.value.dp + val y0 = y0.value.dp + val y1 = y1.value.dp + Box(modifier = Modifier.offset(x0, y0)) { + Box( + modifier = Modifier.size(x1 - x0, y1 - y0) + .drawBehind { drawLine(Color.Black, Offset.Zero, Offset((x1 - x0).toPx(), (y1 - y0).toPx()), 1f) } + ) { + + } + } +} diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt new file mode 100644 index 00000000..45607b9e --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -0,0 +1,93 @@ +package org.tree.app.view + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.TooltipArea +import androidx.compose.foundation.TooltipPlacement +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import org.tree.binaryTree.KVP +import org.tree.binaryTree.templates.TemplateNode + +class NodeView, NODE_T>>(nd: NODE_T) { // legacy code + var node: NODE_T = nd + var x: Double = 0.0 + var y: Double = 0.0 + var l: NodeView? = null + var r: NodeView? = null + + init { + node.left?.let { + l = NodeView(it) + } + node.right?.let { + r = NodeView(it) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun Node( + x: MutableState, + y: MutableState, + key: Int, value: String, + modifier: Modifier = Modifier, + color: Color = Color.Gray, + size: Int = 10, +) { + println("Compose $key") + Box(modifier = Modifier.fillMaxSize()) { + TooltipArea( + tooltip = { + Surface( + modifier = Modifier.shadow(4.dp), + color = Color(255, 255, 210), + shape = RoundedCornerShape(4.dp) + ) { + Text( + text = "value: $value", + modifier = Modifier.padding(10.dp) + ) + } + }, + modifier = Modifier.offset { IntOffset(x.value - size / 2, y.value - size / 2) }, + delayMillis = 600, + tooltipPlacement = TooltipPlacement.CursorPoint( + alignment = Alignment.BottomEnd, + offset = DpOffset(5.dp, 5.dp) + ) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .size(size.dp).clip(CircleShape).background(color = color) + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + x.value += dragAmount.x.toInt() + y.value += dragAmount.y.toInt() + } + } + ) { + Text(key.toString()) + } + } + } + +} diff --git a/app/src/main/kotlin/org/tree/app/view/NodeView.kt b/app/src/main/kotlin/org/tree/app/view/NodeView.kt deleted file mode 100644 index 53e788f7..00000000 --- a/app/src/main/kotlin/org/tree/app/view/NodeView.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.tree.app.view - -import org.tree.binaryTree.KVP -import org.tree.binaryTree.templates.TemplateNode - -class NodeView, NODE_T>> { // it is just sketch for import/export - var node: NODE_T - var x: Double = 0.0 - var y: Double = 0.0 - var l: NodeView? = null - var r: NodeView? = null - //var colour: String = "#FFFFFF" - - constructor(nd: NODE_T) { - node = nd - node.left?.let { - l = NodeView(it) - } - node.right?.let { - r = NodeView(it) - } - } -} diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt new file mode 100644 index 00000000..8830994c --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -0,0 +1,67 @@ +package org.tree.app.view + +import TreeController +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.IntOffset +import org.tree.binaryTree.KVP +import org.tree.binaryTree.templates.TemplateNode +import kotlin.math.roundToInt + +@Composable +fun , NODE_T>> Tree(t: TreeController) { + Box { + var offsetX by remember { mutableStateOf(0f) } + var offsetY by remember { mutableStateOf(0f) } + + Box( + Modifier + .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) } + .background(Color.Green) //for debug + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + offsetX += dragAmount.x + offsetY += dragAmount.y + } + } + ) { + for (n in t.nodes) { + val x = n.value.x + val y = n.value.y + + with(n.key) { + val l = t.nodes[left] + if (l != null) { + Line(x, y, l.x, l.y) + } + + val r = t.nodes[right] + if (r != null) { + Line(x, y, r.x, r.y) + } + } + } + + for (n in t.nodes) { + val x = n.value.x + val y = n.value.y + val col = n.value.color + with(n.key.elem) { + key(key) { + Node(x, y, key, v ?: "", size = t.nodeSize, color = col) + } + } + } + } + } + + +} + From ee89cef980d8db7d3cb898a94b6c5fd81b2d7abd Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 22 Apr 2023 23:54:38 +0300 Subject: [PATCH 169/296] feat: Add menubar --- app/src/main/kotlin/org/tree/app/App.kt | 59 +++++++++++++++---- .../tree/app/controller/io/TreeController.kt | 39 +++++++++--- .../org/tree/app/view/dialogs/io/ImportRB.kt | 32 ++++++++++ 3 files changed, 111 insertions(+), 19 deletions(-) create mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index b62c1bc4..e8a963a7 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -5,40 +5,79 @@ package org.tree.app import TreeController import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import newTree import org.tree.app.view.Tree -import org.tree.binaryTree.KVP +import org.tree.app.view.dialogs.io.ImportRBDialog +import org.tree.binaryTree.trees.AVLTree +import org.tree.binaryTree.trees.BinSearchTree import org.tree.binaryTree.trees.RBTree -import kotlin.random.Random +enum class DialogType { + EMPTY, + IMPORT_RB, + IMPORT_AVL, + IMPORT_BST +} fun main() = application { val icon = painterResource("icon.png") Window( onCloseRequest = ::exitApplication, title = "Trees", - state = rememberWindowState(width = 300.dp, height = 300.dp), + state = rememberWindowState(width = 800.dp, height = 600.dp), icon = icon ) { + var treeController by remember { mutableStateOf>(newTree(BinSearchTree())) } + var dialogType by remember { mutableStateOf(DialogType.EMPTY) } + + MenuBar { + Menu("File", mnemonic = 'F') { + Menu("New tree", mnemonic = 'N') { + Item("Bin Search Tree", onClick = { treeController = newTree(BinSearchTree()) }) + Item("Red black Tree", onClick = { treeController = newTree(RBTree()) }) + Item("AVL Tree") { treeController = newTree(AVLTree()) } + } + Menu("Open", mnemonic = 'O') { + Item("Bin Search Tree", onClick = { dialogType = DialogType.IMPORT_BST }) + Item("Red black Tree", onClick = { dialogType = DialogType.IMPORT_RB }) + Item("AVL Tree", onClick = { dialogType = DialogType.IMPORT_AVL }) + } + } + } + Box(Modifier.scale(1.0F)) { + Tree(treeController) + } - val t = RBTree>() - val rand = Random(0x33) - for (i in 0..10) { - t.insert(KVP(rand.nextInt(100), "good - $i")) + when (dialogType) { + DialogType.EMPTY -> { } - val k = TreeController(t, 50) - Tree(k) - //Line(10.5.dp, 100.dp, 150.dp, 150.dp) + DialogType.IMPORT_AVL -> { + TODO("Implement") + dialogType = DialogType.EMPTY + } + DialogType.IMPORT_BST -> { + TODO("Implement") + dialogType = DialogType.EMPTY + } + DialogType.IMPORT_RB -> { + ImportRBDialog(onCloseRequest = { dialogType = DialogType.EMPTY }, onSuccess = { treeController = it }) + } } } } diff --git a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt index e6404737..b7704a82 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt @@ -1,11 +1,14 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color +import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP +import org.tree.binaryTree.Node import org.tree.binaryTree.RBNode import org.tree.binaryTree.templates.TemplateBSTree import org.tree.binaryTree.templates.TemplateNode import kotlin.math.pow +import kotlin.random.Random data class NodeExtension(var x: MutableState, var y: MutableState, var color: Color = Color.Gray) class TreeController, NODE_T>>( @@ -17,15 +20,7 @@ class TreeController, NODE_T>>( fun addNode(curNode: NODE_T, x: Int, y: Int, height: Int) { val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) - val col = if (curNode is RBNode<*>) { - if (curNode.col == RBNode.Colour.BLACK) { - Color.DarkGray - } else { - Color.Red - } - } else { - Color.Gray - } + val col = getNodeCol(curNode) nodes[curNode] = NodeExtension(stateX, stateY, col) val deltaX = nodeSize * (2).toDouble().pow(height.toDouble()).toInt() curNode.left?.let { @@ -37,6 +32,22 @@ class TreeController, NODE_T>>( } + private fun getNodeCol(curNode: NODE_T): Color { + return if (curNode is RBNode<*>) { + if (curNode.col == RBNode.Colour.BLACK) { + Color.DarkGray + } else { + Color.Red + } + } else if (curNode is AVLNode<*>) { + Color.Blue + } else if (curNode is Node<*>) { + Color.Yellow + } else { + Color.Gray + } + } + private fun height(curNode: NODE_T?): Int { return if (curNode == null) { 0 @@ -52,3 +63,13 @@ class TreeController, NODE_T>>( } } + +fun , NODE_T>, TREE_T : TemplateBSTree, NODE_T>> newTree( + emptyTree: TREE_T +): TreeController { + val rand = Random(0x1337) + for (i in 0..10) { + emptyTree.insert(KVP(rand.nextInt(100), "Num: $i")) + } + return TreeController(emptyTree) +} diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt new file mode 100644 index 00000000..d0d435b7 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt @@ -0,0 +1,32 @@ +package org.tree.app.view.dialogs.io + +import TreeController +import androidx.compose.foundation.layout.Column +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.window.Dialog +import org.tree.binaryTree.KVP +import org.tree.binaryTree.RBNode + +@Composable +fun ImportRBDialog(onCloseRequest: () -> Unit, onSuccess: (TreeController>>) -> Unit = {}) { + var isDialogOpen by remember { mutableStateOf(true) } + + if (isDialogOpen) { + Dialog(title = "Import Red-Black Tree", + onCloseRequest = { + isDialogOpen = false + }) { + Column { + Text("Клоун! Не отрефакторил код.") + Button(onClick = { isDialogOpen = false }) { + Text("Пип-Пип!") + } + } + } + } else { + onCloseRequest() + } +} + From f62e471c0f10399d5a8df75295649f7a04ecd699 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 13:27:09 +0300 Subject: [PATCH 170/296] fix: Fix immovable nodes after recreate --- app/src/main/kotlin/org/tree/app/view/Node.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 45607b9e..2c2eb1b4 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -51,7 +51,6 @@ fun Node( color: Color = Color.Gray, size: Int = 10, ) { - println("Compose $key") Box(modifier = Modifier.fillMaxSize()) { TooltipArea( tooltip = { @@ -77,7 +76,7 @@ fun Node( contentAlignment = Alignment.Center, modifier = modifier .size(size.dp).clip(CircleShape).background(color = color) - .pointerInput(Unit) { + .pointerInput(x, y) { detectDragGestures { change, dragAmount -> change.consume() x.value += dragAmount.x.toInt() From 62dde78e60c3e9fa448fe2bef6f13184d2a06758 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 15:29:00 +0300 Subject: [PATCH 171/296] feat: Add insert, remove and find --- app/src/main/kotlin/org/tree/app/App.kt | 89 +++++++++++++++++-- .../tree/app/controller/io/TreeController.kt | 35 ++++++-- app/src/main/kotlin/org/tree/app/view/Tree.kt | 22 ++--- 3 files changed, 122 insertions(+), 24 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index e8a963a7..bd76e7a3 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -5,10 +5,12 @@ package org.tree.app import TreeController import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material.Button +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.res.painterResource @@ -20,6 +22,7 @@ import androidx.compose.ui.window.rememberWindowState import newTree import org.tree.app.view.Tree import org.tree.app.view.dialogs.io.ImportRBDialog +import org.tree.binaryTree.KVP import org.tree.binaryTree.trees.AVLTree import org.tree.binaryTree.trees.BinSearchTree import org.tree.binaryTree.trees.RBTree @@ -31,6 +34,50 @@ enum class DialogType { IMPORT_BST } +@Composable +fun InsertRow(onClick: (key: Int, value: String) -> Unit) { + var keyString by remember { mutableStateOf("123") } + var valueString by remember { mutableStateOf("value") } + + Row { + Button(onClick = { + onClick(keyString.toInt(), valueString) + }) { + Text("Insert") + } + OutlinedTextField(value = keyString, onValueChange = { keyString = it }) + OutlinedTextField(value = valueString, onValueChange = { valueString = it }) + } +} + +@Composable +fun RemoveRow(onClick: (key: Int) -> Unit) { + var keyString by remember { mutableStateOf("123") } + + Row { + Button(onClick = { + onClick(keyString.toInt()) + }) { + Text("Remove") + } + OutlinedTextField(value = keyString, onValueChange = { keyString = it }) + } +} + +@Composable +fun FindRow(onClick: (key: Int) -> (Unit)) { + var keyString by remember { mutableStateOf("123") } + + Row { + Button(onClick = { + onClick(keyString.toInt()) + }) { + Text("Find") + } + OutlinedTextField(value = keyString, onValueChange = { keyString = it }) + } +} + fun main() = application { val icon = painterResource("icon.png") Window( @@ -39,8 +86,15 @@ fun main() = application { state = rememberWindowState(width = 800.dp, height = 600.dp), icon = icon ) { - var treeController by remember { mutableStateOf>(newTree(BinSearchTree())) } + var treeController by remember { + mutableStateOf>( + newTree(BinSearchTree()) + ) + } var dialogType by remember { mutableStateOf(DialogType.EMPTY) } + val treeOffsetX = remember { mutableStateOf(100) } + val treeOffsetY = remember { mutableStateOf(100) } + MenuBar { Menu("File", mnemonic = 'F') { @@ -57,8 +111,25 @@ fun main() = application { } } - Box(Modifier.scale(1.0F)) { - Tree(treeController) + Row { + Column { + InsertRow(onClick = { key, value -> + treeController = treeController.insert(KVP(key, value)) + }) + RemoveRow(onClick = { key -> + treeController = treeController.remove(KVP(key)) + }) + FindRow(onClick = { key -> + val a = treeController.find(KVP(key)) + if (a != null) { + treeOffsetX.value = -a.x.value + treeOffsetY.value = -a.y.value + } + }) + } + Box(Modifier.scale(1.0F)) { + Tree(treeController, treeOffsetX, treeOffsetY) + } } when (dialogType) { @@ -76,7 +147,9 @@ fun main() = application { } DialogType.IMPORT_RB -> { - ImportRBDialog(onCloseRequest = { dialogType = DialogType.EMPTY }, onSuccess = { treeController = it }) + ImportRBDialog( + onCloseRequest = { dialogType = DialogType.EMPTY }, + onSuccess = { treeController = it }) } } } diff --git a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt index b7704a82..e52e4443 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt @@ -17,6 +17,12 @@ class TreeController, NODE_T>>( ) { val nodes = mutableMapOf() + init { + tree.root?.let { + addNode(it, 0, 0, height(it) - 2) + } + } + fun addNode(curNode: NODE_T, x: Int, y: Int, height: Int) { val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) @@ -29,7 +35,29 @@ class TreeController, NODE_T>>( curNode.right?.let { addNode(it, x - deltaX, y + nodeSize, height - 1) } + } + + fun insert(obj: KVP): TreeController { + var res = this + val isChanged = tree.insert(obj) + if (isChanged) { + res = TreeController(tree, nodeSize) + } + return res + } + + fun remove(obj: KVP): TreeController { + var res = this + val isChanged = tree.remove(obj) + if (isChanged) { + res = TreeController(tree, nodeSize) + } + return res + } + fun find(obj: KVP): NodeExtension? { + val node = tree.find(obj) + return nodes[node] } private fun getNodeCol(curNode: NODE_T): Color { @@ -56,11 +84,6 @@ class TreeController, NODE_T>>( } } - init { - tree.root?.let { - addNode(it, 100, 100, height(it) - 2) - } - } } @@ -71,5 +94,5 @@ fun , NODE_T>, TREE_T : TemplateBSTree, NODE_T>> Tree(t: TreeController) { +fun , NODE_T>> Tree( + t: TreeController, + offsetX: MutableState, + offsetY: MutableState, +) { Box { - var offsetX by remember { mutableStateOf(0f) } - var offsetY by remember { mutableStateOf(0f) } - Box( Modifier - .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) } + .offset { IntOffset(offsetX.value, offsetY.value) } .background(Color.Green) //for debug - .pointerInput(Unit) { + .pointerInput(offsetX, offsetY) { detectDragGestures { change, dragAmount -> change.consume() - offsetX += dragAmount.x - offsetY += dragAmount.y + offsetX.value += dragAmount.x.toInt() + offsetY.value += dragAmount.y.toInt() } } ) { From 8ecd2e438f22f781cbb93c8f0ee59d318a6dd358 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 15:47:42 +0300 Subject: [PATCH 172/296] fix: Add lost dependencies --- app/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b94b96cc..9f133777 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,6 +30,8 @@ dependencies { implementation("org.jetbrains.exposed:exposed-core:0.40.1") implementation("org.jetbrains.exposed:exposed-dao:0.40.1") implementation("org.jetbrains.exposed:exposed-jdbc:0.40.1") + + implementation(compose.desktop.currentOs) } application { From 08accf70a1e579193ee8ded15bafcf86558eb552 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 17:16:56 +0300 Subject: [PATCH 173/296] fix: Fix broken GUI for windows --- app/src/main/kotlin/org/tree/app/view/Node.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 2c2eb1b4..f8788a00 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import org.tree.binaryTree.KVP import org.tree.binaryTree.templates.TemplateNode @@ -65,7 +64,7 @@ fun Node( ) } }, - modifier = Modifier.offset { IntOffset(x.value - size / 2, y.value - size / 2) }, + modifier = Modifier.offset((x.value - size / 2).dp, (y.value - size / 2).dp), delayMillis = 600, tooltipPlacement = TooltipPlacement.CursorPoint( alignment = Alignment.BottomEnd, From c305018a0d0cd604488c2e7ad52c1e7386986557 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 21:42:58 +0300 Subject: [PATCH 174/296] fix: Fix default node size --- .../main/kotlin/org/tree/app/controller/io/TreeController.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt index e52e4443..ce3eabbe 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt @@ -13,7 +13,7 @@ import kotlin.random.Random data class NodeExtension(var x: MutableState, var y: MutableState, var color: Color = Color.Gray) class TreeController, NODE_T>>( val tree: TemplateBSTree, NODE_T>, - val nodeSize: Int = 20 + val nodeSize: Int = 50 ) { val nodes = mutableMapOf() @@ -94,5 +94,5 @@ fun , NODE_T>, TREE_T : TemplateBSTree Date: Sun, 23 Apr 2023 23:24:40 +0300 Subject: [PATCH 175/296] refactor: Move TreeController.kt --- .../kotlin/org/tree/app/controller/{io => }/TreeController.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/kotlin/org/tree/app/controller/{io => }/TreeController.kt (100%) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt similarity index 100% rename from app/src/main/kotlin/org/tree/app/controller/io/TreeController.kt rename to app/src/main/kotlin/org/tree/app/controller/TreeController.kt From 974c973e08a12413b49ed2c4343dacba252cb55f Mon Sep 17 00:00:00 2001 From: David Date: Mon, 24 Apr 2023 13:37:39 +0300 Subject: [PATCH 176/296] fix(SQLiteIO): Rewrite SQLiteIO with support for the new architecture --- .../org/tree/app/controller/io/SQLiteIO.kt | 125 ++++++++++-------- 1 file changed, 67 insertions(+), 58 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index d281555d..4ae39c30 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -1,5 +1,8 @@ package org.tree.app.controller.io +import NodeExtension +import TreeController +import androidx.compose.runtime.mutableStateOf import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID @@ -10,9 +13,9 @@ import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.StdOutSqlLogger import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.transaction -import org.tree.app.view.NodeView import org.tree.binaryTree.KVP import org.tree.binaryTree.Node +import org.tree.binaryTree.trees.BinSearchTree import java.io.File import java.io.IOException import java.nio.file.Files @@ -21,8 +24,8 @@ object Nodes : IntIdTable() { val key = integer("key") val parentKey = integer("parentKey").nullable() val value = text("value") - val x = float("x") - val y = float("y") + val x = integer("x") + val y = integer("y") } class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with data in SQLite @@ -37,25 +40,26 @@ class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with class SQLiteIO { private var amountOfNodesToHandle = 0 - fun importTree(file: File): NodeView>>? { + private lateinit var treeController : TreeController>> + fun importTree(file: File): TreeController>> { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") - var root: NodeView>>? = null try { transaction { addLogger(StdOutSqlLogger) - val amountOfNodes = InstanceOfNode.all().count() + val setOfNodes = InstanceOfNode.all().toMutableSet() + val amountOfNodes = setOfNodes.count() if (amountOfNodes > 0) { - val setOfNodes = InstanceOfNode.all().toMutableSet() - root = parseRootForImport(setOfNodes) + treeController = TreeController(BinSearchTree()) + parseRootForImport(setOfNodes) } } } catch (ex: ExposedSQLException) { throw IOException("File is not a database", ex) } - return root + return treeController } - fun exportTree(rootOfBinTree: NodeView>>, file: File) { + fun exportTree(treeController_: TreeController>>, file: File) { try { Files.createDirectories(file.toPath().parent) } catch (ex: SecurityException) { @@ -70,27 +74,33 @@ class SQLiteIO { } catch (ex: ExposedSQLException) { throw IOException("File is not a database", ex) } - parseNodesForExport(rootOfBinTree, null) + treeController = treeController_ + val root = treeController.tree.root + if (root != null) { + parseNodesForExport(root, null) + } } } private fun parseRootForImport( - setOfNodeView: MutableSet - ): NodeView>> { + setOfNodes: MutableSet + ) { try { - val nodeView = setOfNodeView.elementAt(0) - setOfNodeView.remove(nodeView) - val parsedKey = nodeView.key - val parsedValue = nodeView.value - val parsedX: Double = nodeView.x.toDouble() - val parsedY: Double = nodeView.y.toDouble() - val newNodeView = NodeView(Node(KVP(parsedKey, parsedValue))) - addCoordinatesForNodeView(newNodeView, parsedX, parsedY) - newNodeView.node = Node(KVP(parsedKey, parsedValue)) - amountOfNodesToHandle = setOfNodeView.count() - parseNodesForImport(setOfNodeView, newNodeView) - return newNodeView + val node = setOfNodes.elementAt(0) + setOfNodes.remove(node) + val parsedKey = node.key + val parsedValue = node.value + val parsedX = node.x + val parsedY = node.y + val bst = treeController.tree + amountOfNodesToHandle = setOfNodes.count() + bst.insert(KVP(parsedKey, parsedValue)) + val root = bst.root + if (root != null) { + addCoordinatesToNode(root, parsedX, parsedY) + parseNodesForImport(setOfNodes, root) + } } catch (ex: NumberFormatException) { throw IOException( "Node keys must be integers, value must be string and coordinates must be doubles", @@ -100,38 +110,35 @@ class SQLiteIO { } private fun parseNodesForImport( - setOfNodeView: MutableSet, - curNode: NodeView>> + setOfNodes: MutableSet, + curNode: Node> ) { if (amountOfNodesToHandle <= 0) { return } - val nodeView = setOfNodeView.elementAt(0) - val parsedKey = nodeView.key - val parsedParentKey: Int = nodeView.parentKey ?: throw IOException("Incorrect binary tree") - val parsedValue = nodeView.value - val parsedX: Double = nodeView.x.toDouble() - val parsedY: Double = nodeView.y.toDouble() - if (parsedParentKey == curNode.node.elem.key) { + val nodes = setOfNodes.elementAt(0) + val parsedKey = nodes.key + val parsedParentKey: Int = nodes.parentKey ?: throw IOException("Incorrect binary tree") + val parsedValue = nodes.value + val parsedX = nodes.x + val parsedY = nodes.y + if (parsedParentKey == curNode.elem.key) { val newNode = Node(KVP(parsedKey, parsedValue)) - val newNodeView = NodeView(newNode) - addCoordinatesForNodeView(newNodeView, parsedX, parsedY) - setOfNodeView.remove(nodeView) + addCoordinatesToNode(newNode, parsedX, parsedY) + setOfNodes.remove(nodes) amountOfNodesToHandle-- if (parsedKey < parsedParentKey) { - curNode.node.left = newNode - curNode.l = newNodeView - val leftChild = curNode.l + curNode.left = newNode + val leftChild = curNode.left if (leftChild != null) { - parseNodesForImport(setOfNodeView, leftChild) + parseNodesForImport(setOfNodes, leftChild) } - parseNodesForImport(setOfNodeView, curNode) + parseNodesForImport(setOfNodes, curNode) } else if (parsedKey > parsedParentKey) { - curNode.node.right = newNode - curNode.r = newNodeView - val rightChild = curNode.r + curNode.right = newNode + val rightChild = curNode.right if (rightChild != null) { - parseNodesForImport(setOfNodeView, rightChild) + parseNodesForImport(setOfNodes, rightChild) } } else { throw IOException("Incorrect binary tree") @@ -140,28 +147,30 @@ class SQLiteIO { } private fun parseNodesForExport( - curNode: NodeView>>, - parNode: NodeView>>? + curNode: Node>, + parNode: Node>? ) { InstanceOfNode.new { - key = curNode.node.elem.key - parentKey = parNode?.node?.elem?.key - value = curNode.node.elem.v.toString() - x = curNode.x.toFloat() - y = curNode.y.toFloat() + key = curNode.elem.key + parentKey = parNode?.elem?.key + value = curNode.elem.v.toString() + x = treeController.nodes[curNode]?.x?.value ?: 0 + y = treeController.nodes[curNode]?.y?.value ?: 0 } - val leftChild = curNode.l + val leftChild = curNode.left if (leftChild != null) { parseNodesForExport(leftChild, curNode) } - val rightChild = curNode.r + val rightChild = curNode.right if (rightChild != null) { parseNodesForExport(rightChild, curNode) } } - private fun addCoordinatesForNodeView(nodeView: NodeView>>, x: Double, y: Double) { - nodeView.x = x - nodeView.y = y + private fun addCoordinatesToNode( + node: Node>, + x: Int, y: Int + ) { + treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y)) } } From 0915460602fff27d5b0f85ba61e36d0207c25573 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 24 Apr 2023 19:35:09 +0300 Subject: [PATCH 177/296] feat(README): Update Readme --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a294cd6..bb1befa9 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,22 @@ We have functions: ### SQLite -TODO: write +At SQLite database we store BinSearchTree. There are 2 types of objects with the following struct: + +- `Tree` + - `root` *Node* +- `Node` + - `key` *int* + - `parentKey` *int* + - `value` *string* + - `x` *int* - node position at ui + - `y` *int* - node position at ui + +We have functions: + +- `exportTree(TreeController, file)` - writes information stored in TreeController object to a `file` +- `importTree(file)` - reads the tree stored in the `file` and returns TreeController object + ### Neo4j From ae344e4c0c113b9aac8ef8a7aecfa051f4c8dbf2 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 18:11:30 +0300 Subject: [PATCH 178/296] fix(Neo4jIO): Rewrite export --- .../org/tree/app/controller/io/Neo4jIO.kt | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index c5b6e998..8761d8b4 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -1,5 +1,7 @@ package org.tree.app.controller.io +import NodeExtension +import TreeController import org.neo4j.driver.* import org.neo4j.driver.exceptions.SessionExpiredException import org.neo4j.driver.exceptions.value.Uncoercible @@ -25,13 +27,14 @@ class Neo4jIO() : Closeable { } fun exportRBTree( - root: NodeView>>, + treeController: TreeController>>, treeName: String = "Tree" ) { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") + val root = treeController.tree.root session.executeWrite { tx -> deleteTree(tx, treeName) - exportRBNode(tx, root) + exportRBNode(tx, root, treeController.nodes) tx.run( "MATCH (p: $RBNODE) " + "MATCH (l: $NEW_NODE {key: p.lkey}) " + @@ -98,34 +101,35 @@ class Neo4jIO() : Closeable { private fun exportRBNode( tx: TransactionContext, - nodeView: NodeView>>, + curNode: RBNode>?, + nodes: MutableMap>, NodeExtension> ) { - with(nodeView) { - val lkey = l?.node?.elem?.key - val rkey = r?.node?.elem?.key - - tx.run( - "CREATE (:$RBNODE:$NEW_NODE {key : \$key, " + - "value: \$value, " + - "x: \$x, y: \$y, " + - "isBlack: \$isBlack, " + - "lkey: \$lkey, " + - "rkey: \$rkey" + - "})", - mutableMapOf( - "key" to node.elem.key, - "value" to (node.elem.v ?: ""), - "x" to x, "y" to y, - "isBlack" to (node.col == RBNode.Colour.BLACK), - "lkey" to lkey, - "rkey" to rkey - ) as Map? - ) - l?.let { - exportRBNode(tx, it) - } - r?.let { - exportRBNode(tx, it) + if (curNode != null) { + val lkey = curNode.left?.elem?.key + val rkey = curNode.right?.elem?.key + val ext = nodes[curNode] + if (ext == null) { + throw IOException("Can't find coordinates for node with key ${curNode.elem.key}") + } else { + tx.run( + "CREATE (:$RBNODE:$NEW_NODE {key : \$key, " + + "value: \$value, " + + "x: \$x, y: \$y, " + + "isBlack: \$isBlack, " + + "lkey: \$lkey, " + + "rkey: \$rkey" + + "})", + mutableMapOf( + "key" to curNode.elem.key, + "value" to (curNode.elem.v ?: ""), + "x" to ext.x, "y" to ext.y, + "isBlack" to (curNode.col == RBNode.Colour.BLACK), + "lkey" to lkey, + "rkey" to rkey + ) as Map? + ) + exportRBNode(tx, curNode.left, nodes) + exportRBNode(tx, curNode.right, nodes) } } } From bd8de3ae529b14663fbe60fabc14f1512b7ecb12 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 18:48:40 +0300 Subject: [PATCH 179/296] fix(Neo4jIO): Rewrite import --- .../org/tree/app/controller/io/Neo4jIO.kt | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 8761d8b4..5e76c525 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -2,12 +2,14 @@ package org.tree.app.controller.io import NodeExtension import TreeController +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color import org.neo4j.driver.* import org.neo4j.driver.exceptions.SessionExpiredException import org.neo4j.driver.exceptions.value.Uncoercible -import org.tree.app.view.NodeView import org.tree.binaryTree.KVP import org.tree.binaryTree.RBNode +import org.tree.binaryTree.trees.RBTree import java.io.Closeable import java.io.IOException @@ -61,10 +63,13 @@ class Neo4jIO() : Closeable { } - fun importRBTree(treeName: String = "Tree"): NodeView>>? { // when we have treeView, fun will be rewritten + fun importRBTree(treeName: String = "Tree"): TreeController>> { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") - val res: NodeView>>? = session.executeRead { tx -> - importRBNodes(tx, treeName) + val res: TreeController>> = session.executeRead { tx -> + val tree = RBTree>() + val treeController = TreeController(tree) + importRBNodes(tx, treeController, treeName) + treeController } session.close() return res @@ -122,7 +127,7 @@ class Neo4jIO() : Closeable { mutableMapOf( "key" to curNode.elem.key, "value" to (curNode.elem.v ?: ""), - "x" to ext.x, "y" to ext.y, + "x" to ext.x.value, "y" to ext.y.value, "isBlack" to (curNode.col == RBNode.Colour.BLACK), "lkey" to lkey, "rkey" to rkey @@ -134,7 +139,11 @@ class Neo4jIO() : Closeable { } } - private fun importRBNodes(tx: TransactionContext, treeName: String): NodeView>>? { + private fun importRBNodes( + tx: TransactionContext, + treeController: TreeController>>, + treeName: String + ) { val nodeAndKeysRecords = tx.run( "MATCH (:$TREE {name: \$treeName})-[*]->(p: $RBNODE)" + "OPTIONAL MATCH (p)-[: $LCHILD]->(l: $RBNODE) " + @@ -145,33 +154,38 @@ class Neo4jIO() : Closeable { "treeName" to treeName ) as Map? ) // for all nodes get their properties + keys of their children - return parseRBNodes(nodeAndKeysRecords) + return parseRBNodes(nodeAndKeysRecords, treeController) } private class NodeAndKeys( - val nv: NodeView>>, + val nd: RBNode>, val lkey: Int?, val rkey: Int? ) - private fun parseRBNodes(nodeAndKeysRecords: Result): NodeView>>? { + private fun parseRBNodes(nodeAndKeysRecords: Result, treeController: TreeController>>) { val key2nk = mutableMapOf() for (nkRecord in nodeAndKeysRecords) { try { val key = nkRecord["key"].asInt() val value = nkRecord["value"].asString() - val nv = NodeView(RBNode(null, KVP(key, value))) + val node = RBNode(null, KVP(key, value)) - nv.x = nkRecord["x"].asDouble() - nv.y = nkRecord["y"].asDouble() + val x = nkRecord["x"].asInt() + val y = nkRecord["y"].asInt() + val color: Color val isBlack = nkRecord["isBlack"].asBoolean() - nv.node.col = if (isBlack) { + node.col = if (isBlack) { + color = Color.DarkGray RBNode.Colour.BLACK } else { + color = Color.Red RBNode.Colour.RED } + treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), color) + val lkey = if (nkRecord["lKey"].isNull) { null } else { @@ -183,40 +197,31 @@ class Neo4jIO() : Closeable { nkRecord["rKey"].asInt() } - key2nk[key] = NodeAndKeys(nv, lkey, rkey) + key2nk[key] = NodeAndKeys(node, lkey, rkey) } catch (ex: Uncoercible) { throw IOException("Invalid nodes label in the database", ex) } } val nks = key2nk.values.toTypedArray() - if (nks.isEmpty()) { // if nodeAndKeysRecords was empty - return null - } + for (nk in nks) { nk.lkey?.let { - nk.nv.l = key2nk[it]?.nv + nk.nd.left = key2nk[it]?.nd + nk.nd.left?.parent = nk.nd key2nk.remove(it) } - nk.nv.l?.let { - nk.nv.node.left = it.node - it.node.parent = nk.nv.node - } nk.rkey?.let { - nk.nv.r = key2nk[it]?.nv + nk.nd.right = key2nk[it]?.nd + nk.nd.right?.parent = nk.nd key2nk.remove(it) } - nk.nv.r?.let { - nk.nv.node.right = it.node - it.node.parent = nk.nv.node - } } if (key2nk.values.size != 1) { throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") } - val root = key2nk.values.first().nv - return root + treeController.tree.root = key2nk.values.first().nd } private fun parseNames(nameRecords: Result): MutableList { From 15fe1fcb6feea628a0a1a0fb693b125b865b0548 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 21:40:13 +0300 Subject: [PATCH 180/296] fix(Neo4jIO): Edit Neo4j specification --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb1befa9..13bc57b3 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,8 @@ At neo4j database we store **RBTree**. There are 2 types of labels with the foll - `key` *int* - `value` *string* - `isBlack` *boolean* - node colour - - `x` *double* - node position at ui - - `y` *double* - node position at ui + - `x` *int* - node position at ui + - `y` *int* - node position at ui Also, there are 3 types of links: From ca004c674155a21187f3f21427ca3ca847a9c751 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 23:59:50 +0300 Subject: [PATCH 181/296] feat: Add error handling --- .../app/controller/io/HandledIOException.kt | 14 +++ .../org/tree/app/controller/io/Neo4jIO.kt | 105 ++++++++++++------ 2 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt diff --git a/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt b/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt new file mode 100644 index 00000000..a1a9be86 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt @@ -0,0 +1,14 @@ +package org.tree.app.controller.io + +import java.io.IOException + + +class HandledIOException : IOException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(message: String, cause: Throwable) : super(message, cause) + +} diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 5e76c525..bedf3478 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -5,6 +5,9 @@ import TreeController import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import org.neo4j.driver.* +import org.neo4j.driver.exceptions.AuthenticationException +import org.neo4j.driver.exceptions.ClientException +import org.neo4j.driver.exceptions.ServiceUnavailableException import org.neo4j.driver.exceptions.SessionExpiredException import org.neo4j.driver.exceptions.value.Uncoercible import org.tree.binaryTree.KVP @@ -34,30 +37,32 @@ class Neo4jIO() : Closeable { ) { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") val root = treeController.tree.root - session.executeWrite { tx -> - deleteTree(tx, treeName) - exportRBNode(tx, root, treeController.nodes) - tx.run( - "MATCH (p: $RBNODE) " + - "MATCH (l: $NEW_NODE {key: p.lkey}) " + - "CREATE (p)-[: $LCHILD]->(l) " + - "REMOVE p.lkey, l: $NEW_NODE" - ) // connect parent and left child - tx.run( - "MATCH (p: $RBNODE) " + - "MATCH (r: $NEW_NODE {key: p.rkey}) " + - "CREATE (p)-[: $RCHILD]->(r) " + - "REMOVE p.rkey, r: $NEW_NODE" - )// connect parent and right child - - tx.run( - "MATCH (r: $NEW_NODE) " + - "CREATE (t: $TREE {name: \$treeName})-[:$ROOT]->(r) " + - "REMOVE r:$NEW_NODE", - mutableMapOf( - "treeName" to treeName - ) as Map? - )// connect tree and root + handleTransactionException { + session.executeWrite { tx -> + deleteTree(tx, treeName) + exportRBNode(tx, root, treeController.nodes) + tx.run( + "MATCH (p: $RBNODE) " + + "MATCH (l: $NEW_NODE {key: p.lkey}) " + + "CREATE (p)-[: $LCHILD]->(l) " + + "REMOVE p.lkey, l: $NEW_NODE" + ) // connect parent and left child + tx.run( + "MATCH (p: $RBNODE) " + + "MATCH (r: $NEW_NODE {key: p.rkey}) " + + "CREATE (p)-[: $RCHILD]->(r) " + + "REMOVE p.rkey, r: $NEW_NODE" + )// connect parent and right child + + tx.run( + "MATCH (r: $NEW_NODE) " + + "CREATE (t: $TREE {name: \$treeName})-[:$ROOT]->(r) " + + "REMOVE r:$NEW_NODE", + mutableMapOf( + "treeName" to treeName + ) as Map? + )// connect tree and root + } } session.close() } @@ -65,29 +70,36 @@ class Neo4jIO() : Closeable { fun importRBTree(treeName: String = "Tree"): TreeController>> { // when we have treeView, fun will be rewritten val session = driver?.session() ?: throw IOException("Driver is not open") - val res: TreeController>> = session.executeRead { tx -> - val tree = RBTree>() - val treeController = TreeController(tree) - importRBNodes(tx, treeController, treeName) - treeController - } + val res: TreeController>> = + handleTransactionException { + session.executeRead { tx -> + val tree = RBTree>() + val treeController = TreeController(tree) + importRBNodes(tx, treeController, treeName) + treeController + } + } session.close() return res } fun removeTree(treeName: String = "Tree") { val session = driver?.session() ?: throw IOException("Driver is not open") - session.executeWrite { tx -> - deleteTree(tx, treeName) + handleTransactionException { + session.executeWrite { tx -> + deleteTree(tx, treeName) + } } session.close() } fun getTreesNames(): MutableList { val session = driver?.session() ?: throw IOException("Driver is not open") - val res: MutableList = session.executeRead { tx -> - val nameRecords = tx.run("MATCH (t: $TREE) RETURN t.name AS name") - parseNames(nameRecords) + val res: MutableList = handleTransactionException { + session.executeRead { tx -> + val nameRecords = tx.run("MATCH (t: $TREE) RETURN t.name AS name") + parseNames(nameRecords) + } } session.close() return res @@ -241,13 +253,32 @@ class Neo4jIO() : Closeable { try { driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password)) } catch (ex: IllegalArgumentException) { - throw IOException("Wrong URI", ex) + throw HandledIOException("Wrong URI", ex) } catch (ex: SessionExpiredException) { - throw IOException("Session failed, try restarting the app", ex) + throw HandledIOException("Session failed, try restarting the app", ex) } + sendTestQuery() } override fun close() { driver?.close() } + + private fun sendTestQuery() { + getTreesNames() + } + + private fun handleTransactionException(transaction: () -> T): T { + try { + return transaction() + } catch (ex: AuthenticationException) { + throw HandledIOException("Wrong username or password", ex) + } catch (ex: ClientException) { + throw HandledIOException("Use the bolt:// URI scheme or some other expected labels", ex) + } catch (ex: ServiceUnavailableException) { + throw HandledIOException("Check your network connection", ex) + } + } } + + From 1f1bb71706385287f9483e3c37ba0d13bb68d2b4 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 23 Apr 2023 21:42:08 +0300 Subject: [PATCH 182/296] feat: Add import/export dialogs for neo4j --- app/src/main/kotlin/org/tree/app/App.kt | 25 ++- .../org/tree/app/view/dialogs/io/ImportRB.kt | 32 ---- .../tree/app/view/dialogs/io/Neo4jConnect.kt | 66 ++++++++ .../tree/app/view/dialogs/io/Neo4jDialogs.kt | 142 ++++++++++++++++++ 4 files changed, 231 insertions(+), 34 deletions(-) delete mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt create mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt create mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index bd76e7a3..a105f87b 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -21,8 +21,10 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree import org.tree.app.view.Tree +import org.tree.app.view.dialogs.io.ExportRBDialog import org.tree.app.view.dialogs.io.ImportRBDialog import org.tree.binaryTree.KVP +import org.tree.binaryTree.RBNode import org.tree.binaryTree.trees.AVLTree import org.tree.binaryTree.trees.BinSearchTree import org.tree.binaryTree.trees.RBTree @@ -31,7 +33,10 @@ enum class DialogType { EMPTY, IMPORT_RB, IMPORT_AVL, - IMPORT_BST + IMPORT_BST, + EXPORT_RB, + EXPORT_AVL, + EXPORT_BST } @Composable @@ -95,7 +100,6 @@ fun main() = application { val treeOffsetX = remember { mutableStateOf(100) } val treeOffsetY = remember { mutableStateOf(100) } - MenuBar { Menu("File", mnemonic = 'F') { Menu("New tree", mnemonic = 'N') { @@ -141,16 +145,33 @@ fun main() = application { dialogType = DialogType.EMPTY } + DialogType.EXPORT_AVL -> { + TODO("Implement") + dialogType = DialogType.EMPTY + } + DialogType.IMPORT_BST -> { TODO("Implement") dialogType = DialogType.EMPTY } + DialogType.EXPORT_BST -> { + TODO("Implement") + dialogType = DialogType.EMPTY + } + DialogType.IMPORT_RB -> { ImportRBDialog( onCloseRequest = { dialogType = DialogType.EMPTY }, onSuccess = { treeController = it }) } + + DialogType.EXPORT_RB -> { + ExportRBDialog( + onCloseRequest = { dialogType = DialogType.EMPTY }, + treeController as TreeController>> + ) + } } } } diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt deleted file mode 100644 index d0d435b7..00000000 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/ImportRB.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.tree.app.view.dialogs.io - -import TreeController -import androidx.compose.foundation.layout.Column -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.ui.window.Dialog -import org.tree.binaryTree.KVP -import org.tree.binaryTree.RBNode - -@Composable -fun ImportRBDialog(onCloseRequest: () -> Unit, onSuccess: (TreeController>>) -> Unit = {}) { - var isDialogOpen by remember { mutableStateOf(true) } - - if (isDialogOpen) { - Dialog(title = "Import Red-Black Tree", - onCloseRequest = { - isDialogOpen = false - }) { - Column { - Text("Клоун! Не отрефакторил код.") - Button(onClick = { isDialogOpen = false }) { - Text("Пип-Пип!") - } - } - } - } else { - onCloseRequest() - } -} - diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt new file mode 100644 index 00000000..498d989a --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt @@ -0,0 +1,66 @@ +package org.tree.app.view.dialogs.io + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import org.tree.app.controller.io.HandledIOException +import org.tree.app.controller.io.Neo4jIO + + +@Composable +fun Neo4jConnect(onSuccess: (Neo4jIO) -> Unit, onFail: (Neo4jIO) -> Unit) { + var urlString by remember { mutableStateOf("bolt://localhost:7687") } + var loginString by remember { mutableStateOf("neo4j") } + var passwordString by remember { mutableStateOf("qwertyui") } + var connectionStatus by remember { mutableStateOf("Not connected") } + var iconColor by remember { mutableStateOf(Color.Red) } + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + OutlinedTextField(value = urlString, onValueChange = { urlString = it }) + OutlinedTextField(value = loginString, onValueChange = { loginString = it }) + OutlinedTextField( + value = passwordString, + onValueChange = { passwordString = it }, + visualTransformation = PasswordVisualTransformation() + ) + Row(verticalAlignment = Alignment.CenterVertically) { + Button(onClick = { + val db = Neo4jIO() + try { + db.open(urlString, loginString, passwordString) + } catch (ex: HandledIOException) { + connectionStatus = ex.toString() + iconColor = Color.Yellow + onFail(db) + return@Button + } + iconColor = Color.Green + connectionStatus = "Connected" + onSuccess(db) + }) { + Text("Connect") + } + Icon( + Icons.Default.AddCircle, + contentDescription = connectionStatus, + tint = iconColor, + modifier = Modifier.size(15.dp).padding(start = 10.dp), + ) + + Text(connectionStatus) + } + } +} diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt new file mode 100644 index 00000000..72b8949a --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -0,0 +1,142 @@ +package org.tree.app.view.dialogs.io + +import TreeController +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.rememberDialogState +import org.tree.app.controller.io.Neo4jIO +import org.tree.binaryTree.KVP +import org.tree.binaryTree.RBNode + +@Composable +fun ImportRBDialog( + onCloseRequest: () -> Unit, + onSuccess: (TreeController>>) -> Unit = {} +) { + Neo4jIODialog("Import RBTree", onCloseRequest = onCloseRequest) { enabled, closeRequest, db, treeName -> + Button( + enabled = enabled, + onClick = { + val treeController = db.importRBTree(treeName) + db.close() + onSuccess(treeController) + closeRequest() + } + ) { + Text("Import") + } + } +} + +@Composable +fun ExportRBDialog( + onCloseRequest: () -> Unit, + treeController: TreeController>> +) { + + Neo4jIODialog("Export RBTree", onCloseRequest = onCloseRequest) { enabled, closeRequest, db, treeName -> + Button( + enabled = enabled, + onClick = { + db.exportRBTree(treeController, treeName) + db.close() + closeRequest() + } + ) { + Text("Export") + } + } +} + +@Composable +fun Neo4jIODialog( + title: String, + onCloseRequest: () -> Unit, + button: @Composable() ((enabled: Boolean, closeRequest: () -> Unit, db: Neo4jIO, treeName: String) -> Unit + ) +) { + var isDialogOpen by remember { mutableStateOf(true) } + var isDBEnable by remember { mutableStateOf(false) } + var expandMenu by remember { mutableStateOf(false) } + var treeName by remember { mutableStateOf("Tree") } + + var db = Neo4jIO() + + if (isDialogOpen) { + Dialog(state = rememberDialogState(width = 500.dp, height = 500.dp), + title = title, + onCloseRequest = { + isDialogOpen = false + }) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Neo4jConnect(onSuccess = { + db = it + isDBEnable = true + }, + onFail = { + db = it + isDBEnable = false + }) + + Box { + Row { + OutlinedTextField( + enabled = isDBEnable, + value = treeName, + onValueChange = { treeName = it }, + ) + IconButton( + enabled = isDBEnable, + onClick = { expandMenu = true } + ) { + Icon(Icons.Default.MoreVert, contentDescription = "Show tree names in db") + } + } + + DropdownMenu( + expanded = expandMenu, + onDismissRequest = { expandMenu = false } + ) { + if (isDBEnable) { + val names = db.getTreesNames() + for (name in names) { + Text(name, modifier = Modifier.padding(10.dp).clickable(onClick = { + treeName = name + expandMenu = false + })) + } + } + } + } + + Row { + Spacer(modifier = Modifier.weight(0.1f)) + Button( + onClick = { + isDialogOpen = false + }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color.LightGray) + ) { + Text("Cancel") + } + Spacer(modifier = Modifier.weight(1f)) + button(isDBEnable, { isDialogOpen = false }, db, treeName) + Spacer(modifier = Modifier.weight(0.1f)) + + } + } + } + } else { + onCloseRequest() + } +} + From 7ebcfba8d12dcf7e2cf0a070ee712a6cd011bb38 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 13:52:33 +0300 Subject: [PATCH 183/296] feat: Add save to menubar --- app/src/main/kotlin/org/tree/app/App.kt | 19 +++++++++++++++++++ .../org/tree/app/controller/TreeController.kt | 3 +++ 2 files changed, 22 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index a105f87b..ee5cdbdb 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -23,7 +23,9 @@ import newTree import org.tree.app.view.Tree import org.tree.app.view.dialogs.io.ExportRBDialog import org.tree.app.view.dialogs.io.ImportRBDialog +import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP +import org.tree.binaryTree.Node import org.tree.binaryTree.RBNode import org.tree.binaryTree.trees.AVLTree import org.tree.binaryTree.trees.BinSearchTree @@ -112,6 +114,23 @@ fun main() = application { Item("Red black Tree", onClick = { dialogType = DialogType.IMPORT_RB }) Item("AVL Tree", onClick = { dialogType = DialogType.IMPORT_AVL }) } + Menu("Save", mnemonic = 'S') { + Item( + "Bin Search Tree", + onClick = { dialogType = DialogType.EXPORT_BST }, + enabled = (treeController.nodeType() is Node<*>?) + ) + Item( + "Red black Tree", + onClick = { dialogType = DialogType.EXPORT_RB }, + enabled = (treeController.nodeType() is RBNode<*>?) + ) + Item( + "AVL Tree", + onClick = { dialogType = DialogType.EXPORT_AVL }, + enabled = (treeController.nodeType() is AVLNode<*>?) + ) + } } } diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index ce3eabbe..673532db 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -84,6 +84,9 @@ class TreeController, NODE_T>>( } } + fun nodeType(): NODE_T? { + return tree.root + } } From 6059104230902fda143aeb9a0005e63918f4b6c4 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 14:00:37 +0300 Subject: [PATCH 184/296] fix(Neo4jIO): Fix case of exporting empty tree --- .../main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index bedf3478..277eb9e8 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -54,14 +54,22 @@ class Neo4jIO() : Closeable { "REMOVE p.rkey, r: $NEW_NODE" )// connect parent and right child + tx.run( + "CREATE (t: $TREE {name: \$treeName})", mutableMapOf( + "treeName" to treeName + ) as Map? + ) // create tree label + tx.run( "MATCH (r: $NEW_NODE) " + - "CREATE (t: $TREE {name: \$treeName})-[:$ROOT]->(r) " + + "MATCH (t: $TREE {name: \$treeName}) " + + "CREATE (t)-[:$ROOT]->(r) " + "REMOVE r:$NEW_NODE", mutableMapOf( "treeName" to treeName ) as Map? )// connect tree and root + } } session.close() @@ -274,6 +282,7 @@ class Neo4jIO() : Closeable { } catch (ex: AuthenticationException) { throw HandledIOException("Wrong username or password", ex) } catch (ex: ClientException) { + println(ex.message) throw HandledIOException("Use the bolt:// URI scheme or some other expected labels", ex) } catch (ex: ServiceUnavailableException) { throw HandledIOException("Check your network connection", ex) From aeee57ed363e9cc8e7c768d3957f24cbc144cfc7 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 14:14:06 +0300 Subject: [PATCH 185/296] fix(Neo4jIO): Fix case of importing empty tree --- .../org/tree/app/controller/io/Neo4jIO.kt | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 277eb9e8..b1fdb739 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -224,24 +224,27 @@ class Neo4jIO() : Closeable { } val nks = key2nk.values.toTypedArray() + if (key2nk.isEmpty()) { // tree was empty + treeController.tree.root = null + } else { + for (nk in nks) { + nk.lkey?.let { + nk.nd.left = key2nk[it]?.nd + nk.nd.left?.parent = nk.nd + key2nk.remove(it) + } - for (nk in nks) { - nk.lkey?.let { - nk.nd.left = key2nk[it]?.nd - nk.nd.left?.parent = nk.nd - key2nk.remove(it) + nk.rkey?.let { + nk.nd.right = key2nk[it]?.nd + nk.nd.right?.parent = nk.nd + key2nk.remove(it) + } } - - nk.rkey?.let { - nk.nd.right = key2nk[it]?.nd - nk.nd.right?.parent = nk.nd - key2nk.remove(it) + if (key2nk.values.size != 1) { + throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") } + treeController.tree.root = key2nk.values.first().nd } - if (key2nk.values.size != 1) { - throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") - } - treeController.tree.root = key2nk.values.first().nd } private fun parseNames(nameRecords: Result): MutableList { From 869695953fe30967511bb636c67de387bbd1423f Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 19:10:51 +0300 Subject: [PATCH 186/296] feat: Add alert dialog for exceptions --- .../app/view/dialogs/io/ExceptionIOHandler.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt new file mode 100644 index 00000000..9eb64065 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt @@ -0,0 +1,48 @@ +package org.tree.app.view.dialogs.io + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.rememberDialogState +import org.tree.app.controller.io.HandledIOException + +fun handleIOException(onCatch: (HandledIOException) -> Unit, handledCode: () -> T): T? { + try { + return handledCode() + } catch (ex: HandledIOException) { + onCatch(ex) + return null + } +} + +@Composable +fun AlertDialog(description: String, isOpen: Boolean, onCloseRequest: () -> Unit) { + if (isOpen) { + Dialog(state = rememberDialogState(width = 500.dp, height = 250.dp), + title = "Something go wrong...", + onCloseRequest = { + onCloseRequest() + }) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = Modifier.weight(0.1f)) + Row { + Spacer(modifier = Modifier.weight(0.1f)) + Text(description, modifier = Modifier.weight(1.0f)) + Spacer(modifier = Modifier.weight(0.1f)) + } + + Spacer(modifier = Modifier.weight(0.1f)) + Button(onClick = { onCloseRequest() }) { + Text("Ok") + } + } + } + } +} From 53815e584e9e9c0da0e47cb5709c9264295a4237 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 19:11:47 +0300 Subject: [PATCH 187/296] feat: Add exception handling to neo4j import/export --- .../tree/app/view/dialogs/io/Neo4jDialogs.kt | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt index 72b8949a..6ed5f1f4 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -22,14 +22,24 @@ fun ImportRBDialog( onCloseRequest: () -> Unit, onSuccess: (TreeController>>) -> Unit = {} ) { + var throwException by remember { mutableStateOf(false) } + var exceptionContent by remember { mutableStateOf("Nothing...") } + + AlertDialog(exceptionContent, throwException, { throwException = false }) Neo4jIODialog("Import RBTree", onCloseRequest = onCloseRequest) { enabled, closeRequest, db, treeName -> Button( enabled = enabled, onClick = { - val treeController = db.importRBTree(treeName) - db.close() - onSuccess(treeController) - closeRequest() + val treeController = + handleIOException(onCatch = { + exceptionContent = it.toString() + throwException = true + }) { db.importRBTree(treeName) } + if (treeController != null) { + db.close() + onSuccess(treeController) + closeRequest() + } } ) { Text("Import") @@ -43,13 +53,22 @@ fun ExportRBDialog( treeController: TreeController>> ) { + var throwException by remember { mutableStateOf(false) } + var exceptionContent by remember { mutableStateOf("Nothing...") } + + AlertDialog(exceptionContent, throwException, { throwException = false }) Neo4jIODialog("Export RBTree", onCloseRequest = onCloseRequest) { enabled, closeRequest, db, treeName -> Button( enabled = enabled, onClick = { - db.exportRBTree(treeController, treeName) - db.close() - closeRequest() + handleIOException(onCatch = { + exceptionContent = it.toString() + throwException = true + }) { db.exportRBTree(treeController, treeName) } + if (!throwException) { + db.close() + closeRequest() + } } ) { Text("Export") @@ -68,6 +87,10 @@ fun Neo4jIODialog( var isDBEnable by remember { mutableStateOf(false) } var expandMenu by remember { mutableStateOf(false) } var treeName by remember { mutableStateOf("Tree") } + var throwException by remember { mutableStateOf(false) } + var exceptionContent by remember { mutableStateOf("Nothing...") } + + AlertDialog(exceptionContent, throwException, { throwException = false }) var db = Neo4jIO() @@ -107,12 +130,19 @@ fun Neo4jIODialog( onDismissRequest = { expandMenu = false } ) { if (isDBEnable) { - val names = db.getTreesNames() - for (name in names) { - Text(name, modifier = Modifier.padding(10.dp).clickable(onClick = { - treeName = name - expandMenu = false - })) + val names = handleIOException( + onCatch = { + exceptionContent = it.toString() + throwException = true + } + ) { db.getTreesNames() } + if (names != null) { + for (name in names) { + Text(name, modifier = Modifier.padding(10.dp).clickable(onClick = { + treeName = name + expandMenu = false + })) + } } } } From 6bbb9e04495e53a68b2540a31b44ec10c3fb1542 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Mon, 24 Apr 2023 15:00:42 +0300 Subject: [PATCH 188/296] refactor(TreeController): Change AVLNode color from blue to cyan --- app/src/main/kotlin/org/tree/app/controller/TreeController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 673532db..cbe19dde 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -68,7 +68,7 @@ class TreeController, NODE_T>>( Color.Red } } else if (curNode is AVLNode<*>) { - Color.Blue + Color.Cyan } else if (curNode is Node<*>) { Color.Yellow } else { From ffd8549678868712fbbe08fee653fd155b129707 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Mon, 24 Apr 2023 15:02:56 +0300 Subject: [PATCH 189/296] feat(TreeController): Add childrenCount() method --- .../kotlin/org/tree/app/controller/TreeController.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index cbe19dde..9410978e 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -23,6 +23,17 @@ class TreeController, NODE_T>>( } } + private fun childrenCount(node: NODE_T?): Int{ + var count = 0 + if (node?.left != null) { + count++ + } + if (node?.right != null) { + count++ + } + return count + } + fun addNode(curNode: NODE_T, x: Int, y: Int, height: Int) { val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) From a3ada0ec99dd59f911ebcf02a0c3dd1a96e4faec Mon Sep 17 00:00:00 2001 From: Anna-er Date: Mon, 24 Apr 2023 16:22:01 +0300 Subject: [PATCH 190/296] refactor(TreeController): Add methods drawLeft() and drawRight() instead of addNode() method --- .../org/tree/app/controller/TreeController.kt | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 9410978e..8cd2d8c1 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -7,7 +7,6 @@ import org.tree.binaryTree.Node import org.tree.binaryTree.RBNode import org.tree.binaryTree.templates.TemplateBSTree import org.tree.binaryTree.templates.TemplateNode -import kotlin.math.pow import kotlin.random.Random data class NodeExtension(var x: MutableState, var y: MutableState, var color: Color = Color.Gray) @@ -19,11 +18,12 @@ class TreeController, NODE_T>>( init { tree.root?.let { - addNode(it, 0, 0, height(it) - 2) + drawLeft(it, 0, 0, height(it) - 2) + drawRight(it, 0, 0, height(it) - 2) } } - private fun childrenCount(node: NODE_T?): Int{ + private fun childrenCount(node: NODE_T?): Int { var count = 0 if (node?.left != null) { count++ @@ -34,18 +34,38 @@ class TreeController, NODE_T>>( return count } - fun addNode(curNode: NODE_T, x: Int, y: Int, height: Int) { + private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { + var count = 0 + if (node?.right != null) { + count = 1 + (height)*childrenCount(node.right) + } + val x = parentX - nodeSize - (count*nodeSize) + val y = parentY + nodeSize val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) - val col = getNodeCol(curNode) - nodes[curNode] = NodeExtension(stateX, stateY, col) - val deltaX = nodeSize * (2).toDouble().pow(height.toDouble()).toInt() - curNode.left?.let { - addNode(it, x + deltaX, y + nodeSize, height - 1) + if (node != null) { + val col = getNodeCol(node) + nodes[node] = NodeExtension(stateX, stateY, col) + } + if (node?.left != null) drawLeft(node.left, x, y, height - 1) + if (node?.right != null) drawRight(node.right, x, y, height - 1) + } + + private fun drawRight(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { + var count = 0 + if (node?.left != null) { + count = 1 + (height)*childrenCount(node.left) } - curNode.right?.let { - addNode(it, x - deltaX, y + nodeSize, height - 1) + val x = parentX + nodeSize + (count*nodeSize) + val y = parentY + nodeSize + val stateX = mutableStateOf(x) + val stateY = mutableStateOf(y) + if (node != null) { + val col = getNodeCol(node) + nodes[node] = NodeExtension(stateX, stateY, col) } + if (node?.left != null) drawLeft(node.left, x, y, height - 1) + if (node?.right != null) drawRight(node.right, x, y, height - 1) } fun insert(obj: KVP): TreeController { From 9d77466ac25b2ce8774dcd4c775e9154301e110c Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 25 Apr 2023 00:19:51 +0300 Subject: [PATCH 191/296] refactor(TreeController): Use an existing function to count children --- .../kotlin/org/tree/app/controller/TreeController.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 8cd2d8c1..5bc67a63 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -24,14 +24,7 @@ class TreeController, NODE_T>>( } private fun childrenCount(node: NODE_T?): Int { - var count = 0 - if (node?.left != null) { - count++ - } - if (node?.right != null) { - count++ - } - return count + return (2 - (node?.countNullChildren() ?: 2)) } private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { From a08b666884de9d8a5bbe1e6d5f801f452299e191 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 25 Apr 2023 23:24:51 +0300 Subject: [PATCH 192/296] fix(Json): Rewrite Json with support for the new architecture --- .../kotlin/org/tree/app/controller/io/Json.kt | 70 ++++++++++++------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt index 3de8b352..2b491ec5 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Json.kt @@ -4,20 +4,22 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import org.tree.app.view.NodeView +import TreeController import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP import java.io.File +import NodeExtension +import androidx.compose.runtime.mutableStateOf +import org.tree.binaryTree.trees.AVLTree import java.io.FileNotFoundException -import java.io.IOException import java.nio.file.Files @Serializable private data class JsonAVLNode( val key: Int, val value: String?, - val x: Double, - val y: Double, + val x: Int, + val y: Int, val height: Int, val left: JsonAVLNode?, val right: JsonAVLNode? @@ -28,34 +30,45 @@ private data class JsonAVLTree( val root: JsonAVLNode? ) class Json { - private fun NodeView>>.serialize(): JsonAVLNode = JsonAVLNode( - key = this.node.elem.key, - value = this.node.elem.v, - x = this.x, - y = this.y, - height = this.node.height, - left = l?.serialize(), - right = r?.serialize() - ) + private lateinit var treeController : TreeController>> - private fun JsonAVLNode.deserialize(): NodeView>> { - val nv = NodeView(AVLNode(KVP(key, value))) - nv.node.height = height - nv.x = x - nv.y = y - nv.l = left?.deserialize() - nv.r = right?.deserialize() + private fun AVLNode>.serialize(): JsonAVLNode{ + return JsonAVLNode( + key = this.elem.key, + value = this.elem.v, + x = treeController.nodes[this]?.x?.value ?: 0, + y = treeController.nodes[this]?.y?.value ?: 0, + height = this.height, + left = this.left?.serialize(), + right = this.right?.serialize() + ) + } + private fun JsonAVLNode.deserialize(): AVLNode> { + val nv = AVLNode(KVP(key, value)) + nv.height = height + addCoordinatesToNode(nv, x, y) + nv.left = left?.deserialize() + nv.right = right?.deserialize() return nv } - fun exportTree(root: NodeView>>, file: File) { + private fun addCoordinatesToNode( + node: AVLNode>, + x: Int, y: Int + ) { + treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y)) + } + + fun exportTree(treeController_: TreeController>>, file: File) { try { Files.createDirectories(file.toPath().parent) } catch (ex: SecurityException) { - throw IOException("Directory ${file.toPath().parent} cannot be created: no access", ex) + throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } - val jsonTree = JsonAVLTree(root.serialize()) + + treeController = treeController_ + val jsonTree = JsonAVLTree(treeController.tree.root?.serialize()) file.run { createNewFile() @@ -63,15 +76,18 @@ class Json { } } - fun importTree(file: File): NodeView>>? { + fun importTree(file: File): TreeController>> { val json = try { file.readText() - } catch (_: FileNotFoundException) { - return null + } catch (ex: FileNotFoundException) { + throw HandledIOException("File ${file.toPath().fileName} not found: no access", ex) } + treeController = TreeController(AVLTree()) val jsonTree = Json.decodeFromString(json) - return jsonTree.root?.deserialize() + treeController.tree.root = jsonTree.root?.deserialize() + return treeController + } fun cleanDataBase(file: File) { From bce4f7a7bf5e062e9b4248c9f29b648f6dd9936a Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 25 Apr 2023 23:26:30 +0300 Subject: [PATCH 193/296] fix(Json): Edit Json specification --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 13bc57b3..4956ad14 100644 --- a/README.md +++ b/README.md @@ -73,15 +73,15 @@ At plain text database we store AVLTree. We use json files. There are 2 types of - `key` *int* - `value` *string* - `height` *int* - node height - - `x` *double* - node position at ui - - `y` *double* - node position at ui + - `x` *int* - node position at ui + - `y` *int* - node position at ui - `left` *AVLNode* - node left child - `right` *AVLNode* - node right child We have functions: -- `exportTree(root, file.json)` - writes a tree with root `root` to a file `file.json`. -- `importTree(file.json)` - reads the tree stored in the file `file.json` and returns it`s root. +- `exportTree(TreeController, file.json)` - writes information stored in TreeController object to a file `file.json`. +- `importTree(file.json)` - reads the tree stored in the file `file.json` and returns TreeController object. - `cleanDataBase(file.json)` - deletes a file `file.json`. From da152a23343c79b69a82f5cd53a1ae82cdf9fe97 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 21:30:39 +0300 Subject: [PATCH 194/296] fix: Fix moment than all tree disappears --- app/src/main/kotlin/org/tree/app/App.kt | 4 +- app/src/main/kotlin/org/tree/app/view/Line.kt | 15 +++--- app/src/main/kotlin/org/tree/app/view/Node.kt | 16 +++--- app/src/main/kotlin/org/tree/app/view/Tree.kt | 53 ++++++++++--------- 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ee5cdbdb..ec9510a3 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree -import org.tree.app.view.Tree +import org.tree.app.view.TreeView import org.tree.app.view.dialogs.io.ExportRBDialog import org.tree.app.view.dialogs.io.ImportRBDialog import org.tree.binaryTree.AVLNode @@ -151,7 +151,7 @@ fun main() = application { }) } Box(Modifier.scale(1.0F)) { - Tree(treeController, treeOffsetX, treeOffsetY) + TreeView(treeController, treeOffsetX, treeOffsetY) } } diff --git a/app/src/main/kotlin/org/tree/app/view/Line.kt b/app/src/main/kotlin/org/tree/app/view/Line.kt index b844f242..7e84b660 100644 --- a/app/src/main/kotlin/org/tree/app/view/Line.kt +++ b/app/src/main/kotlin/org/tree/app/view/Line.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset @@ -12,17 +11,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @Composable -fun Line(x0: MutableState, y0: MutableState, x1: MutableState, y1: MutableState) { - val x0 = x0.value.dp - val x1 = x1.value.dp - val y0 = y0.value.dp - val y1 = y1.value.dp +fun Line(x0: Int, y0: Int, x1: Int, y1: Int) { + val x0 = x0.dp + val x1 = x1.dp + val y0 = y0.dp + val y1 = y1.dp Box(modifier = Modifier.offset(x0, y0)) { Box( modifier = Modifier.size(x1 - x0, y1 - y0) .drawBehind { drawLine(Color.Black, Offset.Zero, Offset((x1 - x0).toPx(), (y1 - y0).toPx()), 1f) } - ) { - - } + ) } } diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index f8788a00..9c5f42e4 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -11,12 +11,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp @@ -43,9 +44,10 @@ class NodeView, NODE_T>>(nd: NODE_T) { // @OptIn(ExperimentalFoundationApi::class) @Composable fun Node( - x: MutableState, - y: MutableState, + x: Int, + y: Int, key: Int, value: String, + onDrag: (PointerInputChange, Offset) -> Unit, modifier: Modifier = Modifier, color: Color = Color.Gray, size: Int = 10, @@ -64,7 +66,7 @@ fun Node( ) } }, - modifier = Modifier.offset((x.value - size / 2).dp, (y.value - size / 2).dp), + modifier = Modifier.offset((x - size / 2).dp, (y - size / 2).dp), delayMillis = 600, tooltipPlacement = TooltipPlacement.CursorPoint( alignment = Alignment.BottomEnd, @@ -75,11 +77,9 @@ fun Node( contentAlignment = Alignment.Center, modifier = modifier .size(size.dp).clip(CircleShape).background(color = color) - .pointerInput(x, y) { + .pointerInput(onDrag) { detectDragGestures { change, dragAmount -> - change.consume() - x.value += dragAmount.x.toInt() - y.value += dragAmount.y.toInt() + onDrag(change, dragAmount) } } ) { diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index 677b7450..16c8fb3e 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -4,60 +4,65 @@ import TreeController import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.offset -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.key +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.unit.IntOffset import org.tree.binaryTree.KVP import org.tree.binaryTree.templates.TemplateNode @Composable -fun , NODE_T>> Tree( +fun , NODE_T>> TreeView( t: TreeController, offsetX: MutableState, offsetY: MutableState, ) { - Box { + Box(modifier = Modifier.background(Color.Cyan).clipToBounds() + .pointerInput(offsetX, offsetY) { + detectDragGestures { change, dragAmount -> + change.consume() + offsetX.value += dragAmount.x.toInt() + offsetY.value += dragAmount.y.toInt() + } + }) { Box( - Modifier - .offset { IntOffset(offsetX.value, offsetY.value) } - .background(Color.Green) //for debug - .pointerInput(offsetX, offsetY) { - detectDragGestures { change, dragAmount -> - change.consume() - offsetX.value += dragAmount.x.toInt() - offsetY.value += dragAmount.y.toInt() - } - } ) { for (n in t.nodes) { - val x = n.value.x - val y = n.value.y + val x by n.value.x + val y by n.value.y with(n.key) { val l = t.nodes[left] if (l != null) { - Line(x, y, l.x, l.y) + Line(x + offsetX.value, y + offsetY.value, l.x.value + offsetX.value, l.y.value + offsetY.value) } val r = t.nodes[right] if (r != null) { - Line(x, y, r.x, r.y) + Line(x + offsetX.value, y + offsetY.value, r.x.value + offsetX.value, r.y.value + offsetY.value) } } } for (n in t.nodes) { - val x = n.value.x - val y = n.value.y + var x by n.value.x + var y by n.value.y val col = n.value.color with(n.key.elem) { key(key) { - Node(x, y, key, v ?: "", size = t.nodeSize, color = col) + Node( + x + offsetX.value, + y + offsetY.value, + key, + v ?: "", + size = t.nodeSize, + color = col, + onDrag = { change, dragAmount -> + change.consume() + x += dragAmount.x.toInt() + y += dragAmount.y.toInt() + }) } } } From cc5157b581f33591adc73c8bcd60d72490f29e5e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 22:55:26 +0300 Subject: [PATCH 195/296] feat: Add resizable to TreeView --- app/src/main/kotlin/org/tree/app/App.kt | 57 +++++++++++++------ app/src/main/kotlin/org/tree/app/view/Tree.kt | 7 ++- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ec9510a3..38b5c5f3 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -4,15 +4,19 @@ package org.tree.app import TreeController -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.MenuBar @@ -47,13 +51,16 @@ fun InsertRow(onClick: (key: Int, value: String) -> Unit) { var valueString by remember { mutableStateOf("value") } Row { - Button(onClick = { - onClick(keyString.toInt(), valueString) - }) { + Button( + onClick = { + onClick(keyString.toInt(), valueString) + }, + modifier = Modifier.weight(0.3f).defaultMinSize(minWidth = 100.dp) + ) { Text("Insert") } - OutlinedTextField(value = keyString, onValueChange = { keyString = it }) - OutlinedTextField(value = valueString, onValueChange = { valueString = it }) + OutlinedTextField(value = keyString, onValueChange = { keyString = it }, modifier = Modifier.weight(0.35f)) + OutlinedTextField(value = valueString, onValueChange = { valueString = it }, modifier = Modifier.weight(0.35f)) } } @@ -62,12 +69,16 @@ fun RemoveRow(onClick: (key: Int) -> Unit) { var keyString by remember { mutableStateOf("123") } Row { - Button(onClick = { - onClick(keyString.toInt()) - }) { + Button( + onClick = { + onClick(keyString.toInt()) + }, + modifier = Modifier.weight(0.3f) + ) { Text("Remove") } - OutlinedTextField(value = keyString, onValueChange = { keyString = it }) + + OutlinedTextField(value = keyString, onValueChange = { keyString = it }, modifier = Modifier.weight(0.7f)) } } @@ -78,10 +89,10 @@ fun FindRow(onClick: (key: Int) -> (Unit)) { Row { Button(onClick = { onClick(keyString.toInt()) - }) { + }, modifier = Modifier.weight(0.3f)) { Text("Find") } - OutlinedTextField(value = keyString, onValueChange = { keyString = it }) + OutlinedTextField(value = keyString, onValueChange = { keyString = it }, modifier = Modifier.weight(0.7f)) } } @@ -98,6 +109,7 @@ fun main() = application { newTree(BinSearchTree()) ) } + var widthOfPanel by remember { mutableStateOf(400) } var dialogType by remember { mutableStateOf(DialogType.EMPTY) } val treeOffsetX = remember { mutableStateOf(100) } val treeOffsetY = remember { mutableStateOf(100) } @@ -134,8 +146,8 @@ fun main() = application { } } - Row { - Column { + Row(modifier = Modifier.background(Color.LightGray)) { + Column(Modifier.width(widthOfPanel.dp)) { InsertRow(onClick = { key, value -> treeController = treeController.insert(KVP(key, value)) }) @@ -150,6 +162,19 @@ fun main() = application { } }) } + Spacer(modifier = Modifier.width(3.dp)) + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxHeight()) { + Box(Modifier.background(color = Color.Gray, shape = RoundedCornerShape(5.dp)).size(5.dp, 100.dp) + .draggable( + orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, + state = rememberDraggableState { delta -> + widthOfPanel += delta.toInt() + treeOffsetX.value -= delta.toInt() / 2 + } + ) + ) + } + Spacer(modifier = Modifier.width(3.dp)) Box(Modifier.scale(1.0F)) { TreeView(treeController, treeOffsetX, treeOffsetY) } diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index 16c8fb3e..9c5e138b 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -4,11 +4,13 @@ import TreeController import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp import org.tree.binaryTree.KVP import org.tree.binaryTree.templates.TemplateNode @@ -18,7 +20,7 @@ fun , NODE_T>> TreeView( offsetX: MutableState, offsetY: MutableState, ) { - Box(modifier = Modifier.background(Color.Cyan).clipToBounds() + Box(modifier = Modifier.background(Color.White, shape = RoundedCornerShape(16.dp)).clipToBounds() .pointerInput(offsetX, offsetY) { detectDragGestures { change, dragAmount -> change.consume() @@ -26,8 +28,7 @@ fun , NODE_T>> TreeView( offsetY.value += dragAmount.y.toInt() } }) { - Box( - ) { + Box { for (n in t.nodes) { val x by n.value.x val y by n.value.y From 67b5493b2cc3047597566986862be047291c2f8d Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 24 Apr 2023 23:03:00 +0300 Subject: [PATCH 196/296] feat: Improve insert, remove, find --- app/src/main/kotlin/org/tree/app/App.kt | 87 +++++++++++++++++++++---- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 38b5c5f3..6a269dac 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -9,14 +9,16 @@ import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.MenuBar @@ -45,11 +47,16 @@ enum class DialogType { EXPORT_BST } +@OptIn(ExperimentalComposeUiApi::class) @Composable fun InsertRow(onClick: (key: Int, value: String) -> Unit) { var keyString by remember { mutableStateOf("123") } var valueString by remember { mutableStateOf("value") } + fun execute() { + onClick(keyString.toInt(), valueString) + } + Row { Button( onClick = { @@ -59,15 +66,44 @@ fun InsertRow(onClick: (key: Int, value: String) -> Unit) { ) { Text("Insert") } - OutlinedTextField(value = keyString, onValueChange = { keyString = it }, modifier = Modifier.weight(0.35f)) - OutlinedTextField(value = valueString, onValueChange = { valueString = it }, modifier = Modifier.weight(0.35f)) + OutlinedTextField( + value = keyString, + onValueChange = { keyString = it }, + maxLines = 1, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.35f).onKeyEvent { + if (it.key == Key.Enter) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) + OutlinedTextField( + value = valueString, + onValueChange = { valueString = it }, + maxLines = 1, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.35f).onKeyEvent { + if (it.key == Key.Enter) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) } } +@OptIn(ExperimentalComposeUiApi::class) @Composable fun RemoveRow(onClick: (key: Int) -> Unit) { var keyString by remember { mutableStateOf("123") } + fun execute() { + onClick(keyString.toInt()) + } + Row { Button( onClick = { @@ -78,21 +114,48 @@ fun RemoveRow(onClick: (key: Int) -> Unit) { Text("Remove") } - OutlinedTextField(value = keyString, onValueChange = { keyString = it }, modifier = Modifier.weight(0.7f)) + OutlinedTextField( + value = keyString, + onValueChange = { keyString = it }, + maxLines = 1, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.7f).onKeyEvent { + if (it.key == Key.Enter) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) } } +@OptIn(ExperimentalComposeUiApi::class) @Composable fun FindRow(onClick: (key: Int) -> (Unit)) { var keyString by remember { mutableStateOf("123") } + fun execute() { + onClick(keyString.toInt()) + } + Row { - Button(onClick = { - onClick(keyString.toInt()) - }, modifier = Modifier.weight(0.3f)) { + Button(onClick = ::execute, modifier = Modifier.weight(0.3f)) { Text("Find") } - OutlinedTextField(value = keyString, onValueChange = { keyString = it }, modifier = Modifier.weight(0.7f)) + OutlinedTextField( + value = keyString, + onValueChange = { keyString = it }, + maxLines = 1, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.7f).onKeyEvent { + if (it.key == Key.Enter) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) } } @@ -157,8 +220,8 @@ fun main() = application { FindRow(onClick = { key -> val a = treeController.find(KVP(key)) if (a != null) { - treeOffsetX.value = -a.x.value - treeOffsetY.value = -a.y.value + treeOffsetX.value = 100 - a.x.value + treeOffsetY.value = 100 - a.y.value } }) } From 807221f1a837daec9ac6fecbf111f1aa9520dea7 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 25 Apr 2023 10:58:31 +0300 Subject: [PATCH 197/296] feat: Add "reset" and "to root" buttons --- app/src/main/kotlin/org/tree/app/App.kt | 33 ++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 6a269dac..b131818e 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -210,13 +210,15 @@ fun main() = application { } Row(modifier = Modifier.background(Color.LightGray)) { - Column(Modifier.width(widthOfPanel.dp)) { + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.width(widthOfPanel.dp)) { InsertRow(onClick = { key, value -> treeController = treeController.insert(KVP(key, value)) }) + Spacer(modifier = Modifier.size(10.dp)) RemoveRow(onClick = { key -> treeController = treeController.remove(KVP(key)) }) + Spacer(modifier = Modifier.size(10.dp)) FindRow(onClick = { key -> val a = treeController.find(KVP(key)) if (a != null) { @@ -224,6 +226,35 @@ fun main() = application { treeOffsetY.value = 100 - a.y.value } }) + Spacer(modifier = Modifier.size(10.dp)) + Row(horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { treeController = TreeController(treeController.tree) }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color(0xffff5b79), + contentColor = Color.White + ) + ) { + Text("Reset coordinates") + } + Button( + onClick = { + treeController.tree.root?.let { + val a = treeController.find(it.elem) + if (a != null) { + treeOffsetX.value = 100 - a.x.value + treeOffsetY.value = 100 - a.y.value + } + } + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.Gray, + contentColor = Color.White + ) + ) { + Text("To root") + } + } } Spacer(modifier = Modifier.width(3.dp)) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxHeight()) { From f5749756fee8621b6222d7734f07ec26c6f9262b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 25 Apr 2023 21:00:57 +0300 Subject: [PATCH 198/296] feat: Add Logger --- app/src/main/kotlin/org/tree/app/App.kt | 148 ++++++++++++------ .../org/tree/app/controller/TreeController.kt | 5 +- .../main/kotlin/org/tree/app/view/Logger.kt | 28 ++++ .../tree/app/view/dialogs/io/Neo4jConnect.kt | 17 +- 4 files changed, 132 insertions(+), 66 deletions(-) create mode 100644 app/src/main/kotlin/org/tree/app/view/Logger.kt diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index b131818e..83019fff 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree +import org.tree.app.view.Logger import org.tree.app.view.TreeView import org.tree.app.view.dialogs.io.ExportRBDialog import org.tree.app.view.dialogs.io.ImportRBDialog @@ -174,6 +175,8 @@ fun main() = application { } var widthOfPanel by remember { mutableStateOf(400) } var dialogType by remember { mutableStateOf(DialogType.EMPTY) } + var logString by remember { mutableStateOf("Log string") } + var logColor by remember { mutableStateOf(Color.DarkGray) } val treeOffsetX = remember { mutableStateOf(100) } val treeOffsetY = remember { mutableStateOf(100) } @@ -210,60 +213,109 @@ fun main() = application { } Row(modifier = Modifier.background(Color.LightGray)) { - Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.width(widthOfPanel.dp)) { - InsertRow(onClick = { key, value -> - treeController = treeController.insert(KVP(key, value)) - }) - Spacer(modifier = Modifier.size(10.dp)) - RemoveRow(onClick = { key -> - treeController = treeController.remove(KVP(key)) - }) - Spacer(modifier = Modifier.size(10.dp)) - FindRow(onClick = { key -> - val a = treeController.find(KVP(key)) - if (a != null) { - treeOffsetX.value = 100 - a.x.value - treeOffsetY.value = 100 - a.y.value - } - }) - Spacer(modifier = Modifier.size(10.dp)) - Row(horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth()) { - Button( - onClick = { treeController = TreeController(treeController.tree) }, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color(0xffff5b79), - contentColor = Color.White - ) - ) { - Text("Reset coordinates") - } - Button( - onClick = { - treeController.tree.root?.let { - val a = treeController.find(it.elem) - if (a != null) { - treeOffsetX.value = 100 - a.x.value - treeOffsetY.value = 100 - a.y.value - } + Column(modifier = Modifier.width(widthOfPanel.dp)) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + InsertRow(onClick = { key, value -> + val rememd = treeController + treeController = treeController.insert(KVP(key, value)) + if (rememd == treeController) { + logString = "Node with key = $key already in tree. Nothing is done" + logColor = Color.Yellow + } else { + logString = "Node with key = $key and value = $value inserted" + logColor = Color.Green + } + }) + Spacer(modifier = Modifier.size(5.dp)) + RemoveRow(onClick = { key -> + val rememd = treeController + treeController = treeController.remove(KVP(key)) + if (rememd == treeController) { + logString = "There isn't node with key = $key in tree. Nothing is done" + logColor = Color.Yellow + } else { + logString = "Node with key = $key removed" + logColor = Color.Green + } + }) + Spacer(modifier = Modifier.size(5.dp)) + FindRow(onClick = { key -> + val node = treeController.find(KVP(key)) + if (node != null) { + logString = "Found node with key = $key and value = ${node.elem.v}" + logColor = Color.Green + val coord = treeController.nodes[node] + if (coord != null) { + treeOffsetX.value = 100 - coord.x.value + treeOffsetY.value = 100 - coord.y.value } - }, - colors = ButtonDefaults.buttonColors( - backgroundColor = Color.Gray, - contentColor = Color.White - ) - ) { - Text("To root") + } else { + logString = "No node with key = $key found" + logColor = Color.Yellow + } + }) + Spacer(modifier = Modifier.size(5.dp)) + Row(horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { + treeController = TreeController(treeController.tree) + logString = "Tree coordinates reset" + logColor = Color.Green + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color(0xffff5b79), + contentColor = Color.White + ) + ) { + Text("Reset coordinates") + } + Button( + onClick = { + val treeRoot = treeController.tree.root + if (treeRoot != null) { + val coord = treeController.nodes[treeController.find(treeRoot.elem)] + if (coord != null) { + logString = "Moved to root" + logColor = Color.Green + treeOffsetX.value = 100 - coord.x.value + treeOffsetY.value = 100 - coord.y.value + } + } else { + logString = "Current tree is empty" + logColor = Color.Yellow + } + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.Gray, + contentColor = Color.White + ) + ) { + Text("To root") + } } } + Spacer(Modifier.height(5.dp)) + Row(horizontalArrangement = Arrangement.Center) { + Spacer(Modifier.width(5.dp)) + Box( + Modifier.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(5.dp)) + .height(3.dp).fillMaxWidth() + ) + Spacer(Modifier.width(5.dp)) + } + Spacer(Modifier.height(5.dp)) + Logger(logString, logColor) } + Spacer(modifier = Modifier.width(3.dp)) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxHeight()) { - Box(Modifier.background(color = Color.Gray, shape = RoundedCornerShape(5.dp)).size(5.dp, 100.dp) - .draggable( - orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, - state = rememberDraggableState { delta -> - widthOfPanel += delta.toInt() - treeOffsetX.value -= delta.toInt() / 2 + Box( + Modifier.background(color = Color.Gray, shape = RoundedCornerShape(5.dp)).size(5.dp, 100.dp) + .draggable( + orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, + state = rememberDraggableState { delta -> + widthOfPanel += delta.toInt() + treeOffsetX.value -= delta.toInt() / 2 } ) ) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 5bc67a63..92dd0daa 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -79,9 +79,8 @@ class TreeController, NODE_T>>( return res } - fun find(obj: KVP): NodeExtension? { - val node = tree.find(obj) - return nodes[node] + fun find(obj: KVP): NODE_T? { + return tree.find(obj) } private fun getNodeCol(curNode: NODE_T): Color { diff --git a/app/src/main/kotlin/org/tree/app/view/Logger.kt b/app/src/main/kotlin/org/tree/app/view/Logger.kt new file mode 100644 index 00000000..e7bb0aea --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/Logger.kt @@ -0,0 +1,28 @@ +package org.tree.app.view + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun Logger(content: String, col: Color) { + Row(horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Default.AddCircle, + contentDescription = content, + tint = col, + modifier = Modifier.size(25.dp).padding(start = 10.dp, end = 5.dp), + ) + Text(content) + } +} diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt index 498d989a..f2fea1e4 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt @@ -2,22 +2,16 @@ package org.tree.app.view.dialogs.io import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.material.Button -import androidx.compose.material.Icon import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AddCircle import androidx.compose.runtime.* import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.unit.dp import org.tree.app.controller.io.HandledIOException import org.tree.app.controller.io.Neo4jIO +import org.tree.app.view.Logger @Composable @@ -53,14 +47,7 @@ fun Neo4jConnect(onSuccess: (Neo4jIO) -> Unit, onFail: (Neo4jIO) -> Unit) { }) { Text("Connect") } - Icon( - Icons.Default.AddCircle, - contentDescription = connectionStatus, - tint = iconColor, - modifier = Modifier.size(15.dp).padding(start = 10.dp), - ) - - Text(connectionStatus) + Logger(connectionStatus, iconColor) } } } From 62efa0d370f946dfb8881e5bc9a1f501ebae9b42 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 25 Apr 2023 22:13:29 +0300 Subject: [PATCH 199/296] feat: Add auto-cleat to insert, remove, find --- app/src/main/kotlin/org/tree/app/App.kt | 51 ++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 83019fff..ea79c99d 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -16,9 +16,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.* import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.MenuBar @@ -51,29 +49,33 @@ enum class DialogType { @OptIn(ExperimentalComposeUiApi::class) @Composable fun InsertRow(onClick: (key: Int, value: String) -> Unit) { - var keyString by remember { mutableStateOf("123") } - var valueString by remember { mutableStateOf("value") } + var keyString by remember { mutableStateOf("") } + var valueString by remember { mutableStateOf("") } fun execute() { onClick(keyString.toInt(), valueString) + keyString = "" + valueString = "" } - Row { + Row(verticalAlignment = Alignment.CenterVertically) { + Button( onClick = { - onClick(keyString.toInt(), valueString) + execute() }, modifier = Modifier.weight(0.3f).defaultMinSize(minWidth = 100.dp) ) { Text("Insert") } OutlinedTextField( + label = { Text("Key") }, value = keyString, onValueChange = { keyString = it }, - maxLines = 1, + singleLine = true, colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), modifier = Modifier.weight(0.35f).onKeyEvent { - if (it.key == Key.Enter) { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { execute() return@onKeyEvent true } @@ -81,12 +83,13 @@ fun InsertRow(onClick: (key: Int, value: String) -> Unit) { } ) OutlinedTextField( + label = { Text("Value") }, value = valueString, onValueChange = { valueString = it }, - maxLines = 1, + singleLine = true, colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), modifier = Modifier.weight(0.35f).onKeyEvent { - if (it.key == Key.Enter) { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { execute() return@onKeyEvent true } @@ -99,16 +102,17 @@ fun InsertRow(onClick: (key: Int, value: String) -> Unit) { @OptIn(ExperimentalComposeUiApi::class) @Composable fun RemoveRow(onClick: (key: Int) -> Unit) { - var keyString by remember { mutableStateOf("123") } + var keyString by remember { mutableStateOf("") } fun execute() { onClick(keyString.toInt()) + keyString = "" } - Row { + Row(verticalAlignment = Alignment.CenterVertically) { Button( onClick = { - onClick(keyString.toInt()) + execute() }, modifier = Modifier.weight(0.3f) ) { @@ -116,12 +120,13 @@ fun RemoveRow(onClick: (key: Int) -> Unit) { } OutlinedTextField( + label = { Text("Key") }, value = keyString, onValueChange = { keyString = it }, - maxLines = 1, + singleLine = true, colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), modifier = Modifier.weight(0.7f).onKeyEvent { - if (it.key == Key.Enter) { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { execute() return@onKeyEvent true } @@ -134,23 +139,25 @@ fun RemoveRow(onClick: (key: Int) -> Unit) { @OptIn(ExperimentalComposeUiApi::class) @Composable fun FindRow(onClick: (key: Int) -> (Unit)) { - var keyString by remember { mutableStateOf("123") } + var keyString by remember { mutableStateOf("") } fun execute() { onClick(keyString.toInt()) + keyString = "" } - Row { + Row(verticalAlignment = Alignment.CenterVertically) { Button(onClick = ::execute, modifier = Modifier.weight(0.3f)) { Text("Find") } OutlinedTextField( + label = { Text("Key") }, value = keyString, onValueChange = { keyString = it }, - maxLines = 1, + singleLine = true, colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), modifier = Modifier.weight(0.7f).onKeyEvent { - if (it.key == Key.Enter) { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { execute() return@onKeyEvent true } @@ -316,8 +323,8 @@ fun main() = application { state = rememberDraggableState { delta -> widthOfPanel += delta.toInt() treeOffsetX.value -= delta.toInt() / 2 - } - ) + } + ) ) } Spacer(modifier = Modifier.width(3.dp)) From f77a9527af595f97db6003d20ab357da14a2817b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 25 Apr 2023 22:30:20 +0300 Subject: [PATCH 200/296] refactor: move irf rows to separate file --- app/src/main/kotlin/org/tree/app/App.kt | 139 ++---------------- .../org/tree/app/view/ImportRemoveFindRows.kt | 136 +++++++++++++++++ 2 files changed, 146 insertions(+), 129 deletions(-) create mode 100644 app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ea79c99d..71e9a370 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -9,14 +9,17 @@ import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.key.* import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.MenuBar @@ -24,8 +27,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree -import org.tree.app.view.Logger -import org.tree.app.view.TreeView +import org.tree.app.view.* import org.tree.app.view.dialogs.io.ExportRBDialog import org.tree.app.view.dialogs.io.ImportRBDialog import org.tree.binaryTree.AVLNode @@ -46,127 +48,6 @@ enum class DialogType { EXPORT_BST } -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun InsertRow(onClick: (key: Int, value: String) -> Unit) { - var keyString by remember { mutableStateOf("") } - var valueString by remember { mutableStateOf("") } - - fun execute() { - onClick(keyString.toInt(), valueString) - keyString = "" - valueString = "" - } - - Row(verticalAlignment = Alignment.CenterVertically) { - - Button( - onClick = { - execute() - }, - modifier = Modifier.weight(0.3f).defaultMinSize(minWidth = 100.dp) - ) { - Text("Insert") - } - OutlinedTextField( - label = { Text("Key") }, - value = keyString, - onValueChange = { keyString = it }, - singleLine = true, - colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), - modifier = Modifier.weight(0.35f).onKeyEvent { - if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { - execute() - return@onKeyEvent true - } - return@onKeyEvent false - } - ) - OutlinedTextField( - label = { Text("Value") }, - value = valueString, - onValueChange = { valueString = it }, - singleLine = true, - colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), - modifier = Modifier.weight(0.35f).onKeyEvent { - if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { - execute() - return@onKeyEvent true - } - return@onKeyEvent false - } - ) - } -} - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun RemoveRow(onClick: (key: Int) -> Unit) { - var keyString by remember { mutableStateOf("") } - - fun execute() { - onClick(keyString.toInt()) - keyString = "" - } - - Row(verticalAlignment = Alignment.CenterVertically) { - Button( - onClick = { - execute() - }, - modifier = Modifier.weight(0.3f) - ) { - Text("Remove") - } - - OutlinedTextField( - label = { Text("Key") }, - value = keyString, - onValueChange = { keyString = it }, - singleLine = true, - colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), - modifier = Modifier.weight(0.7f).onKeyEvent { - if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { - execute() - return@onKeyEvent true - } - return@onKeyEvent false - } - ) - } -} - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun FindRow(onClick: (key: Int) -> (Unit)) { - var keyString by remember { mutableStateOf("") } - - fun execute() { - onClick(keyString.toInt()) - keyString = "" - } - - Row(verticalAlignment = Alignment.CenterVertically) { - Button(onClick = ::execute, modifier = Modifier.weight(0.3f)) { - Text("Find") - } - OutlinedTextField( - label = { Text("Key") }, - value = keyString, - onValueChange = { keyString = it }, - singleLine = true, - colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), - modifier = Modifier.weight(0.7f).onKeyEvent { - if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { - execute() - return@onKeyEvent true - } - return@onKeyEvent false - } - ) - } -} - fun main() = application { val icon = painterResource("icon.png") Window( @@ -229,7 +110,7 @@ fun main() = application { logString = "Node with key = $key already in tree. Nothing is done" logColor = Color.Yellow } else { - logString = "Node with key = $key and value = $value inserted" + logString = "Node with key = $key and value = \"$value\" inserted" logColor = Color.Green } }) @@ -249,7 +130,7 @@ fun main() = application { FindRow(onClick = { key -> val node = treeController.find(KVP(key)) if (node != null) { - logString = "Found node with key = $key and value = ${node.elem.v}" + logString = "Found node with key = $key and value = \"${node.elem.v}\"" logColor = Color.Green val coord = treeController.nodes[node] if (coord != null) { diff --git a/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt b/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt new file mode 100644 index 00000000..ec38eb29 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt @@ -0,0 +1,136 @@ +package org.tree.app.view + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.material.Button +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.* +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun InsertRow(onClick: (key: Int, value: String) -> Unit) { + var keyString by remember { mutableStateOf("") } + var valueString by remember { mutableStateOf("") } + + fun execute() { + onClick(keyString.toInt(), valueString) + keyString = "" + valueString = "" + } + + Row(verticalAlignment = Alignment.CenterVertically) { + + Button( + onClick = { + execute() + }, + modifier = Modifier.weight(0.3f).defaultMinSize(minWidth = 100.dp) + ) { + Text("Insert") + } + OutlinedTextField( + label = { Text("Key") }, + value = keyString, + onValueChange = { keyString = it }, + singleLine = true, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.35f).onKeyEvent { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) + OutlinedTextField( + label = { Text("Value") }, + value = valueString, + onValueChange = { valueString = it }, + singleLine = true, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.35f).onKeyEvent { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) + } +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun RemoveRow(onClick: (key: Int) -> Unit) { + var keyString by remember { mutableStateOf("") } + + fun execute() { + onClick(keyString.toInt()) + keyString = "" + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = { + execute() + }, + modifier = Modifier.weight(0.3f) + ) { + Text("Remove") + } + + OutlinedTextField( + label = { Text("Key") }, + value = keyString, + onValueChange = { keyString = it }, + singleLine = true, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.7f).onKeyEvent { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) + } +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun FindRow(onClick: (key: Int) -> (Unit)) { + var keyString by remember { mutableStateOf("") } + + fun execute() { + onClick(keyString.toInt()) + keyString = "" + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Button(onClick = ::execute, modifier = Modifier.weight(0.3f)) { + Text("Find") + } + OutlinedTextField( + label = { Text("Key") }, + value = keyString, + onValueChange = { keyString = it }, + singleLine = true, + colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White.copy(alpha = 0.3f)), + modifier = Modifier.weight(0.7f).onKeyEvent { + if ((it.key == Key.Enter) && (it.type == KeyEventType.KeyDown)) { + execute() + return@onKeyEvent true + } + return@onKeyEvent false + } + ) + } +} From 7c34daa38fa032396d3adbb473acb0b17c1fa791 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 25 Apr 2023 22:48:51 +0300 Subject: [PATCH 201/296] feat: Add non-int key processing --- app/src/main/kotlin/org/tree/app/App.kt | 20 ++++++++++++++++--- .../org/tree/app/view/ImportRemoveFindRows.kt | 12 +++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 71e9a370..5c443b65 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -68,6 +68,16 @@ fun main() = application { val treeOffsetX = remember { mutableStateOf(100) } val treeOffsetY = remember { mutableStateOf(100) } + fun convertKey(keyString: String): Int? { + return try { + keyString.toInt() + } catch (ex: NumberFormatException) { + logString = "Can't convert \"$keyString\" to int." + logColor = Color.Red + null + } + } + MenuBar { Menu("File", mnemonic = 'F') { Menu("New tree", mnemonic = 'N') { @@ -103,7 +113,8 @@ fun main() = application { Row(modifier = Modifier.background(Color.LightGray)) { Column(modifier = Modifier.width(widthOfPanel.dp)) { Column(horizontalAlignment = Alignment.CenterHorizontally) { - InsertRow(onClick = { key, value -> + InsertRow(onClick = { keyString, value -> + val key = convertKey(keyString) ?: return@InsertRow val rememd = treeController treeController = treeController.insert(KVP(key, value)) if (rememd == treeController) { @@ -115,7 +126,8 @@ fun main() = application { } }) Spacer(modifier = Modifier.size(5.dp)) - RemoveRow(onClick = { key -> + RemoveRow(onClick = { keyString -> + val key = convertKey(keyString) ?: return@RemoveRow val rememd = treeController treeController = treeController.remove(KVP(key)) if (rememd == treeController) { @@ -127,7 +139,8 @@ fun main() = application { } }) Spacer(modifier = Modifier.size(5.dp)) - FindRow(onClick = { key -> + FindRow(onClick = { keyString -> + val key = convertKey(keyString) ?: return@FindRow val node = treeController.find(KVP(key)) if (node != null) { logString = "Found node with key = $key and value = \"${node.elem.v}\"" @@ -245,6 +258,7 @@ fun main() = application { } DialogType.EXPORT_RB -> { + @Suppress("UNCHECKED_CAST") ExportRBDialog( onCloseRequest = { dialogType = DialogType.EMPTY }, treeController as TreeController>> diff --git a/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt b/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt index ec38eb29..9f7752ff 100644 --- a/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt +++ b/app/src/main/kotlin/org/tree/app/view/ImportRemoveFindRows.kt @@ -16,12 +16,12 @@ import androidx.compose.ui.unit.dp @OptIn(ExperimentalComposeUiApi::class) @Composable -fun InsertRow(onClick: (key: Int, value: String) -> Unit) { +fun InsertRow(onClick: (keyString: String, value: String) -> Unit) { var keyString by remember { mutableStateOf("") } var valueString by remember { mutableStateOf("") } fun execute() { - onClick(keyString.toInt(), valueString) + onClick(keyString, valueString) keyString = "" valueString = "" } @@ -69,11 +69,11 @@ fun InsertRow(onClick: (key: Int, value: String) -> Unit) { @OptIn(ExperimentalComposeUiApi::class) @Composable -fun RemoveRow(onClick: (key: Int) -> Unit) { +fun RemoveRow(onClick: (key: String) -> Unit) { var keyString by remember { mutableStateOf("") } fun execute() { - onClick(keyString.toInt()) + onClick(keyString) keyString = "" } @@ -106,11 +106,11 @@ fun RemoveRow(onClick: (key: Int) -> Unit) { @OptIn(ExperimentalComposeUiApi::class) @Composable -fun FindRow(onClick: (key: Int) -> (Unit)) { +fun FindRow(onClick: (key: String) -> (Unit)) { var keyString by remember { mutableStateOf("") } fun execute() { - onClick(keyString.toInt()) + onClick(keyString) keyString = "" } From c5035cf95628371e7c10a8bc7f3490551f5c19f5 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 25 Apr 2023 23:38:15 +0300 Subject: [PATCH 202/296] feat: Add auto resizable text to node --- .../kotlin/org/tree/app/view/AutoSizedText.kt | 125 ++++++++++++++++++ app/src/main/kotlin/org/tree/app/view/Node.kt | 3 +- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/org/tree/app/view/AutoSizedText.kt diff --git a/app/src/main/kotlin/org/tree/app/view/AutoSizedText.kt b/app/src/main/kotlin/org/tree/app/view/AutoSizedText.kt new file mode 100644 index 00000000..3a17f254 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/AutoSizedText.kt @@ -0,0 +1,125 @@ +package org.tree.app.view + +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit + +sealed class AutoSizeConstraint(open val min: TextUnit = TextUnit.Unspecified) { + data class Width(override val min: TextUnit = TextUnit.Unspecified) : AutoSizeConstraint(min) + data class Height(override val min: TextUnit = TextUnit.Unspecified) : AutoSizeConstraint(min) +} + +@Composable +fun AutoSizeText( + text: AnnotatedString, + fontSize: TextUnit, + modifier: Modifier = Modifier, + color: Color = Color.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + style: TextStyle = LocalTextStyle.current, + constraint: AutoSizeConstraint = AutoSizeConstraint.Width(), +) { + var newFontSize by remember { mutableStateOf(fontSize) } + var readyToDraw by remember { mutableStateOf(false) } + Text( + modifier = modifier.drawWithContent { + if (readyToDraw) drawContent() + }, + text = text, + color = color, + fontSize = newFontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + style = style, + onTextLayout = { result -> + fun constrain() { + newFontSize *= 0.9f + } + when (constraint) { + is AutoSizeConstraint.Height -> { + if (result.didOverflowHeight) { + constrain() + } else { + readyToDraw = true + } + } + + is AutoSizeConstraint.Width -> { + if (result.didOverflowWidth) { + constrain() + } else { + readyToDraw = true + } + } + } + } + ) +} + +@Composable +fun AutoSizeText( + text: String, + fontSize: TextUnit, + modifier: Modifier = Modifier, + color: Color = Color.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + style: TextStyle = LocalTextStyle.current, + constraint: AutoSizeConstraint = AutoSizeConstraint.Width(), +) { + AutoSizeText( + modifier = modifier, + text = AnnotatedString(text), + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + style = style, + constraint = constraint + ) +} diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 9c5f42e4..6826dd1c 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import org.tree.binaryTree.KVP import org.tree.binaryTree.templates.TemplateNode @@ -83,7 +84,7 @@ fun Node( } } ) { - Text(key.toString()) + AutoSizeText(fontSize = 16.sp, text = key.toString(), maxLines = 1, softWrap = false) } } } From 30a686048fb7fc5cffe07e1b3aafd72e18ff17ed Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 26 Apr 2023 00:58:09 +0300 Subject: [PATCH 203/296] fix: Fix text disappearing on resize --- app/src/main/kotlin/org/tree/app/App.kt | 7 +++---- app/src/main/kotlin/org/tree/app/view/Node.kt | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 5c443b65..ebafad6f 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @@ -222,9 +221,9 @@ fun main() = application { ) } Spacer(modifier = Modifier.width(3.dp)) - Box(Modifier.scale(1.0F)) { - TreeView(treeController, treeOffsetX, treeOffsetY) - } + + TreeView(treeController, treeOffsetX, treeOffsetY) + } when (dialogType) { diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 6826dd1c..7611ecfe 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -77,7 +77,7 @@ fun Node( Box( contentAlignment = Alignment.Center, modifier = modifier - .size(size.dp).clip(CircleShape).background(color = color) + .requiredSize(size.dp).clip(CircleShape).background(color = color) .pointerInput(onDrag) { detectDragGestures { change, dragAmount -> onDrag(change, dragAmount) From 209d3dc5cd59264b204b52aef5204b0f7639fcfc Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 26 Apr 2023 14:54:20 +0300 Subject: [PATCH 204/296] fix: Add case of empty string --- app/src/main/kotlin/org/tree/app/App.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ebafad6f..2126650f 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -68,6 +68,9 @@ fun main() = application { val treeOffsetY = remember { mutableStateOf(100) } fun convertKey(keyString: String): Int? { + if (keyString.isEmpty()) { + return null + } return try { keyString.toInt() } catch (ex: NumberFormatException) { From 567c22160656ac6011a66ac653d57ae8fbce9c66 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 26 Apr 2023 15:40:50 +0300 Subject: [PATCH 205/296] fix: Change find result --- app/src/main/kotlin/org/tree/app/App.kt | 41 +++++++++++-------- .../org/tree/app/controller/TreeController.kt | 13 +++--- app/src/main/kotlin/org/tree/app/view/Node.kt | 2 +- app/src/main/kotlin/org/tree/app/view/Tree.kt | 27 +++++++----- 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 2126650f..7ae30a59 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -64,8 +64,8 @@ fun main() = application { var dialogType by remember { mutableStateOf(DialogType.EMPTY) } var logString by remember { mutableStateOf("Log string") } var logColor by remember { mutableStateOf(Color.DarkGray) } - val treeOffsetX = remember { mutableStateOf(100) } - val treeOffsetY = remember { mutableStateOf(100) } + val treeOffsetX = remember { mutableStateOf(0) } + val treeOffsetY = remember { mutableStateOf(0) } fun convertKey(keyString: String): Int? { if (keyString.isEmpty()) { @@ -80,6 +80,24 @@ fun main() = application { } } + fun toTreeRoot() { + val treeRoot = treeController.tree.root + if (treeRoot != null) { + val coord = treeController.nodes[treeController.find(treeRoot.elem)] + if (coord != null) { + logString = "Moved to root" + logColor = Color.Green + treeOffsetX.value = -coord.x.value + treeOffsetY.value = -coord.y.value + } + } else { + logString = "Current tree is empty" + logColor = Color.Yellow + } + } + + remember { toTreeRoot() } // will be removed in future + MenuBar { Menu("File", mnemonic = 'F') { Menu("New tree", mnemonic = 'N') { @@ -149,8 +167,8 @@ fun main() = application { logColor = Color.Green val coord = treeController.nodes[node] if (coord != null) { - treeOffsetX.value = 100 - coord.x.value - treeOffsetY.value = 100 - coord.y.value + treeOffsetX.value = -coord.x.value + treeOffsetY.value = -coord.y.value } } else { logString = "No node with key = $key found" @@ -174,19 +192,7 @@ fun main() = application { } Button( onClick = { - val treeRoot = treeController.tree.root - if (treeRoot != null) { - val coord = treeController.nodes[treeController.find(treeRoot.elem)] - if (coord != null) { - logString = "Moved to root" - logColor = Color.Green - treeOffsetX.value = 100 - coord.x.value - treeOffsetY.value = 100 - coord.y.value - } - } else { - logString = "Current tree is empty" - logColor = Color.Yellow - } + toTreeRoot() }, colors = ButtonDefaults.buttonColors( backgroundColor = Color.Gray, @@ -218,7 +224,6 @@ fun main() = application { orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, state = rememberDraggableState { delta -> widthOfPanel += delta.toInt() - treeOffsetX.value -= delta.toInt() / 2 } ) ) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 92dd0daa..65e2b485 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -30,9 +30,9 @@ class TreeController, NODE_T>>( private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { var count = 0 if (node?.right != null) { - count = 1 + (height)*childrenCount(node.right) + count = 1 + (height) * childrenCount(node.right) } - val x = parentX - nodeSize - (count*nodeSize) + val x = parentX - nodeSize - (count * nodeSize) val y = parentY + nodeSize val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) @@ -47,9 +47,9 @@ class TreeController, NODE_T>>( private fun drawRight(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { var count = 0 if (node?.left != null) { - count = 1 + (height)*childrenCount(node.left) + count = 1 + (height) * childrenCount(node.left) } - val x = parentX + nodeSize + (count*nodeSize) + val x = parentX + nodeSize + (count * nodeSize) val y = parentY + nodeSize val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) @@ -114,10 +114,11 @@ class TreeController, NODE_T>>( } fun , NODE_T>, TREE_T : TemplateBSTree, NODE_T>> newTree( - emptyTree: TREE_T + emptyTree: TREE_T, + nodesCount: Int = 10 ): TreeController { val rand = Random(0x1337) - for (i in 0..10) { + for (i in 1..nodesCount) { emptyTree.insert(KVP(rand.nextInt(100), "Num: $i")) } return TreeController(emptyTree) diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 7611ecfe..5ce1d6d5 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -67,7 +67,7 @@ fun Node( ) } }, - modifier = Modifier.offset((x - size / 2).dp, (y - size / 2).dp), + modifier = Modifier.offset((x).dp, (y).dp), delayMillis = 600, tooltipPlacement = TooltipPlacement.CursorPoint( alignment = Alignment.BottomEnd, diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index 9c5e138b..963363cf 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -4,8 +4,11 @@ import TreeController 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.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color @@ -20,15 +23,18 @@ fun , NODE_T>> TreeView( offsetX: MutableState, offsetY: MutableState, ) { - Box(modifier = Modifier.background(Color.White, shape = RoundedCornerShape(16.dp)).clipToBounds() - .pointerInput(offsetX, offsetY) { - detectDragGestures { change, dragAmount -> - change.consume() - offsetX.value += dragAmount.x.toInt() - offsetY.value += dragAmount.y.toInt() - } - }) { - Box { + Box(contentAlignment = Alignment.Center, + modifier = Modifier.background(Color.White, shape = RoundedCornerShape(16.dp)).clipToBounds().fillMaxSize() + .pointerInput(offsetX, offsetY) { + detectDragGestures { change, dragAmount -> + change.consume() + offsetX.value += dragAmount.x.toInt() + offsetY.value += dragAmount.y.toInt() + } + }) + { + Box(modifier = Modifier.size(0.dp).background(Color.Green)) { + //Box { for (n in t.nodes) { val x by n.value.x val y by n.value.y @@ -52,8 +58,7 @@ fun , NODE_T>> TreeView( val col = n.value.color with(n.key.elem) { key(key) { - Node( - x + offsetX.value, + Node(x + offsetX.value, y + offsetY.value, key, v ?: "", From 75bc2a1ad90f5651909677767cbe2801329a8420 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 26 Apr 2023 15:48:43 +0300 Subject: [PATCH 206/296] fix: Fix broken tooltips --- app/src/main/kotlin/org/tree/app/view/Node.kt | 2 +- app/src/main/kotlin/org/tree/app/view/Tree.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 5ce1d6d5..7611ecfe 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -67,7 +67,7 @@ fun Node( ) } }, - modifier = Modifier.offset((x).dp, (y).dp), + modifier = Modifier.offset((x - size / 2).dp, (y - size / 2).dp), delayMillis = 600, tooltipPlacement = TooltipPlacement.CursorPoint( alignment = Alignment.BottomEnd, diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index 963363cf..9ff259e5 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -33,8 +33,7 @@ fun , NODE_T>> TreeView( } }) { - Box(modifier = Modifier.size(0.dp).background(Color.Green)) { - //Box { + Box(modifier = Modifier.size(t.nodeSize.dp)) { for (n in t.nodes) { val x by n.value.x val y by n.value.y From 45777cf387fbcadb231b5f8ad645a68ffa8da462 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 18:09:51 +0300 Subject: [PATCH 207/296] feat(Contributing): Add remove --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e72371a..ac92f42f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,7 @@ * `struct` для изменений связанных с изменением структуры проекта (НО НЕ КОДА), например изменение расположения папок * `ci` для различных задач ci/cd +* `remove` для удаления Поле `` опционально и показывает к какому модулю, классу, методу функции и т.п применены изменения. From defdfdaa9b8e5892428e35594233f19d16ea1644 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 28 Apr 2023 18:29:37 +0300 Subject: [PATCH 208/296] feat(FilesIOHandler): Add Import and Export of SQLite files in UI --- app/src/main/kotlin/org/tree/app/App.kt | 21 +++++++--- .../app/view/dialogs/io/FilesIOHandler.kt | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 7ae30a59..ca9ca615 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -18,6 +18,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.awt.ComposeWindow import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @@ -29,6 +30,8 @@ import newTree import org.tree.app.view.* import org.tree.app.view.dialogs.io.ExportRBDialog import org.tree.app.view.dialogs.io.ImportRBDialog +import org.tree.app.view.dialogs.io.exportBST +import org.tree.app.view.dialogs.io.importBST import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP import org.tree.binaryTree.Node @@ -37,6 +40,7 @@ import org.tree.binaryTree.trees.AVLTree import org.tree.binaryTree.trees.BinSearchTree import org.tree.binaryTree.trees.RBTree + enum class DialogType { EMPTY, IMPORT_RB, @@ -106,14 +110,24 @@ fun main() = application { Item("AVL Tree") { treeController = newTree(AVLTree()) } } Menu("Open", mnemonic = 'O') { - Item("Bin Search Tree", onClick = { dialogType = DialogType.IMPORT_BST }) + Item("Bin Search Tree", onClick = { + val tc = importBST(ComposeWindow()) + if (tc != null) { + treeController = tc + } + }) Item("Red black Tree", onClick = { dialogType = DialogType.IMPORT_RB }) Item("AVL Tree", onClick = { dialogType = DialogType.IMPORT_AVL }) } Menu("Save", mnemonic = 'S') { Item( "Bin Search Tree", - onClick = { dialogType = DialogType.EXPORT_BST }, + onClick = { + exportBST( + ComposeWindow(), + treeController as TreeController>> + ) + }, enabled = (treeController.nodeType() is Node<*>?) ) Item( @@ -229,7 +243,6 @@ fun main() = application { ) } Spacer(modifier = Modifier.width(3.dp)) - TreeView(treeController, treeOffsetX, treeOffsetY) } @@ -249,12 +262,10 @@ fun main() = application { } DialogType.IMPORT_BST -> { - TODO("Implement") dialogType = DialogType.EMPTY } DialogType.EXPORT_BST -> { - TODO("Implement") dialogType = DialogType.EMPTY } diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt new file mode 100644 index 00000000..6095e59a --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -0,0 +1,38 @@ +package org.tree.app.view.dialogs.io + +import TreeController +import androidx.compose.ui.awt.ComposeWindow +import org.tree.app.controller.io.SQLiteIO +import org.tree.binaryTree.KVP +import org.tree.binaryTree.Node +import java.awt.FileDialog +import java.io.File + +fun importBST( + window: ComposeWindow, +): TreeController>>? { + val fileString = selectFile(window, "sqlite") ?: return null + val file = File(fileString) + val db = SQLiteIO() + return db.importTree(file) +} + +fun exportBST(window: ComposeWindow, tc: TreeController>>) { + val fileString = selectFile(window, "sqlite") ?: return + val file = File(fileString) + val db = SQLiteIO() + db.exportTree(tc, file) +} + +fun selectFile(window: ComposeWindow, fileFormant: String): String? { + val fd = FileDialog(window, "Choose a file", FileDialog.LOAD) + fd.directory = "C:\\" + fd.file = "*.$fileFormant" + fd.isVisible = true + val fileString = fd.directory + fd.file + if (fileString != "nullnull") { + return fd.directory + fd.file + } + return null +} + From 237986ecea42ceed74518b6e234ecdb83045e221 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 28 Apr 2023 23:34:29 +0300 Subject: [PATCH 209/296] feat(FilesIOHandler): Add Import and Export of json files in UI --- app/src/main/kotlin/org/tree/app/App.kt | 19 +++++++++++++------ .../app/view/dialogs/io/FilesIOHandler.kt | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ca9ca615..ff3b6e1f 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -28,10 +28,7 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree import org.tree.app.view.* -import org.tree.app.view.dialogs.io.ExportRBDialog -import org.tree.app.view.dialogs.io.ImportRBDialog -import org.tree.app.view.dialogs.io.exportBST -import org.tree.app.view.dialogs.io.importBST +import org.tree.app.view.dialogs.io.* import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP import org.tree.binaryTree.Node @@ -117,7 +114,12 @@ fun main() = application { } }) Item("Red black Tree", onClick = { dialogType = DialogType.IMPORT_RB }) - Item("AVL Tree", onClick = { dialogType = DialogType.IMPORT_AVL }) + Item("AVL Tree", onClick = { + val tc = importAVLT(ComposeWindow()) + if (tc != null) { + treeController = tc + } + }) } Menu("Save", mnemonic = 'S') { Item( @@ -137,7 +139,12 @@ fun main() = application { ) Item( "AVL Tree", - onClick = { dialogType = DialogType.EXPORT_AVL }, + onClick = { + exportAVLT( + ComposeWindow(), + treeController as TreeController>> + ) + }, enabled = (treeController.nodeType() is AVLNode<*>?) ) } diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 6095e59a..5081f899 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -2,12 +2,30 @@ package org.tree.app.view.dialogs.io import TreeController import androidx.compose.ui.awt.ComposeWindow +import org.tree.app.controller.io.Json import org.tree.app.controller.io.SQLiteIO +import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP import org.tree.binaryTree.Node import java.awt.FileDialog import java.io.File +fun importAVLT( + window: ComposeWindow, +): TreeController>>? { + val fileString = selectFile(window, "json") ?: return null + val file = File(fileString) + val db = Json() + return db.importTree(file) +} + +fun exportAVLT(window: ComposeWindow, tc: TreeController>>) { + val fileString = selectFile(window, "json") ?: return + val file = File(fileString) + val db = Json() + db.exportTree(tc, file) +} + fun importBST( window: ComposeWindow, ): TreeController>>? { From 3f69cca62047b66849af9d64eae7ec31b41d93ad Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 00:08:05 +0300 Subject: [PATCH 210/296] refactor(FilesIOHandler): Remove unused DialogTypes --- app/src/main/kotlin/org/tree/app/App.kt | 27 ++----------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ff3b6e1f..fea94fcc 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -41,11 +41,7 @@ import org.tree.binaryTree.trees.RBTree enum class DialogType { EMPTY, IMPORT_RB, - IMPORT_AVL, - IMPORT_BST, - EXPORT_RB, - EXPORT_AVL, - EXPORT_BST + EXPORT_RB } fun main() = application { @@ -258,28 +254,9 @@ fun main() = application { DialogType.EMPTY -> { } - DialogType.IMPORT_AVL -> { - TODO("Implement") - dialogType = DialogType.EMPTY - } - - DialogType.EXPORT_AVL -> { - TODO("Implement") - dialogType = DialogType.EMPTY - } - - DialogType.IMPORT_BST -> { - dialogType = DialogType.EMPTY - } - - DialogType.EXPORT_BST -> { - dialogType = DialogType.EMPTY - } - DialogType.IMPORT_RB -> { ImportRBDialog( - onCloseRequest = { dialogType = DialogType.EMPTY }, - onSuccess = { treeController = it }) + onCloseRequest = { dialogType = DialogType.EMPTY }, onSuccess = { treeController = it }) } DialogType.EXPORT_RB -> { From 7ab1a00311651d5031befef25d00bfc65c20fcfe Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 00:10:44 +0300 Subject: [PATCH 211/296] refactor(FilesIOHandler): Add Suppress --- app/src/main/kotlin/org/tree/app/App.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index fea94fcc..dd8f8dfe 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -121,6 +121,7 @@ fun main() = application { Item( "Bin Search Tree", onClick = { + @Suppress("UNCHECKED_CAST") exportBST( ComposeWindow(), treeController as TreeController>> @@ -136,6 +137,7 @@ fun main() = application { Item( "AVL Tree", onClick = { + @Suppress("UNCHECKED_CAST") exportAVLT( ComposeWindow(), treeController as TreeController>> From 608423de38951651ba28cacf2467357337e447b0 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 01:02:54 +0300 Subject: [PATCH 212/296] refactor(App, Tree): Change some variables, fix typos --- app/src/main/kotlin/org/tree/app/App.kt | 38 +++++++++---------- app/src/main/kotlin/org/tree/app/view/Tree.kt | 14 +++---- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index dd8f8dfe..ab4ae0b5 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -80,15 +80,15 @@ fun main() = application { fun toTreeRoot() { val treeRoot = treeController.tree.root if (treeRoot != null) { - val coord = treeController.nodes[treeController.find(treeRoot.elem)] - if (coord != null) { - logString = "Moved to root" + val coordinates = treeController.nodes[treeController.find(treeRoot.elem)] + if (coordinates != null) { + logString = "Moved to root." logColor = Color.Green - treeOffsetX.value = -coord.x.value - treeOffsetY.value = -coord.y.value + treeOffsetX.value = -coordinates.x.value + treeOffsetY.value = -coordinates.y.value } } else { - logString = "Current tree is empty" + logString = "Current tree is empty." logColor = Color.Yellow } } @@ -154,13 +154,13 @@ fun main() = application { Column(horizontalAlignment = Alignment.CenterHorizontally) { InsertRow(onClick = { keyString, value -> val key = convertKey(keyString) ?: return@InsertRow - val rememd = treeController + val treeBeforeInsert = treeController treeController = treeController.insert(KVP(key, value)) - if (rememd == treeController) { - logString = "Node with key = $key already in tree. Nothing is done" + if (treeBeforeInsert == treeController) { + logString = "The node with key = $key is already in the tree. Nothing has been done." logColor = Color.Yellow } else { - logString = "Node with key = $key and value = \"$value\" inserted" + logString = "The node with key = $key and value = \"$value\" has been inserted." logColor = Color.Green } }) @@ -170,10 +170,10 @@ fun main() = application { val rememd = treeController treeController = treeController.remove(KVP(key)) if (rememd == treeController) { - logString = "There isn't node with key = $key in tree. Nothing is done" + logString = "There is no node with key = $key in the tree. Nothing has been done." logColor = Color.Yellow } else { - logString = "Node with key = $key removed" + logString = "The node with key = $key has been removed." logColor = Color.Green } }) @@ -182,15 +182,15 @@ fun main() = application { val key = convertKey(keyString) ?: return@FindRow val node = treeController.find(KVP(key)) if (node != null) { - logString = "Found node with key = $key and value = \"${node.elem.v}\"" + logString = "Node with key = $key and value = \"${node.elem.v}\" found." logColor = Color.Green - val coord = treeController.nodes[node] - if (coord != null) { - treeOffsetX.value = -coord.x.value - treeOffsetY.value = -coord.y.value + val coordinates = treeController.nodes[node] + if (coordinates != null) { + treeOffsetX.value = -coordinates.x.value + treeOffsetY.value = -coordinates.y.value } } else { - logString = "No node with key = $key found" + logString = "Node with key = $key not found." logColor = Color.Yellow } }) @@ -199,7 +199,7 @@ fun main() = application { Button( onClick = { treeController = TreeController(treeController.tree) - logString = "Tree coordinates reset" + logString = "Tree coordinates reset." logColor = Color.Green }, colors = ButtonDefaults.buttonColors( diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index 9ff259e5..86939aad 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -19,7 +19,7 @@ import org.tree.binaryTree.templates.TemplateNode @Composable fun , NODE_T>> TreeView( - t: TreeController, + treeController: TreeController, offsetX: MutableState, offsetY: MutableState, ) { @@ -33,25 +33,25 @@ fun , NODE_T>> TreeView( } }) { - Box(modifier = Modifier.size(t.nodeSize.dp)) { - for (n in t.nodes) { + Box(modifier = Modifier.size(treeController.nodeSize.dp)) { + for (n in treeController.nodes) { val x by n.value.x val y by n.value.y with(n.key) { - val l = t.nodes[left] + val l = treeController.nodes[left] if (l != null) { Line(x + offsetX.value, y + offsetY.value, l.x.value + offsetX.value, l.y.value + offsetY.value) } - val r = t.nodes[right] + val r = treeController.nodes[right] if (r != null) { Line(x + offsetX.value, y + offsetY.value, r.x.value + offsetX.value, r.y.value + offsetY.value) } } } - for (n in t.nodes) { + for (n in treeController.nodes) { var x by n.value.x var y by n.value.y val col = n.value.color @@ -61,7 +61,7 @@ fun , NODE_T>> TreeView( y + offsetY.value, key, v ?: "", - size = t.nodeSize, + size = treeController.nodeSize, color = col, onDrag = { change, dragAmount -> change.consume() From 5a0229a82ce58a8c05ab6acb3506aee36b0c7fdb Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 19:13:16 +0300 Subject: [PATCH 213/296] feat(FilesIOHandler): Add special window for save in db --- .../tree/app/view/dialogs/io/FilesIOHandler.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 5081f899..7ba8ed99 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -13,14 +13,14 @@ import java.io.File fun importAVLT( window: ComposeWindow, ): TreeController>>? { - val fileString = selectFile(window, "json") ?: return null + val fileString = selectFile(window, "json", "import") ?: return null val file = File(fileString) val db = Json() return db.importTree(file) } fun exportAVLT(window: ComposeWindow, tc: TreeController>>) { - val fileString = selectFile(window, "json") ?: return + val fileString = selectFile(window, "json", "export") ?: return val file = File(fileString) val db = Json() db.exportTree(tc, file) @@ -29,21 +29,25 @@ fun exportAVLT(window: ComposeWindow, tc: TreeController>>? { - val fileString = selectFile(window, "sqlite") ?: return null + val fileString = selectFile(window, "sqlite", "import") ?: return null val file = File(fileString) val db = SQLiteIO() return db.importTree(file) } fun exportBST(window: ComposeWindow, tc: TreeController>>) { - val fileString = selectFile(window, "sqlite") ?: return + val fileString = selectFile(window, "sqlite", "export") ?: return val file = File(fileString) val db = SQLiteIO() db.exportTree(tc, file) } -fun selectFile(window: ComposeWindow, fileFormant: String): String? { - val fd = FileDialog(window, "Choose a file", FileDialog.LOAD) +fun selectFile(window: ComposeWindow, fileFormant: String, mode: String): String? { + val fd = if (mode == "import") { + FileDialog(window, "Choose .sqlite file to import", FileDialog.LOAD) + } else { + FileDialog(window, "Choose .sqlite file to export", FileDialog.SAVE) + } fd.directory = "C:\\" fd.file = "*.$fileFormant" fd.isVisible = true From 586e321385b55b8de90b340a7103ed5ccb3b2130 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 00:16:07 +0300 Subject: [PATCH 214/296] fix(FilesIOHandler): Fix case of import empty tree --- app/src/main/kotlin/org/tree/app/App.kt | 5 +++++ .../kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index ab4ae0b5..8c17e295 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -107,6 +107,11 @@ fun main() = application { val tc = importBST(ComposeWindow()) if (tc != null) { treeController = tc + logString = "" + } + else{ + logString = "Database has no Nodes table" + logColor = Color.Red } }) Item("Red black Tree", onClick = { dialogType = DialogType.IMPORT_RB }) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 7ba8ed99..10804d57 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -2,6 +2,7 @@ package org.tree.app.view.dialogs.io import TreeController import androidx.compose.ui.awt.ComposeWindow +import org.tree.app.controller.io.HandledIOException import org.tree.app.controller.io.Json import org.tree.app.controller.io.SQLiteIO import org.tree.binaryTree.AVLNode @@ -32,7 +33,11 @@ fun importBST( val fileString = selectFile(window, "sqlite", "import") ?: return null val file = File(fileString) val db = SQLiteIO() - return db.importTree(file) + return try { + db.importTree(file) + } catch(ex: HandledIOException) { + null + } } fun exportBST(window: ComposeWindow, tc: TreeController>>) { From d7c9dbdc1edb697fcf4455ce64a1bb28244c778f Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 12:31:51 +0300 Subject: [PATCH 215/296] fix(SQLiteIO): Add change of color --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 4ae39c30..b78786ea 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -3,6 +3,7 @@ package org.tree.app.controller.io import NodeExtension import TreeController import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.graphics.Color import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID @@ -171,6 +172,6 @@ class SQLiteIO { node: Node>, x: Int, y: Int ) { - treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y)) + treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), Color.Yellow) } } From 845e29dfaadd9bead85fe83d0a5f0b4406960091 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 13:49:37 +0300 Subject: [PATCH 216/296] feat(SQLiteIO): Add check for double left/right child --- .../kotlin/org/tree/app/controller/io/SQLiteIO.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index b78786ea..4d50f047 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -41,7 +41,7 @@ class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with class SQLiteIO { private var amountOfNodesToHandle = 0 - private lateinit var treeController : TreeController>> + private lateinit var treeController: TreeController>> fun importTree(file: File): TreeController>> { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") try { @@ -101,6 +101,9 @@ class SQLiteIO { if (root != null) { addCoordinatesToNode(root, parsedX, parsedY) parseNodesForImport(setOfNodes, root) + if (setOfNodes.count() > 0) { + throw IOException("Incorrect binary tree: there are at least two left/right children of some node") + } } } catch (ex: NumberFormatException) { throw IOException( @@ -119,30 +122,32 @@ class SQLiteIO { } val nodes = setOfNodes.elementAt(0) val parsedKey = nodes.key - val parsedParentKey: Int = nodes.parentKey ?: throw IOException("Incorrect binary tree") + val parsedParentKey: Int = + nodes.parentKey ?: throw IOException("Incorrect binary tree: there are at least 2 roots") val parsedValue = nodes.value val parsedX = nodes.x val parsedY = nodes.y + if (parsedKey == parsedParentKey) throw IOException("Child with key = ${curNode.elem.key} is parent for himself") if (parsedParentKey == curNode.elem.key) { val newNode = Node(KVP(parsedKey, parsedValue)) addCoordinatesToNode(newNode, parsedX, parsedY) setOfNodes.remove(nodes) amountOfNodesToHandle-- if (parsedKey < parsedParentKey) { + if (curNode.left != null) throw IOException("Incorrect binary tree: there are at least two left children of node with key = ${curNode.elem.key}") curNode.left = newNode val leftChild = curNode.left if (leftChild != null) { parseNodesForImport(setOfNodes, leftChild) } parseNodesForImport(setOfNodes, curNode) - } else if (parsedKey > parsedParentKey) { + } else { // When parsedKey is greater than parsedParentKey + if (curNode.right != null) throw IOException("Incorrect binary tree: there are at least two right children of node with key = ${curNode.elem.key}") curNode.right = newNode val rightChild = curNode.right if (rightChild != null) { parseNodesForImport(setOfNodes, rightChild) } - } else { - throw IOException("Incorrect binary tree") } } } From c072be108048c070a3c98bd1a9c77897fe4ecc7a Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 14:03:18 +0300 Subject: [PATCH 217/296] feat(SQLiteIO): Change IOException to HandledIOException --- .../org/tree/app/controller/io/SQLiteIO.kt | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 4d50f047..ee68531e 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -18,7 +18,6 @@ import org.tree.binaryTree.KVP import org.tree.binaryTree.Node import org.tree.binaryTree.trees.BinSearchTree import java.io.File -import java.io.IOException import java.nio.file.Files object Nodes : IntIdTable() { @@ -55,7 +54,7 @@ class SQLiteIO { } } } catch (ex: ExposedSQLException) { - throw IOException("File is not a database", ex) + throw HandledIOException("File is not a database", ex) } return treeController } @@ -64,7 +63,7 @@ class SQLiteIO { try { Files.createDirectories(file.toPath().parent) } catch (ex: SecurityException) { - throw IOException("Directory ${file.toPath().parent} cannot be created: no access", ex) + throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") transaction { @@ -73,7 +72,7 @@ class SQLiteIO { SchemaUtils.drop(Nodes) SchemaUtils.create(Nodes) } catch (ex: ExposedSQLException) { - throw IOException("File is not a database", ex) + throw HandledIOException("File is not a database", ex) } treeController = treeController_ val root = treeController.tree.root @@ -101,12 +100,12 @@ class SQLiteIO { if (root != null) { addCoordinatesToNode(root, parsedX, parsedY) parseNodesForImport(setOfNodes, root) - if (setOfNodes.count() > 0) { - throw IOException("Incorrect binary tree: there are at least two left/right children of some node") + if (setOfNodes.isNotEmpty()) { + throw HandledIOException("Incorrect binary tree: there are at least two left/right children of some node") } } } catch (ex: NumberFormatException) { - throw IOException( + throw HandledIOException( "Node keys must be integers, value must be string and coordinates must be doubles", ex ) @@ -123,18 +122,18 @@ class SQLiteIO { val nodes = setOfNodes.elementAt(0) val parsedKey = nodes.key val parsedParentKey: Int = - nodes.parentKey ?: throw IOException("Incorrect binary tree: there are at least 2 roots") + nodes.parentKey ?: throw HandledIOException("Incorrect binary tree: there are at least 2 roots") val parsedValue = nodes.value val parsedX = nodes.x val parsedY = nodes.y - if (parsedKey == parsedParentKey) throw IOException("Child with key = ${curNode.elem.key} is parent for himself") + if (parsedKey == parsedParentKey) throw HandledIOException("Child with key = ${curNode.elem.key} is parent for himself") if (parsedParentKey == curNode.elem.key) { val newNode = Node(KVP(parsedKey, parsedValue)) addCoordinatesToNode(newNode, parsedX, parsedY) setOfNodes.remove(nodes) amountOfNodesToHandle-- if (parsedKey < parsedParentKey) { - if (curNode.left != null) throw IOException("Incorrect binary tree: there are at least two left children of node with key = ${curNode.elem.key}") + if (curNode.left != null) throw HandledIOException("Incorrect binary tree: there are at least two left children of node with key = ${curNode.elem.key}") curNode.left = newNode val leftChild = curNode.left if (leftChild != null) { @@ -142,7 +141,7 @@ class SQLiteIO { } parseNodesForImport(setOfNodes, curNode) } else { // When parsedKey is greater than parsedParentKey - if (curNode.right != null) throw IOException("Incorrect binary tree: there are at least two right children of node with key = ${curNode.elem.key}") + if (curNode.right != null) throw HandledIOException("Incorrect binary tree: there are at least two right children of node with key = ${curNode.elem.key}") curNode.right = newNode val rightChild = curNode.right if (rightChild != null) { From 2bad2ebff0559630a8f2880b51f0ae3d2c45497a Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 14:13:29 +0300 Subject: [PATCH 218/296] feat(Readme): Add some details about SQLiteIO methods --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4956ad14..04dbae59 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,9 @@ At SQLite database we store BinSearchTree. There are 2 types of objects with the We have functions: -- `exportTree(TreeController, file)` - writes information stored in TreeController object to a `file` -- `importTree(file)` - reads the tree stored in the `file` and returns TreeController object +- `exportTree(TreeController, file)` - writes information stored in TreeController object to a `file` in preorder order. +- `importTree(file)` - reads the tree stored in the `file` and returns TreeController object. **Nodes must be stored in preorder order (check root -> then check left child -> then check right child).** +If there are some extra tree nodes, that can't fit in the tree, then `importTree` will throw `HandledIOException`. ### Neo4j From 862cc78870a4577107e1a14d210909696f5a1f65 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 18:40:05 +0300 Subject: [PATCH 219/296] remove(SQLiteIO): Remove logger --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index ee68531e..f6358581 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -45,7 +45,6 @@ class SQLiteIO { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") try { transaction { - addLogger(StdOutSqlLogger) val setOfNodes = InstanceOfNode.all().toMutableSet() val amountOfNodes = setOfNodes.count() if (amountOfNodes > 0) { @@ -67,7 +66,6 @@ class SQLiteIO { } Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") transaction { - addLogger(StdOutSqlLogger) try { SchemaUtils.drop(Nodes) SchemaUtils.create(Nodes) From 6001957f0312d3bd3f1deeb0b006d8a00f8c3254 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 18:54:25 +0300 Subject: [PATCH 220/296] remove(SQLiteIO): Remove unused imports --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index f6358581..d7dcc78f 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -11,8 +11,6 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.transaction import org.tree.binaryTree.KVP import org.tree.binaryTree.Node From 0b916a752dbf39536c6ba5acf06fc93a2540de5a Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 19:52:26 +0300 Subject: [PATCH 221/296] fix(FilesIOHandler): Fix case of import/export of null node table --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index d7dcc78f..6203f372 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -41,12 +41,12 @@ class SQLiteIO { private lateinit var treeController: TreeController>> fun importTree(file: File): TreeController>> { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") + treeController = TreeController(BinSearchTree()) try { transaction { val setOfNodes = InstanceOfNode.all().toMutableSet() val amountOfNodes = setOfNodes.count() if (amountOfNodes > 0) { - treeController = TreeController(BinSearchTree()) parseRootForImport(setOfNodes) } } @@ -63,6 +63,7 @@ class SQLiteIO { throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") + treeController = treeController_ transaction { try { SchemaUtils.drop(Nodes) @@ -70,7 +71,6 @@ class SQLiteIO { } catch (ex: ExposedSQLException) { throw HandledIOException("File is not a database", ex) } - treeController = treeController_ val root = treeController.tree.root if (root != null) { parseNodesForExport(root, null) From 949267a54323af917ceed50e8b8f376756d736e8 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 22:33:03 +0300 Subject: [PATCH 222/296] fix(SQLiteIO): Fix case of import database without Nodes table --- .../kotlin/org/tree/app/controller/io/SQLiteIO.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 6203f372..c7915cf6 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -11,6 +11,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.exceptions.ExposedSQLException import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.exists import org.jetbrains.exposed.sql.transactions.transaction import org.tree.binaryTree.KVP import org.tree.binaryTree.Node @@ -44,10 +45,15 @@ class SQLiteIO { treeController = TreeController(BinSearchTree()) try { transaction { - val setOfNodes = InstanceOfNode.all().toMutableSet() - val amountOfNodes = setOfNodes.count() - if (amountOfNodes > 0) { - parseRootForImport(setOfNodes) + if (Nodes.exists()) { + val setOfNodes = InstanceOfNode.all().toMutableSet() + val amountOfNodes = setOfNodes.count() + if (amountOfNodes > 0) { + parseRootForImport(setOfNodes) + } + } + else{ + throw HandledIOException("Database without a node table") } } } catch (ex: ExposedSQLException) { From d27e834bbde92f1ed3e32c159ec159896ac04b5e Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 22:33:43 +0300 Subject: [PATCH 223/296] fix(TreeController): Open getNodeCol method --- app/src/main/kotlin/org/tree/app/controller/TreeController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 65e2b485..569679ea 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -83,7 +83,7 @@ class TreeController, NODE_T>>( return tree.find(obj) } - private fun getNodeCol(curNode: NODE_T): Color { + fun getNodeCol(curNode: NODE_T): Color { return if (curNode is RBNode<*>) { if (curNode.col == RBNode.Colour.BLACK) { Color.DarkGray From 41096d34268a36fbe571a03b75cc2c175c80a976 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 29 Apr 2023 22:49:39 +0300 Subject: [PATCH 224/296] fix(SQLiteIO): Fix setting color of node --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index c7915cf6..a082fb84 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -3,7 +3,6 @@ package org.tree.app.controller.io import NodeExtension import TreeController import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.graphics.Color import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID @@ -178,6 +177,6 @@ class SQLiteIO { node: Node>, x: Int, y: Int ) { - treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), Color.Yellow) + treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), treeController.getNodeCol(node)) } } From dcbda7616add57a0059754b45f23af0c969fb1e6 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 12:05:35 +0300 Subject: [PATCH 225/296] feat(FilesIOHandler): Use enum as function argument --- .../tree/app/view/dialogs/io/FilesIOHandler.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 10804d57..9d390f7a 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -14,14 +14,14 @@ import java.io.File fun importAVLT( window: ComposeWindow, ): TreeController>>? { - val fileString = selectFile(window, "json", "import") ?: return null + val fileString = selectFile(window, "json", Mode.IMPORT) ?: return null val file = File(fileString) val db = Json() return db.importTree(file) } fun exportAVLT(window: ComposeWindow, tc: TreeController>>) { - val fileString = selectFile(window, "json", "export") ?: return + val fileString = selectFile(window, "json", Mode.EXPORT) ?: return val file = File(fileString) val db = Json() db.exportTree(tc, file) @@ -30,7 +30,7 @@ fun exportAVLT(window: ComposeWindow, tc: TreeController>>? { - val fileString = selectFile(window, "sqlite", "import") ?: return null + val fileString = selectFile(window, "sqlite", Mode.IMPORT) ?: return null val file = File(fileString) val db = SQLiteIO() return try { @@ -41,14 +41,17 @@ fun importBST( } fun exportBST(window: ComposeWindow, tc: TreeController>>) { - val fileString = selectFile(window, "sqlite", "export") ?: return + val fileString = selectFile(window, "sqlite", Mode.EXPORT) ?: return val file = File(fileString) val db = SQLiteIO() db.exportTree(tc, file) } - -fun selectFile(window: ComposeWindow, fileFormant: String, mode: String): String? { - val fd = if (mode == "import") { +enum class Mode{ + IMPORT, + EXPORT +} +fun selectFile(window: ComposeWindow, fileFormant: String, mode: Mode): String? { + val fd = if (mode == Mode.IMPORT) { FileDialog(window, "Choose .sqlite file to import", FileDialog.LOAD) } else { FileDialog(window, "Choose .sqlite file to export", FileDialog.SAVE) From c371fe52f00a4d6ecc372da823325bea81a2d4c3 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 12:09:01 +0300 Subject: [PATCH 226/296] feat(FilesIOHandler): Format code --- .../kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 9d390f7a..9a00167a 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -35,7 +35,7 @@ fun importBST( val db = SQLiteIO() return try { db.importTree(file) - } catch(ex: HandledIOException) { + } catch (ex: HandledIOException) { null } } @@ -46,10 +46,12 @@ fun exportBST(window: ComposeWindow, tc: TreeController>>) val db = SQLiteIO() db.exportTree(tc, file) } -enum class Mode{ + +enum class Mode { IMPORT, EXPORT } + fun selectFile(window: ComposeWindow, fileFormant: String, mode: Mode): String? { val fd = if (mode == Mode.IMPORT) { FileDialog(window, "Choose .sqlite file to import", FileDialog.LOAD) From e139d5c8ef975c48670171e59e6bacfff5c48dde Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 29 Apr 2023 22:22:40 +0300 Subject: [PATCH 227/296] feat: Init AppData class --- .../app/controller/io/AppDataController.kt | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt diff --git a/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt b/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt new file mode 100644 index 00000000..bbe85d4c --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt @@ -0,0 +1,125 @@ +package org.tree.app.controller.io + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encodeToString +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import java.io.File +import java.io.FileNotFoundException +import java.nio.file.Files + +@Serializable(with = Neo4jDataSerializer::class) +class Neo4jData( + urlValue: String = "bolt://localhost:7687", + loginValue: String = "neo4j", + passwordValue: String = "qwertyui" +) { + var url by mutableStateOf(urlValue) + var login by mutableStateOf(loginValue) + var password by mutableStateOf(passwordValue) +} + + +object Neo4jDataSerializer : KSerializer { + @Serializable + class SurrogateNeo4jData(val url: String, val login: String, val password: String) + + override val descriptor: SerialDescriptor = SurrogateNeo4jData.serializer().descriptor + + override fun serialize(encoder: Encoder, value: Neo4jData) { + val surrogate = SurrogateNeo4jData(value.url, value.login, value.password) + encoder.encodeSerializableValue(SurrogateNeo4jData.serializer(), surrogate) + } + + override fun deserialize(decoder: Decoder): Neo4jData { + val surrogate = decoder.decodeSerializableValue(SurrogateNeo4jData.serializer()) + return Neo4jData(surrogate.url, surrogate.login, surrogate.password) + } +} + +enum class SavedType { + SQLite, + Json, + Neo4j +} + +@Serializable +class SavedTree( + val type: SavedType, + val path: String +) + +@Serializable +class AppData { + var neo4j = Neo4jData() + var lastTree: SavedTree? = null +} + +class AppDataController { + val file = getAppDataFile() + var data: AppData + + init { + data = loadData() + saveData() + } + + fun loadData(): AppData { + val appData = try { + val fileContent = file.readText() + Json.decodeFromString(fileContent) + } catch (ex: Exception) { + when (ex) { + is FileNotFoundException, is IllegalArgumentException -> { + AppData() + } + + else -> throw ex + } + } + return appData + } + + fun saveData(appData: AppData = data) { + try { + file.writeText(Json.encodeToString(appData)) + } catch (ex: SecurityException) { + // If we can't create file, we wouldn't create it. + // Because it is bad to throw warning or exception at the start of app + // throw HandledIOException("Directory ${file.toPath()} cannot be created: no access", ex) + } + } + + private fun getAppDataFile(): File { + val workFile = File(getWorkingDirPath() + "appData.json") + try { + Files.createDirectories(workFile.toPath().parent) + workFile.createNewFile() + } catch (ex: SecurityException) { + // If we can't create directory, we wouldn't create it. + // Because it is bad to throw warning or exception at the start of app + // throw HandledIOException("Directory ${workFile.toPath().parent} cannot be created: no access", ex) + } + return workFile + } + + private fun getWorkingDirPath(): String { + val os = (System.getProperty("os.name")).uppercase() + var workingDirectory: String + if (os.contains("WIN")) { + workingDirectory = System.getenv("AppData") + workingDirectory += "\\trees2\\" + } else { + workingDirectory = System.getProperty("user.home") + workingDirectory += "/.trees2/" + } + return workingDirectory + } +} From f471641669cf952a9cd86ea0fd3cfdbe76f30c33 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 29 Apr 2023 22:36:38 +0300 Subject: [PATCH 228/296] fix: Edit Neo4jConnect and add global variable appDataController --- app/src/main/kotlin/org/tree/app/App.kt | 2 ++ .../tree/app/view/dialogs/io/Neo4jConnect.kt | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 8c17e295..798a0377 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree +import org.tree.app.controller.io.AppDataController import org.tree.app.view.* import org.tree.app.view.dialogs.io.* import org.tree.binaryTree.AVLNode @@ -44,6 +45,7 @@ enum class DialogType { EXPORT_RB } +val appDataController = AppDataController() fun main() = application { val icon = painterResource("icon.png") Window( diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt index f2fea1e4..fa8b9f91 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.PasswordVisualTransformation +import org.tree.app.appDataController import org.tree.app.controller.io.HandledIOException import org.tree.app.controller.io.Neo4jIO import org.tree.app.view.Logger @@ -16,25 +17,31 @@ import org.tree.app.view.Logger @Composable fun Neo4jConnect(onSuccess: (Neo4jIO) -> Unit, onFail: (Neo4jIO) -> Unit) { - var urlString by remember { mutableStateOf("bolt://localhost:7687") } - var loginString by remember { mutableStateOf("neo4j") } - var passwordString by remember { mutableStateOf("qwertyui") } var connectionStatus by remember { mutableStateOf("Not connected") } var iconColor by remember { mutableStateOf(Color.Red) } Column(horizontalAlignment = Alignment.CenterHorizontally) { - OutlinedTextField(value = urlString, onValueChange = { urlString = it }) - OutlinedTextField(value = loginString, onValueChange = { loginString = it }) OutlinedTextField( - value = passwordString, - onValueChange = { passwordString = it }, + value = appDataController.data.neo4j.url, + onValueChange = { appDataController.data.neo4j.url = it }) + OutlinedTextField( + value = appDataController.data.neo4j.login, + onValueChange = { appDataController.data.neo4j.login = it }) + OutlinedTextField( + value = appDataController.data.neo4j.password, + onValueChange = { appDataController.data.neo4j.password = it }, visualTransformation = PasswordVisualTransformation() ) Row(verticalAlignment = Alignment.CenterVertically) { Button(onClick = { val db = Neo4jIO() + appDataController.saveData() try { - db.open(urlString, loginString, passwordString) + db.open( + appDataController.data.neo4j.url, + appDataController.data.neo4j.login, + appDataController.data.neo4j.password + ) } catch (ex: HandledIOException) { connectionStatus = ex.toString() iconColor = Color.Yellow From a4730d326c08643392d39df70e3bef394c58d35f Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 29 Apr 2023 22:53:30 +0300 Subject: [PATCH 229/296] feat: Add opening of last tree --- app/src/main/kotlin/org/tree/app/App.kt | 35 +++++++++++++++---- .../app/controller/io/AppDataController.kt | 33 +++++++++++++++++ .../tree/app/view/dialogs/io/Neo4jDialogs.kt | 2 ++ 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 798a0377..4fe24246 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -28,7 +28,13 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import newTree import org.tree.app.controller.io.AppDataController +import org.tree.app.controller.io.SavedTree +import org.tree.app.controller.io.SavedType import org.tree.app.view.* +import org.tree.app.view.dialogs.io.AlertDialog +import org.tree.app.view.dialogs.io.ExportRBDialog +import org.tree.app.view.dialogs.io.ImportRBDialog +import org.tree.app.view.dialogs.io.handleIOException import org.tree.app.view.dialogs.io.* import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP @@ -38,7 +44,6 @@ import org.tree.binaryTree.trees.AVLTree import org.tree.binaryTree.trees.BinSearchTree import org.tree.binaryTree.trees.RBTree - enum class DialogType { EMPTY, IMPORT_RB, @@ -54,17 +59,27 @@ fun main() = application { state = rememberWindowState(width = 800.dp, height = 600.dp), icon = icon ) { - var treeController by remember { - mutableStateOf>( - newTree(BinSearchTree()) - ) - } + var throwException by remember { mutableStateOf(false) } + var exceptionContent by remember { mutableStateOf("Nothing...") } + + AlertDialog(exceptionContent, throwException, { throwException = false }) + var widthOfPanel by remember { mutableStateOf(400) } var dialogType by remember { mutableStateOf(DialogType.EMPTY) } var logString by remember { mutableStateOf("Log string") } var logColor by remember { mutableStateOf(Color.DarkGray) } val treeOffsetX = remember { mutableStateOf(0) } val treeOffsetY = remember { mutableStateOf(0) } + var treeController by remember { + mutableStateOf( + handleIOException(onCatch = { ex -> + exceptionContent = ex.toString() + throwException = true + }) { + appDataController.loadLastTree() + } ?: newTree(BinSearchTree()) + ) + } fun convertKey(keyString: String): Int? { if (keyString.isEmpty()) { @@ -255,6 +270,7 @@ fun main() = application { ) } Spacer(modifier = Modifier.width(3.dp)) + TreeView(treeController, treeOffsetX, treeOffsetY) } @@ -265,13 +281,18 @@ fun main() = application { DialogType.IMPORT_RB -> { ImportRBDialog( - onCloseRequest = { dialogType = DialogType.EMPTY }, onSuccess = { treeController = it }) + onCloseRequest = { dialogType = DialogType.EMPTY }, + onSuccess = { treeController = it }) } DialogType.EXPORT_RB -> { @Suppress("UNCHECKED_CAST") ExportRBDialog( onCloseRequest = { dialogType = DialogType.EMPTY }, + onSuccess = { treeName -> + appDataController.data.lastTree = SavedTree(SavedType.Neo4j, treeName) + appDataController.saveData() + }, treeController as TreeController>> ) } diff --git a/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt b/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt index bbe85d4c..733976ca 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt @@ -1,5 +1,6 @@ package org.tree.app.controller.io +import TreeController import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -11,6 +12,9 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json +import newTree +import org.tree.app.appDataController +import org.tree.binaryTree.trees.BinSearchTree import java.io.File import java.io.FileNotFoundException import java.nio.file.Files @@ -97,6 +101,35 @@ class AppDataController { } } + fun loadLastTree(): TreeController<*> { + var treeController: TreeController<*> = newTree(BinSearchTree()) + data.lastTree?.let { + when (it.type) { + SavedType.SQLite -> { + val db = SQLiteIO() + treeController = db.importTree(File(it.path)) + } + + SavedType.Json -> { + val db = Json() + treeController = db.importTree(File(it.path)) + } + + SavedType.Neo4j -> { + val db = Neo4jIO() + db.open( + appDataController.data.neo4j.url, + appDataController.data.neo4j.login, + appDataController.data.neo4j.password + ) + treeController = db.importRBTree(it.path) + db.close() + } + } + } + return treeController + } + private fun getAppDataFile(): File { val workFile = File(getWorkingDirPath() + "appData.json") try { diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt index 6ed5f1f4..41b5c477 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -50,6 +50,7 @@ fun ImportRBDialog( @Composable fun ExportRBDialog( onCloseRequest: () -> Unit, + onSuccess: (treeName: String) -> Unit, treeController: TreeController>> ) { @@ -67,6 +68,7 @@ fun ExportRBDialog( }) { db.exportRBTree(treeController, treeName) } if (!throwException) { db.close() + onSuccess(treeName) closeRequest() } } From 681b99f09b2c31d39489692de3fe6fb8597aeb0b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 29 Apr 2023 23:51:15 +0300 Subject: [PATCH 230/296] fix: Rework alertDialog at main --- app/src/main/kotlin/org/tree/app/App.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 4fe24246..fb414ce9 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -62,8 +62,6 @@ fun main() = application { var throwException by remember { mutableStateOf(false) } var exceptionContent by remember { mutableStateOf("Nothing...") } - AlertDialog(exceptionContent, throwException, { throwException = false }) - var widthOfPanel by remember { mutableStateOf(400) } var dialogType by remember { mutableStateOf(DialogType.EMPTY) } var logString by remember { mutableStateOf("Log string") } @@ -73,7 +71,7 @@ fun main() = application { var treeController by remember { mutableStateOf( handleIOException(onCatch = { ex -> - exceptionContent = ex.toString() + exceptionContent = "Can't open last tree because: $ex" throwException = true }) { appDataController.loadLastTree() @@ -170,6 +168,8 @@ fun main() = application { } } } + AlertDialog(exceptionContent, throwException, { throwException = false }) + Row(modifier = Modifier.background(Color.LightGray)) { Column(modifier = Modifier.width(widthOfPanel.dp)) { From 0b2710ae9b452daf8aefac4e7f5eef942b9bfffa Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sat, 29 Apr 2023 23:52:22 +0300 Subject: [PATCH 231/296] feat: Add updating of lastTree on importRB --- app/src/main/kotlin/org/tree/app/App.kt | 6 +++++- .../kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index fb414ce9..fd10b022 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -282,7 +282,11 @@ fun main() = application { DialogType.IMPORT_RB -> { ImportRBDialog( onCloseRequest = { dialogType = DialogType.EMPTY }, - onSuccess = { treeController = it }) + onSuccess = { newTreeController, treeName -> + treeController = newTreeController + appDataController.data.lastTree = SavedTree(SavedType.Neo4j, treeName) + appDataController.saveData() + }) } DialogType.EXPORT_RB -> { diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt index 41b5c477..ee91318f 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -20,7 +20,7 @@ import org.tree.binaryTree.RBNode @Composable fun ImportRBDialog( onCloseRequest: () -> Unit, - onSuccess: (TreeController>>) -> Unit = {} + onSuccess: (newTreeController: TreeController>>, treeName: String) -> Unit ) { var throwException by remember { mutableStateOf(false) } var exceptionContent by remember { mutableStateOf("Nothing...") } @@ -37,7 +37,7 @@ fun ImportRBDialog( }) { db.importRBTree(treeName) } if (treeController != null) { db.close() - onSuccess(treeController) + onSuccess(treeController, treeName) closeRequest() } } From 5bbcf102c1a39f6ec805fd49c0a71743d87a762c Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 30 Apr 2023 12:06:05 +0300 Subject: [PATCH 232/296] fix(SQLiteIO): Add additional exception in `importTree()` --- .../main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index a082fb84..40368ed0 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -17,6 +17,7 @@ import org.tree.binaryTree.Node import org.tree.binaryTree.trees.BinSearchTree import java.io.File import java.nio.file.Files +import java.sql.SQLException object Nodes : IntIdTable() { val key = integer("key") @@ -50,13 +51,14 @@ class SQLiteIO { if (amountOfNodes > 0) { parseRootForImport(setOfNodes) } - } - else{ + } else { throw HandledIOException("Database without a node table") } } } catch (ex: ExposedSQLException) { throw HandledIOException("File is not a database", ex) + } catch (ex: SQLException) { + throw HandledIOException("File is not a database", ex) } return treeController } @@ -177,6 +179,7 @@ class SQLiteIO { node: Node>, x: Int, y: Int ) { - treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), treeController.getNodeCol(node)) + treeController.nodes[node] = + NodeExtension(mutableStateOf(x), mutableStateOf(y), treeController.getNodeCol(node)) } } From 1f480f3e34d08f5a89f123c08185db64c4770841 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 30 Apr 2023 12:09:11 +0300 Subject: [PATCH 233/296] refactor: Reformat files --- app/src/main/kotlin/org/tree/app/App.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index fd10b022..6965a669 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -31,10 +31,6 @@ import org.tree.app.controller.io.AppDataController import org.tree.app.controller.io.SavedTree import org.tree.app.controller.io.SavedType import org.tree.app.view.* -import org.tree.app.view.dialogs.io.AlertDialog -import org.tree.app.view.dialogs.io.ExportRBDialog -import org.tree.app.view.dialogs.io.ImportRBDialog -import org.tree.app.view.dialogs.io.handleIOException import org.tree.app.view.dialogs.io.* import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP @@ -123,8 +119,7 @@ fun main() = application { if (tc != null) { treeController = tc logString = "" - } - else{ + } else { logString = "Database has no Nodes table" logColor = Color.Red } @@ -204,7 +199,7 @@ fun main() = application { val key = convertKey(keyString) ?: return@FindRow val node = treeController.find(KVP(key)) if (node != null) { - logString = "Node with key = $key and value = \"${node.elem.v}\" found." + logString = "Node with key = $key and value = \"${node.elem.v}\" found." logColor = Color.Green val coordinates = treeController.nodes[node] if (coordinates != null) { From a3fdb6499b97c457df7510b09145e47ea5f325f7 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Sun, 30 Apr 2023 12:22:17 +0300 Subject: [PATCH 234/296] feat: Add updating of lastTree in BinSearch and AVL cases --- .../app/view/dialogs/io/FilesIOHandler.kt | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 9a00167a..67373232 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -2,9 +2,8 @@ package org.tree.app.view.dialogs.io import TreeController import androidx.compose.ui.awt.ComposeWindow -import org.tree.app.controller.io.HandledIOException -import org.tree.app.controller.io.Json -import org.tree.app.controller.io.SQLiteIO +import org.tree.app.appDataController +import org.tree.app.controller.io.* import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP import org.tree.binaryTree.Node @@ -17,7 +16,10 @@ fun importAVLT( val fileString = selectFile(window, "json", Mode.IMPORT) ?: return null val file = File(fileString) val db = Json() - return db.importTree(file) + val treeController = db.importTree(file) + appDataController.data.lastTree = SavedTree(SavedType.Json, file.path) + appDataController.saveData() + return treeController } fun exportAVLT(window: ComposeWindow, tc: TreeController>>) { @@ -25,6 +27,8 @@ fun exportAVLT(window: ComposeWindow, tc: TreeController>>) { @@ -45,6 +54,8 @@ fun exportBST(window: ComposeWindow, tc: TreeController>>) val file = File(fileString) val db = SQLiteIO() db.exportTree(tc, file) + appDataController.data.lastTree = SavedTree(SavedType.SQLite, file.path) + appDataController.saveData() } enum class Mode { From 1f7c40c76d06c08881034a134972ee9addff4969 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 19:06:44 +0300 Subject: [PATCH 235/296] fix(SQLiteIO): Fix catch of exceptions --- .../kotlin/org/tree/app/controller/io/SQLiteIO.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 40368ed0..07bcdaf2 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -43,8 +43,8 @@ class SQLiteIO { fun importTree(file: File): TreeController>> { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") treeController = TreeController(BinSearchTree()) - try { - transaction { + transaction { + try { if (Nodes.exists()) { val setOfNodes = InstanceOfNode.all().toMutableSet() val amountOfNodes = setOfNodes.count() @@ -52,17 +52,14 @@ class SQLiteIO { parseRootForImport(setOfNodes) } } else { - throw HandledIOException("Database without a node table") + throw HandledIOException("Database without a Nodes table") } + } catch (ex: SQLException) { + throw HandledIOException("File is not a SQLite database", ex) } - } catch (ex: ExposedSQLException) { - throw HandledIOException("File is not a database", ex) - } catch (ex: SQLException) { - throw HandledIOException("File is not a database", ex) } return treeController } - fun exportTree(treeController_: TreeController>>, file: File) { try { Files.createDirectories(file.toPath().parent) From 41a38cd06ea12c200e1575df0e3ade89c3279d72 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 19:07:47 +0300 Subject: [PATCH 236/296] feat(App, FilesIOHandler): Improve import methods: now it's using caught exceptions --- app/src/main/kotlin/org/tree/app/App.kt | 22 ++++++++++++------- .../app/view/dialogs/io/FilesIOHandler.kt | 18 ++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 6965a669..264cf0c6 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -67,7 +67,7 @@ fun main() = application { var treeController by remember { mutableStateOf( handleIOException(onCatch = { ex -> - exceptionContent = "Can't open last tree because: $ex" + exceptionContent = "Can't open the last tree because: $ex" throwException = true }) { appDataController.loadLastTree() @@ -115,18 +115,24 @@ fun main() = application { } Menu("Open", mnemonic = 'O') { Item("Bin Search Tree", onClick = { - val tc = importBST(ComposeWindow()) + val tc = handleIOException(onCatch = { ex -> + exceptionContent = "Failed to import tree from file: ${ex.message}" + throwException = true + }) { + importBST(ComposeWindow()) + } if (tc != null) { treeController = tc - logString = "" - } else { - logString = "Database has no Nodes table" - logColor = Color.Red } }) Item("Red black Tree", onClick = { dialogType = DialogType.IMPORT_RB }) Item("AVL Tree", onClick = { - val tc = importAVLT(ComposeWindow()) + val tc = handleIOException(onCatch = { ex -> + exceptionContent = "Failed to import tree from file: ${ex.message}" + throwException = true + }) { + importAVLT(ComposeWindow()) + } if (tc != null) { treeController = tc } @@ -163,7 +169,7 @@ fun main() = application { } } } - AlertDialog(exceptionContent, throwException, { throwException = false }) + AlertDialog(exceptionContent, throwException) { throwException = false } Row(modifier = Modifier.background(Color.LightGray)) { diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 67373232..e9a7e570 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -3,7 +3,10 @@ package org.tree.app.view.dialogs.io import TreeController import androidx.compose.ui.awt.ComposeWindow import org.tree.app.appDataController -import org.tree.app.controller.io.* +import org.tree.app.controller.io.Json +import org.tree.app.controller.io.SQLiteIO +import org.tree.app.controller.io.SavedTree +import org.tree.app.controller.io.SavedType import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP import org.tree.binaryTree.Node @@ -37,15 +40,9 @@ fun importBST( val fileString = selectFile(window, "sqlite", Mode.IMPORT) ?: return null val file = File(fileString) val db = SQLiteIO() - val treeController = try { - db.importTree(file) - } catch (ex: HandledIOException) { - null - } - if (treeController != null) { - appDataController.data.lastTree = SavedTree(SavedType.SQLite, file.path) - appDataController.saveData() - } + val treeController = db.importTree(file) + appDataController.data.lastTree = SavedTree(SavedType.SQLite, file.path) + appDataController.saveData() return treeController } @@ -78,4 +75,3 @@ fun selectFile(window: ComposeWindow, fileFormant: String, mode: Mode): String? } return null } - From 954218cb5cf70de2bdf88590d7810d26002a7b19 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 19:09:50 +0300 Subject: [PATCH 237/296] fix(App): Fix xyevoe name of variable --- app/src/main/kotlin/org/tree/app/App.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 264cf0c6..3056c411 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -190,9 +190,9 @@ fun main() = application { Spacer(modifier = Modifier.size(5.dp)) RemoveRow(onClick = { keyString -> val key = convertKey(keyString) ?: return@RemoveRow - val rememd = treeController + val treeBeforeInsert = treeController treeController = treeController.remove(KVP(key)) - if (rememd == treeController) { + if (treeBeforeInsert == treeController) { logString = "There is no node with key = $key in the tree. Nothing has been done." logColor = Color.Yellow } else { From 50a4980c9de3ace7935dac9583bcb2f5b2e9b063 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 19:41:44 +0300 Subject: [PATCH 238/296] feat(App): Remove signs of ignorance, replace place of init of `ComposeWindow` object --- app/src/main/kotlin/org/tree/app/App.kt | 7 ++---- .../app/view/dialogs/io/FilesIOHandler.kt | 22 +++++++++---------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 3056c411..e3515ced 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.awt.ComposeWindow import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @@ -119,7 +118,7 @@ fun main() = application { exceptionContent = "Failed to import tree from file: ${ex.message}" throwException = true }) { - importBST(ComposeWindow()) + importBST() } if (tc != null) { treeController = tc @@ -131,7 +130,7 @@ fun main() = application { exceptionContent = "Failed to import tree from file: ${ex.message}" throwException = true }) { - importAVLT(ComposeWindow()) + importAVLT() } if (tc != null) { treeController = tc @@ -144,7 +143,6 @@ fun main() = application { onClick = { @Suppress("UNCHECKED_CAST") exportBST( - ComposeWindow(), treeController as TreeController>> ) }, @@ -160,7 +158,6 @@ fun main() = application { onClick = { @Suppress("UNCHECKED_CAST") exportAVLT( - ComposeWindow(), treeController as TreeController>> ) }, diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index e9a7e570..1b8c1c99 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -14,9 +14,8 @@ import java.awt.FileDialog import java.io.File fun importAVLT( - window: ComposeWindow, ): TreeController>>? { - val fileString = selectFile(window, "json", Mode.IMPORT) ?: return null + val fileString = selectFile("json", Mode.IMPORT) ?: return null val file = File(fileString) val db = Json() val treeController = db.importTree(file) @@ -25,8 +24,8 @@ fun importAVLT( return treeController } -fun exportAVLT(window: ComposeWindow, tc: TreeController>>) { - val fileString = selectFile(window, "json", Mode.EXPORT) ?: return +fun exportAVLT(tc: TreeController>>) { + val fileString = selectFile("json", Mode.EXPORT) ?: return val file = File(fileString) val db = Json() db.exportTree(tc, file) @@ -35,9 +34,8 @@ fun exportAVLT(window: ComposeWindow, tc: TreeController>>? { - val fileString = selectFile(window, "sqlite", Mode.IMPORT) ?: return null + val fileString = selectFile("sqlite", Mode.IMPORT) ?: return null val file = File(fileString) val db = SQLiteIO() val treeController = db.importTree(file) @@ -46,8 +44,8 @@ fun importBST( return treeController } -fun exportBST(window: ComposeWindow, tc: TreeController>>) { - val fileString = selectFile(window, "sqlite", Mode.EXPORT) ?: return +fun exportBST(tc: TreeController>>) { + val fileString = selectFile("sqlite", Mode.EXPORT) ?: return val file = File(fileString) val db = SQLiteIO() db.exportTree(tc, file) @@ -60,14 +58,14 @@ enum class Mode { EXPORT } -fun selectFile(window: ComposeWindow, fileFormant: String, mode: Mode): String? { +fun selectFile(fileExtension: String, mode: Mode): String? { val fd = if (mode == Mode.IMPORT) { - FileDialog(window, "Choose .sqlite file to import", FileDialog.LOAD) + FileDialog(ComposeWindow(), "Choose .sqlite file to import", FileDialog.LOAD) } else { - FileDialog(window, "Choose .sqlite file to export", FileDialog.SAVE) + FileDialog(ComposeWindow(), "Choose .sqlite file to export", FileDialog.SAVE) } fd.directory = "C:\\" - fd.file = "*.$fileFormant" + fd.file = "*.$fileExtension" fd.isVisible = true val fileString = fd.directory + fd.file if (fileString != "nullnull") { From 4bd77a8940ef46cf14a616ebc3d94a48fe7aa8bb Mon Sep 17 00:00:00 2001 From: David Date: Sun, 30 Apr 2023 19:57:29 +0300 Subject: [PATCH 239/296] fix(FilesIOHandler): Fix incorrect conflict resolution --- .../kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 1b8c1c99..6bb88919 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -13,8 +13,7 @@ import org.tree.binaryTree.Node import java.awt.FileDialog import java.io.File -fun importAVLT( -): TreeController>>? { +fun importAVLT(): TreeController>>? { val fileString = selectFile("json", Mode.IMPORT) ?: return null val file = File(fileString) val db = Json() @@ -33,8 +32,7 @@ fun exportAVLT(tc: TreeController>>) { appDataController.saveData() } -fun importBST( -): TreeController>>? { +fun importBST(): TreeController>>? { val fileString = selectFile("sqlite", Mode.IMPORT) ?: return null val file = File(fileString) val db = SQLiteIO() From 6c6cbe63d420851759fd9a4ad2207c29a3b0b5b8 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 03:03:13 +0300 Subject: [PATCH 240/296] feat(SQLiteIO): Remove treeController from class property and add it to arg of functions --- .../org/tree/app/controller/io/SQLiteIO.kt | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 07bcdaf2..067305c3 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -39,17 +39,16 @@ class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with class SQLiteIO { private var amountOfNodesToHandle = 0 - private lateinit var treeController: TreeController>> fun importTree(file: File): TreeController>> { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") - treeController = TreeController(BinSearchTree()) + val treeController = TreeController(BinSearchTree()) transaction { try { if (Nodes.exists()) { val setOfNodes = InstanceOfNode.all().toMutableSet() val amountOfNodes = setOfNodes.count() if (amountOfNodes > 0) { - parseRootForImport(setOfNodes) + parseRootForImport(setOfNodes, treeController) } } else { throw HandledIOException("Database without a Nodes table") @@ -60,14 +59,14 @@ class SQLiteIO { } return treeController } - fun exportTree(treeController_: TreeController>>, file: File) { + + fun exportTree(treeController: TreeController>>, file: File) { try { Files.createDirectories(file.toPath().parent) } catch (ex: SecurityException) { throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") - treeController = treeController_ transaction { try { SchemaUtils.drop(Nodes) @@ -77,14 +76,15 @@ class SQLiteIO { } val root = treeController.tree.root if (root != null) { - parseNodesForExport(root, null) + parseNodesForExport(root, null, treeController) } } } private fun parseRootForImport( - setOfNodes: MutableSet + setOfNodes: MutableSet, + treeController: TreeController>> ) { try { val node = setOfNodes.elementAt(0) @@ -98,8 +98,8 @@ class SQLiteIO { bst.insert(KVP(parsedKey, parsedValue)) val root = bst.root if (root != null) { - addCoordinatesToNode(root, parsedX, parsedY) - parseNodesForImport(setOfNodes, root) + addCoordinatesToNode(root, parsedX, parsedY, treeController) + parseNodesForImport(setOfNodes, root, treeController) if (setOfNodes.isNotEmpty()) { throw HandledIOException("Incorrect binary tree: there are at least two left/right children of some node") } @@ -114,7 +114,8 @@ class SQLiteIO { private fun parseNodesForImport( setOfNodes: MutableSet, - curNode: Node> + curNode: Node>, + treeController: TreeController>> ) { if (amountOfNodesToHandle <= 0) { return @@ -129,7 +130,7 @@ class SQLiteIO { if (parsedKey == parsedParentKey) throw HandledIOException("Child with key = ${curNode.elem.key} is parent for himself") if (parsedParentKey == curNode.elem.key) { val newNode = Node(KVP(parsedKey, parsedValue)) - addCoordinatesToNode(newNode, parsedX, parsedY) + addCoordinatesToNode(newNode, parsedX, parsedY, treeController) setOfNodes.remove(nodes) amountOfNodesToHandle-- if (parsedKey < parsedParentKey) { @@ -137,15 +138,15 @@ class SQLiteIO { curNode.left = newNode val leftChild = curNode.left if (leftChild != null) { - parseNodesForImport(setOfNodes, leftChild) + parseNodesForImport(setOfNodes, leftChild, treeController) } - parseNodesForImport(setOfNodes, curNode) + parseNodesForImport(setOfNodes, curNode, treeController) } else { // When parsedKey is greater than parsedParentKey if (curNode.right != null) throw HandledIOException("Incorrect binary tree: there are at least two right children of node with key = ${curNode.elem.key}") curNode.right = newNode val rightChild = curNode.right if (rightChild != null) { - parseNodesForImport(setOfNodes, rightChild) + parseNodesForImport(setOfNodes, rightChild, treeController) } } } @@ -153,7 +154,8 @@ class SQLiteIO { private fun parseNodesForExport( curNode: Node>, - parNode: Node>? + parNode: Node>?, + treeController: TreeController>> ) { InstanceOfNode.new { key = curNode.elem.key @@ -164,17 +166,17 @@ class SQLiteIO { } val leftChild = curNode.left if (leftChild != null) { - parseNodesForExport(leftChild, curNode) + parseNodesForExport(leftChild, curNode, treeController) } val rightChild = curNode.right if (rightChild != null) { - parseNodesForExport(rightChild, curNode) + parseNodesForExport(rightChild, curNode, treeController) } } private fun addCoordinatesToNode( node: Node>, - x: Int, y: Int + x: Int, y: Int, treeController: TreeController>> ) { treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), treeController.getNodeCol(node)) From ed77a95394e8e4668b363e05f7565ffc7ce356f3 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 29 Apr 2023 14:12:45 +0300 Subject: [PATCH 241/296] refactor(TreeController): Change the root coordinates to (0,0) --- .../main/kotlin/org/tree/app/controller/TreeController.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 569679ea..de31624b 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -18,8 +18,9 @@ class TreeController, NODE_T>>( init { tree.root?.let { - drawLeft(it, 0, 0, height(it) - 2) - drawRight(it, 0, 0, height(it) - 2) + nodes[it] = NodeExtension(mutableStateOf(0), mutableStateOf(0), getNodeCol(it)) + drawLeft(it.left, 0, 0, height(it) - 2) + drawRight(it.right, 0, 0, height(it) - 2) } } From a825e73034bc765bfcfaea256dbfe6c3488ac6c5 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 29 Apr 2023 14:19:17 +0300 Subject: [PATCH 242/296] feat(TreeController): Add width calculation method --- .../org/tree/app/controller/TreeController.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index de31624b..f62c80d3 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -17,15 +17,26 @@ class TreeController, NODE_T>>( val nodes = mutableMapOf() init { + val nodesWithWidths = mutableMapOf>() tree.root?.let { nodes[it] = NodeExtension(mutableStateOf(0), mutableStateOf(0), getNodeCol(it)) + countWidth(it, nodesWithWidths) drawLeft(it.left, 0, 0, height(it) - 2) drawRight(it.right, 0, 0, height(it) - 2) } } - private fun childrenCount(node: NODE_T?): Int { - return (2 - (node?.countNullChildren() ?: 2)) + private fun countWidth(node: NODE_T?, map: MutableMap>): Pair{ + var leftWidth = 0 + var rightWidth = 0 + if (node?.left != null){ + leftWidth = countWidth(node.left, map).first + countWidth(node.left, map).second + 1 + } + if (node?.right != null){ + rightWidth = countWidth(node.right, map).first + countWidth(node.right, map).second + 1 + } + if (node != null) map[node] = Pair(leftWidth, rightWidth) + return Pair(leftWidth, rightWidth) } private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { From f0ae3737d3ee4b2c0ffddfd1cfffb83b2a72c702 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 29 Apr 2023 14:32:10 +0300 Subject: [PATCH 243/296] refactor(TreeController): Use widths in drawLeft() and drawRight() --- .../org/tree/app/controller/TreeController.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index f62c80d3..4a0771b5 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -21,8 +21,8 @@ class TreeController, NODE_T>>( tree.root?.let { nodes[it] = NodeExtension(mutableStateOf(0), mutableStateOf(0), getNodeCol(it)) countWidth(it, nodesWithWidths) - drawLeft(it.left, 0, 0, height(it) - 2) - drawRight(it.right, 0, 0, height(it) - 2) + drawLeft(it.left, 0, 0, nodesWithWidths) + drawRight(it.right, 0, 0, nodesWithWidths) } } @@ -39,10 +39,10 @@ class TreeController, NODE_T>>( return Pair(leftWidth, rightWidth) } - private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { + private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, mapOfWidths: MutableMap>) { var count = 0 if (node?.right != null) { - count = 1 + (height) * childrenCount(node.right) + count = mapOfWidths[node]?.second ?: 0 } val x = parentX - nodeSize - (count * nodeSize) val y = parentY + nodeSize @@ -52,14 +52,14 @@ class TreeController, NODE_T>>( val col = getNodeCol(node) nodes[node] = NodeExtension(stateX, stateY, col) } - if (node?.left != null) drawLeft(node.left, x, y, height - 1) - if (node?.right != null) drawRight(node.right, x, y, height - 1) + if (node?.left != null) drawLeft(node.left, x, y, mapOfWidths) + if (node?.right != null) drawRight(node.right, x, y, mapOfWidths) } - private fun drawRight(node: NODE_T?, parentX: Int, parentY: Int, height: Int) { + private fun drawRight(node: NODE_T?, parentX: Int, parentY: Int, mapOfWidths: MutableMap>) { var count = 0 if (node?.left != null) { - count = 1 + (height) * childrenCount(node.left) + count = mapOfWidths[node]?.first ?: 0 } val x = parentX + nodeSize + (count * nodeSize) val y = parentY + nodeSize @@ -69,8 +69,8 @@ class TreeController, NODE_T>>( val col = getNodeCol(node) nodes[node] = NodeExtension(stateX, stateY, col) } - if (node?.left != null) drawLeft(node.left, x, y, height - 1) - if (node?.right != null) drawRight(node.right, x, y, height - 1) + if (node?.left != null) drawLeft(node.left, x, y, mapOfWidths) + if (node?.right != null) drawRight(node.right, x, y, mapOfWidths) } fun insert(obj: KVP): TreeController { From 0acfc64e5864bea60b19cce87a843076e436f03b Mon Sep 17 00:00:00 2001 From: Anna-er Date: Sat, 29 Apr 2023 14:51:23 +0300 Subject: [PATCH 244/296] refactor(TreeController): Refactor countWidth() --- .../kotlin/org/tree/app/controller/TreeController.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 4a0771b5..2163fc40 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -26,17 +26,17 @@ class TreeController, NODE_T>>( } } - private fun countWidth(node: NODE_T?, map: MutableMap>): Pair{ + private fun countWidth(node: NODE_T?, map: MutableMap>): Int { var leftWidth = 0 var rightWidth = 0 - if (node?.left != null){ - leftWidth = countWidth(node.left, map).first + countWidth(node.left, map).second + 1 + if (node?.left != null) { + leftWidth = countWidth(node.left, map) + 1 } - if (node?.right != null){ - rightWidth = countWidth(node.right, map).first + countWidth(node.right, map).second + 1 + if (node?.right != null) { + rightWidth = countWidth(node.right, map) + 1 } if (node != null) map[node] = Pair(leftWidth, rightWidth) - return Pair(leftWidth, rightWidth) + return (leftWidth + rightWidth) } private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, mapOfWidths: MutableMap>) { From e6d6519d0ad5ec777013a879748faffab3302226 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 02:33:38 +0300 Subject: [PATCH 245/296] refactor(TreeController): Combine methods drawLeft() and drawRigh() into getCoordinatesOfNode() --- .../org/tree/app/controller/TreeController.kt | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 2163fc40..a0c61d0d 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -19,10 +19,8 @@ class TreeController, NODE_T>>( init { val nodesWithWidths = mutableMapOf>() tree.root?.let { - nodes[it] = NodeExtension(mutableStateOf(0), mutableStateOf(0), getNodeCol(it)) countWidth(it, nodesWithWidths) - drawLeft(it.left, 0, 0, nodesWithWidths) - drawRight(it.right, 0, 0, nodesWithWidths) + getCoordinatesOfNode(it, 0, 0, nodesWithWidths) } } @@ -39,38 +37,21 @@ class TreeController, NODE_T>>( return (leftWidth + rightWidth) } - private fun drawLeft(node: NODE_T?, parentX: Int, parentY: Int, mapOfWidths: MutableMap>) { - var count = 0 - if (node?.right != null) { - count = mapOfWidths[node]?.second ?: 0 - } - val x = parentX - nodeSize - (count * nodeSize) - val y = parentY + nodeSize + private fun getCoordinatesOfNode(node: NODE_T, x: Int, y: Int, mapOfWidths: MutableMap>) { val stateX = mutableStateOf(x) val stateY = mutableStateOf(y) - if (node != null) { - val col = getNodeCol(node) - nodes[node] = NodeExtension(stateX, stateY, col) - } - if (node?.left != null) drawLeft(node.left, x, y, mapOfWidths) - if (node?.right != null) drawRight(node.right, x, y, mapOfWidths) - } + val col = getNodeCol(node) + nodes[node] = NodeExtension(stateX, stateY, col) - private fun drawRight(node: NODE_T?, parentX: Int, parentY: Int, mapOfWidths: MutableMap>) { - var count = 0 - if (node?.left != null) { - count = mapOfWidths[node]?.first ?: 0 + node.left?.let { + val count = mapOfWidths[it]?.second ?: 0 + getCoordinatesOfNode(it, x - nodeSize - (count * nodeSize), y + nodeSize, mapOfWidths) } - val x = parentX + nodeSize + (count * nodeSize) - val y = parentY + nodeSize - val stateX = mutableStateOf(x) - val stateY = mutableStateOf(y) - if (node != null) { - val col = getNodeCol(node) - nodes[node] = NodeExtension(stateX, stateY, col) + + node.right?.let { + val count = mapOfWidths[it]?.first ?: 0 + getCoordinatesOfNode(it, x + nodeSize + (count * nodeSize), y + nodeSize, mapOfWidths) } - if (node?.left != null) drawLeft(node.left, x, y, mapOfWidths) - if (node?.right != null) drawRight(node.right, x, y, mapOfWidths) } fun insert(obj: KVP): TreeController { From b321be06cbd32617aa7a26045d1cf7fb87c05047 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 03:52:44 +0300 Subject: [PATCH 246/296] refactor(Json): Rename class --- .../kotlin/org/tree/app/controller/io/{Json.kt => JsonIO.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename app/src/main/kotlin/org/tree/app/controller/io/{Json.kt => JsonIO.kt} (99%) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt similarity index 99% rename from app/src/main/kotlin/org/tree/app/controller/io/Json.kt rename to app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt index 2b491ec5..8237b3c2 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Json.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt @@ -29,7 +29,7 @@ private data class JsonAVLNode( private data class JsonAVLTree( val root: JsonAVLNode? ) -class Json { +class JsonIO { private lateinit var treeController : TreeController>> private fun AVLNode>.serialize(): JsonAVLNode{ From eef24360f7e1e421721272905587c45052afab91 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 04:14:02 +0300 Subject: [PATCH 247/296] perf(JsonIO): Add TreeController as an argument to the import and export function --- .../app/controller/io/AppDataController.kt | 2 +- .../org/tree/app/controller/io/JsonIO.kt | 32 +++++++------------ .../app/view/dialogs/io/FilesIOHandler.kt | 6 ++-- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt b/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt index 733976ca..9994ef64 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/AppDataController.kt @@ -111,7 +111,7 @@ class AppDataController { } SavedType.Json -> { - val db = Json() + val db = JsonIO() treeController = db.importTree(File(it.path)) } diff --git a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt index 8237b3c2..071bb56f 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt @@ -29,46 +29,38 @@ private data class JsonAVLNode( private data class JsonAVLTree( val root: JsonAVLNode? ) + class JsonIO { - private lateinit var treeController : TreeController>> - private fun AVLNode>.serialize(): JsonAVLNode{ + private fun AVLNode>.serialize(treeController: TreeController>>): JsonAVLNode { return JsonAVLNode( key = this.elem.key, value = this.elem.v, x = treeController.nodes[this]?.x?.value ?: 0, y = treeController.nodes[this]?.y?.value ?: 0, height = this.height, - left = this.left?.serialize(), - right = this.right?.serialize() + left = this.left?.serialize(treeController), + right = this.right?.serialize(treeController) ) } - private fun JsonAVLNode.deserialize(): AVLNode> { + private fun JsonAVLNode.deserialize(treeController: TreeController>>): AVLNode> { val nv = AVLNode(KVP(key, value)) nv.height = height - addCoordinatesToNode(nv, x, y) - nv.left = left?.deserialize() - nv.right = right?.deserialize() + treeController.nodes[nv] = NodeExtension(mutableStateOf(x), mutableStateOf(y)) + nv.left = left?.deserialize(treeController) + nv.right = right?.deserialize(treeController) return nv } - private fun addCoordinatesToNode( - node: AVLNode>, - x: Int, y: Int - ) { - treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y)) - } - - fun exportTree(treeController_: TreeController>>, file: File) { + fun exportTree(treeController: TreeController>>, file: File) { try { Files.createDirectories(file.toPath().parent) } catch (ex: SecurityException) { throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) } - treeController = treeController_ - val jsonTree = JsonAVLTree(treeController.tree.root?.serialize()) + val jsonTree = JsonAVLTree(treeController.tree.root?.serialize(treeController)) file.run { createNewFile() @@ -83,9 +75,9 @@ class JsonIO { throw HandledIOException("File ${file.toPath().fileName} not found: no access", ex) } - treeController = TreeController(AVLTree()) + val treeController = TreeController(AVLTree()) val jsonTree = Json.decodeFromString(json) - treeController.tree.root = jsonTree.root?.deserialize() + treeController.tree.root = jsonTree.root?.deserialize(treeController) return treeController } diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 6bb88919..69a234e1 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -3,7 +3,7 @@ package org.tree.app.view.dialogs.io import TreeController import androidx.compose.ui.awt.ComposeWindow import org.tree.app.appDataController -import org.tree.app.controller.io.Json +import org.tree.app.controller.io.JsonIO import org.tree.app.controller.io.SQLiteIO import org.tree.app.controller.io.SavedTree import org.tree.app.controller.io.SavedType @@ -16,7 +16,7 @@ import java.io.File fun importAVLT(): TreeController>>? { val fileString = selectFile("json", Mode.IMPORT) ?: return null val file = File(fileString) - val db = Json() + val db = JsonIO() val treeController = db.importTree(file) appDataController.data.lastTree = SavedTree(SavedType.Json, file.path) appDataController.saveData() @@ -26,7 +26,7 @@ fun importAVLT(): TreeController>>? { fun exportAVLT(tc: TreeController>>) { val fileString = selectFile("json", Mode.EXPORT) ?: return val file = File(fileString) - val db = Json() + val db = JsonIO() db.exportTree(tc, file) appDataController.data.lastTree = SavedTree(SavedType.Json, file.path) appDataController.saveData() From e138ceb5b1c65adc616291f9791c9e2fab11321f Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 04:16:56 +0300 Subject: [PATCH 248/296] fix(JsonIO): Add color to imported nodes --- app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt index 071bb56f..d8f27cad 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt @@ -47,7 +47,7 @@ class JsonIO { private fun JsonAVLNode.deserialize(treeController: TreeController>>): AVLNode> { val nv = AVLNode(KVP(key, value)) nv.height = height - treeController.nodes[nv] = NodeExtension(mutableStateOf(x), mutableStateOf(y)) + treeController.nodes[nv] = NodeExtension(mutableStateOf(x), mutableStateOf(y), treeController.getNodeCol(nv)) nv.left = left?.deserialize(treeController) nv.right = right?.deserialize(treeController) return nv From 1575dace0a87343e42649010af71aab27e197565 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 05:01:33 +0300 Subject: [PATCH 249/296] refactor(JsonIO): Edit JsonIO specification and remove delete method --- README.md | 3 +-- app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 04dbae59..303d3e7d 100644 --- a/README.md +++ b/README.md @@ -78,11 +78,10 @@ At plain text database we store AVLTree. We use json files. There are 2 types of - `left` *AVLNode* - node left child - `right` *AVLNode* - node right child -We have functions: +We have methods: - `exportTree(TreeController, file.json)` - writes information stored in TreeController object to a file `file.json`. - `importTree(file.json)` - reads the tree stored in the file `file.json` and returns TreeController object. -- `cleanDataBase(file.json)` - deletes a file `file.json`. ### SQLite diff --git a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt index d8f27cad..609b7a10 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt @@ -81,8 +81,4 @@ class JsonIO { return treeController } - - fun cleanDataBase(file: File) { - file.delete() - } } From b66818fa816f976e3a4b0851d35d692f77cf1c62 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 15:09:55 +0300 Subject: [PATCH 250/296] feat(Contributing): Add perf --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac92f42f..88490323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,7 @@ * `feat` для добавления новой функциональности * `fix` для исправления бага в программе * `refactor` для рефакторинга кода, например, переименования переменной +* `perf` для изменения кода, повышающего производительность * `test` для добавления тестов, их рефакторинга * `struct` для изменений связанных с изменением структуры проекта (НО НЕ КОДА), например изменение расположения папок From 285afe7bbc2185fc8148cbbf3bbeb6732d14a304 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 13:59:45 +0300 Subject: [PATCH 251/296] refactor(AVLTree): Rename auxiliary methods --- .../org/tree/binaryTree/trees/AVLTree.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt index 670b8ccc..1f572fe1 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt @@ -5,25 +5,25 @@ import org.tree.binaryTree.templates.TemplateBalanceBSTree import kotlin.math.max class AVLTree> : TemplateBalanceBSTree>() { - private fun height(avlNode: AVLNode?): Int { + private fun heightOrZero(avlNode: AVLNode?): Int { return avlNode?.height ?: 0 } - private fun bfactor(avlNode: AVLNode?): Int { - fixheight(avlNode) - fixheight(avlNode?.left) - fixheight(avlNode?.right) + private fun balanceFactor(avlNode: AVLNode?): Int { + fixHeight(avlNode) + fixHeight(avlNode?.left) + fixHeight(avlNode?.right) return if (avlNode != null) { - height(avlNode.right) - height(avlNode.left) + heightOrZero(avlNode.right) - heightOrZero(avlNode.left) } else { 0 } } - private fun fixheight(avlNode: AVLNode?) { + private fun fixHeight(avlNode: AVLNode?) { if (avlNode != null) { - val hl = height(avlNode.left) - val hr = height(avlNode.right) + val hl = heightOrZero(avlNode.left) + val hr = heightOrZero(avlNode.right) avlNode.height = max(hl, hr) + 1 } } @@ -31,15 +31,15 @@ class AVLTree> : TemplateBalanceBSTree>() { override fun rotateRight(curNode: AVLNode, parentNode: AVLNode?) { val replacementNode = curNode.left super.rotateRight(curNode, parentNode) - fixheight(curNode) - fixheight(replacementNode) + fixHeight(curNode) + fixHeight(replacementNode) } override fun rotateLeft(curNode: AVLNode, parentNode: AVLNode?) { val replacementNode = curNode.right super.rotateLeft(curNode, parentNode) - fixheight(replacementNode) - fixheight(curNode) + fixHeight(replacementNode) + fixHeight(curNode) } override fun insert(curNode: AVLNode?, obj: T): AVLNode? { @@ -47,16 +47,16 @@ class AVLTree> : TemplateBalanceBSTree>() { } private fun balanceNode(curNode: AVLNode, parentNode: AVLNode?) { - if (bfactor(curNode) == 2) { - if (bfactor(curNode.right) < 0) { + if (balanceFactor(curNode) == 2) { + if (balanceFactor(curNode.right) < 0) { curNode.right?.let { rotateRight(it, curNode) } } rotateLeft(curNode, parentNode) return } - if (bfactor(curNode) == -2) { - if (bfactor(curNode.left) > 0) { + if (balanceFactor(curNode) == -2) { + if (balanceFactor(curNode.left) > 0) { curNode.left?.let { rotateLeft(it, curNode) } } rotateRight(curNode, parentNode) From dece591de6cbab479187ff9afec8603f33016f09 Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 14:47:49 +0300 Subject: [PATCH 252/296] refactor(AVLTree): Refactor balance() and related methods --- .../org/tree/binaryTree/trees/AVLTree.kt | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt index 1f572fe1..5c65b56f 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/AVLTree.kt @@ -9,16 +9,13 @@ class AVLTree> : TemplateBalanceBSTree>() { return avlNode?.height ?: 0 } - private fun balanceFactor(avlNode: AVLNode?): Int { - fixHeight(avlNode) - fixHeight(avlNode?.left) - fixHeight(avlNode?.right) - return if (avlNode != null) { - heightOrZero(avlNode.right) - heightOrZero(avlNode.left) - } else { - 0 + private fun balanceFactor(avlNode: AVLNode): Int = //avl balance factor - difference in the heights of the right and left subtrees + avlNode.run { + fixHeight(this) + fixHeight(left) + fixHeight(right) + heightOrZero(right) - heightOrZero(left) } - } private fun fixHeight(avlNode: AVLNode?) { if (avlNode != null) { @@ -48,19 +45,22 @@ class AVLTree> : TemplateBalanceBSTree>() { private fun balanceNode(curNode: AVLNode, parentNode: AVLNode?) { if (balanceFactor(curNode) == 2) { - if (balanceFactor(curNode.right) < 0) { - curNode.right?.let { rotateRight(it, curNode) } + curNode.right?.let { + if (balanceFactor(it) < 0) { + rotateRight(it, curNode) + } } rotateLeft(curNode, parentNode) return } if (balanceFactor(curNode) == -2) { - if (balanceFactor(curNode.left) > 0) { - curNode.left?.let { rotateLeft(it, curNode) } + curNode.left?.let { + if (balanceFactor(it) > 0) { + rotateLeft(it, curNode) + } } rotateRight(curNode, parentNode) - return } } @@ -70,17 +70,12 @@ class AVLTree> : TemplateBalanceBSTree>() { operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive ) { - when (operationType) { - BalanceCase.OpType.REMOVE_0 -> {} - else -> { - if (curNode == null) { - root?.let { balanceNode(it, curNode) } - return - } - - curNode.right?.let { balanceNode(it, curNode) } - curNode.left?.let { balanceNode(it, curNode) } - } + if (operationType == BalanceCase.OpType.REMOVE_0) return + if (curNode == null) { + root?.let { balanceNode(it, curNode) } + return } + curNode.right?.let { balanceNode(it, curNode) } + curNode.left?.let { balanceNode(it, curNode) } } } From ad953adc92c8c91bcfc3d57a790b0a6fc41b28ac Mon Sep 17 00:00:00 2001 From: Anna-er Date: Tue, 2 May 2023 16:48:51 +0300 Subject: [PATCH 253/296] refactor(AVLTreeTest): Remove extra spaces and replace the operator with --- .../src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt index c9bdf4e0..320bce74 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/AVLTreeTest.kt @@ -28,8 +28,6 @@ class AVLTreeTest { MatcherAssert.assertThat(act, Matchers.equalTo(exp)) checkAVLTree(tree, values.toTypedArray()) } - - } companion object { @@ -49,7 +47,6 @@ class AVLTreeTest { return Arguments.of(seed, insertCount) } } - } @@ -68,7 +65,7 @@ class AVLTreeTest { } for (i in 1..removeCount * 2) { - val curVal = if ((i % 2 != 0) and (values.isNotEmpty())) { + val curVal = if (i % 2 != 0 && values.isNotEmpty()) { values.random(randomizer) } else { randomizer.nextInt() @@ -76,7 +73,6 @@ class AVLTreeTest { val exp = values.remove(curVal) val act = tree.remove(curVal) - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) checkAVLTree(tree, values.toTypedArray()) } @@ -102,7 +98,5 @@ class AVLTreeTest { return Arguments.of(seed, treeSize, removeCount) } } - } - } From 21d1b2e4c6cf3f5c017295f964f70c0eb3b12016 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 1 May 2023 09:58:22 +0300 Subject: [PATCH 254/296] struct: Divide ExceptionIOHandler.kt into several files --- app/src/main/kotlin/org/tree/app/App.kt | 2 ++ .../org/tree/app/controller/io/HandledIOException.kt | 8 ++++++++ .../{io/ExceptionIOHandler.kt => AlertDialog.kt} | 11 +---------- .../org/tree/app/view/dialogs/io/Neo4jDialogs.kt | 2 ++ 4 files changed, 13 insertions(+), 10 deletions(-) rename app/src/main/kotlin/org/tree/app/view/dialogs/{io/ExceptionIOHandler.kt => AlertDialog.kt} (81%) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index e3515ced..f8c1268b 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -29,7 +29,9 @@ import newTree import org.tree.app.controller.io.AppDataController import org.tree.app.controller.io.SavedTree import org.tree.app.controller.io.SavedType +import org.tree.app.controller.io.handleIOException import org.tree.app.view.* +import org.tree.app.view.dialogs.AlertDialog import org.tree.app.view.dialogs.io.* import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP diff --git a/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt b/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt index a1a9be86..13de3762 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/HandledIOException.kt @@ -2,6 +2,14 @@ package org.tree.app.controller.io import java.io.IOException +fun handleIOException(onCatch: (HandledIOException) -> Unit, handledCode: () -> T): T? { + try { + return handledCode() + } catch (ex: HandledIOException) { + onCatch(ex) + return null + } +} class HandledIOException : IOException { diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/AlertDialog.kt similarity index 81% rename from app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt rename to app/src/main/kotlin/org/tree/app/view/dialogs/AlertDialog.kt index 9eb64065..f9e00ddd 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/ExceptionIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/AlertDialog.kt @@ -1,4 +1,4 @@ -package org.tree.app.view.dialogs.io +package org.tree.app.view.dialogs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -11,16 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.rememberDialogState -import org.tree.app.controller.io.HandledIOException -fun handleIOException(onCatch: (HandledIOException) -> Unit, handledCode: () -> T): T? { - try { - return handledCode() - } catch (ex: HandledIOException) { - onCatch(ex) - return null - } -} @Composable fun AlertDialog(description: String, isOpen: Boolean, onCloseRequest: () -> Unit) { diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt index ee91318f..6c7276e0 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -14,6 +14,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.rememberDialogState import org.tree.app.controller.io.Neo4jIO +import org.tree.app.controller.io.handleIOException +import org.tree.app.view.dialogs.AlertDialog import org.tree.binaryTree.KVP import org.tree.binaryTree.RBNode From f7e049266ecac9ef81db2c05579b0f9cc4001e63 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 1 May 2023 10:51:56 +0300 Subject: [PATCH 255/296] feat: Add exit dialog --- .../org/tree/app/view/dialogs/ExitDialog.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt new file mode 100644 index 00000000..340aefa7 --- /dev/null +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt @@ -0,0 +1,69 @@ +package org.tree.app.view.dialogs + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.rememberDialogState + +@Composable +fun ExitDialog( + isOpen: Boolean, + onCloseRequest: () -> Unit, + onExitRequest: () -> Unit, + additionalButtons: @Composable () -> Unit +) { + if (isOpen) { + Dialog( + title = "Confirm exit", + onCloseRequest = { onCloseRequest() }, + state = rememberDialogState(width = 750.dp, height = 300.dp) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(modifier = Modifier.weight(0.1f)) + Row { + Spacer(modifier = Modifier.weight(0.1f)) + Text( + "Are you sure you want to exit?", + textAlign = TextAlign.Center, + modifier = Modifier.weight(1.0f) + ) + Spacer(modifier = Modifier.weight(0.1f)) + } + + Spacer(modifier = Modifier.weight(0.1f)) + + Column { + Row(horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) { + additionalButtons() + } + Row(horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { onCloseRequest() }, colors = ButtonDefaults.buttonColors( + backgroundColor = Color.Gray, + contentColor = Color.White + ) + ) { + Text("Cancel") + } + Button( + onClick = { onExitRequest() }, colors = ButtonDefaults.buttonColors( + backgroundColor = Color(0xffff5b79), + contentColor = Color.White + ) + ) { + Text("Exit") + } + } + } + } + } + } +} From e933056573dd85282a675f09a261035440a46f0b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 1 May 2023 10:53:28 +0300 Subject: [PATCH 256/296] feat: Implement exit dialog to main app --- app/src/main/kotlin/org/tree/app/App.kt | 39 +++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index f8c1268b..e7eb09ba 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -32,6 +32,7 @@ import org.tree.app.controller.io.SavedType import org.tree.app.controller.io.handleIOException import org.tree.app.view.* import org.tree.app.view.dialogs.AlertDialog +import org.tree.app.view.dialogs.ExitDialog import org.tree.app.view.dialogs.io.* import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP @@ -50,8 +51,10 @@ enum class DialogType { val appDataController = AppDataController() fun main() = application { val icon = painterResource("icon.png") + var showExitDialog by remember { mutableStateOf(false) } + Window( - onCloseRequest = ::exitApplication, + onCloseRequest = { showExitDialog = true }, title = "Trees", state = rememberWindowState(width = 800.dp, height = 600.dp), icon = icon @@ -168,7 +171,8 @@ fun main() = application { } } } - AlertDialog(exceptionContent, throwException) { throwException = false } + + Row(modifier = Modifier.background(Color.LightGray)) { @@ -301,5 +305,36 @@ fun main() = application { ) } } + + AlertDialog(exceptionContent, throwException, onCloseRequest = { throwException = false }) + ExitDialog( + showExitDialog, + onCloseRequest = { showExitDialog = false }, + onExitRequest = ::exitApplication + ) { + if (treeController.nodeType() is Node<*>?) { + Button(onClick = { + @Suppress("UNCHECKED_CAST") + exportBST(treeController as TreeController>>) + }) { + Text("Save as Bin Search Tree") + } + } + if (treeController.nodeType() is AVLNode<*>?) { + Button(onClick = { + @Suppress("UNCHECKED_CAST") + exportAVLT(treeController as TreeController>>) + }) { + Text("Save as AVL Tree") + } + } + if (treeController.nodeType() is RBNode<*>?) { + Button(onClick = { + dialogType = DialogType.EXPORT_RB + }) { + Text("Save as RB Tree") + } + } + } } } From e91d2936b0df33b04b732cb9a592682542612c80 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:28:14 +0300 Subject: [PATCH 257/296] refactor(ExitDialog): Rename param --- app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt index 340aefa7..a7ff9848 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt @@ -15,12 +15,12 @@ import androidx.compose.ui.window.rememberDialogState @Composable fun ExitDialog( - isOpen: Boolean, + isDialogOpened: Boolean, onCloseRequest: () -> Unit, onExitRequest: () -> Unit, additionalButtons: @Composable () -> Unit ) { - if (isOpen) { + if (isDialogOpened) { Dialog( title = "Confirm exit", onCloseRequest = { onCloseRequest() }, From 8a1cbd8bffe3cdf8617e30cdcc7baee342245c64 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 1 May 2023 22:42:07 +0300 Subject: [PATCH 258/296] refactor(Neo4jIO): Edit getting of color in import --- app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index b1fdb739..0ceb28c4 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -3,7 +3,6 @@ package org.tree.app.controller.io import NodeExtension import TreeController import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.graphics.Color import org.neo4j.driver.* import org.neo4j.driver.exceptions.AuthenticationException import org.neo4j.driver.exceptions.ClientException @@ -194,17 +193,15 @@ class Neo4jIO() : Closeable { val x = nkRecord["x"].asInt() val y = nkRecord["y"].asInt() - val color: Color val isBlack = nkRecord["isBlack"].asBoolean() node.col = if (isBlack) { - color = Color.DarkGray RBNode.Colour.BLACK } else { - color = Color.Red RBNode.Colour.RED } - treeController.nodes[node] = NodeExtension(mutableStateOf(x), mutableStateOf(y), color) + treeController.nodes[node] = + NodeExtension(mutableStateOf(x), mutableStateOf(y), treeController.getNodeCol(node)) val lkey = if (nkRecord["lKey"].isNull) { null From 1283fef8ce0e855bb2af24501f4d26c70b37a5c8 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 1 May 2023 23:04:51 +0300 Subject: [PATCH 259/296] fix(Neo4jDialog): Fix text fields --- .../org/tree/app/view/dialogs/io/Neo4jConnect.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt index fa8b9f91..2ae16f8a 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jConnect.kt @@ -22,15 +22,23 @@ fun Neo4jConnect(onSuccess: (Neo4jIO) -> Unit, onFail: (Neo4jIO) -> Unit) { Column(horizontalAlignment = Alignment.CenterHorizontally) { OutlinedTextField( + label = { Text("Url") }, value = appDataController.data.neo4j.url, - onValueChange = { appDataController.data.neo4j.url = it }) + onValueChange = { appDataController.data.neo4j.url = it }, + singleLine = true + ) OutlinedTextField( + label = { Text("Username") }, value = appDataController.data.neo4j.login, - onValueChange = { appDataController.data.neo4j.login = it }) + onValueChange = { appDataController.data.neo4j.login = it }, + singleLine = true + ) OutlinedTextField( + label = { Text("Password") }, value = appDataController.data.neo4j.password, onValueChange = { appDataController.data.neo4j.password = it }, - visualTransformation = PasswordVisualTransformation() + visualTransformation = PasswordVisualTransformation(), + singleLine = true ) Row(verticalAlignment = Alignment.CenterVertically) { Button(onClick = { From 17892751278d92dfa00112a449bb26fd9689e99e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Mon, 1 May 2023 23:11:25 +0300 Subject: [PATCH 260/296] feat(Neo4jDialog): Add auto choosing of tree name --- .../tree/app/view/dialogs/io/Neo4jDialogs.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt index 6c7276e0..b9fafc96 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -105,10 +105,21 @@ fun Neo4jIODialog( isDialogOpen = false }) { Column(horizontalAlignment = Alignment.CenterHorizontally) { - Neo4jConnect(onSuccess = { - db = it - isDBEnable = true - }, + Neo4jConnect( + onSuccess = { dataBase -> + db = dataBase + isDBEnable = true + val names = handleIOException( + onCatch = { + exceptionContent = it.toString() + throwException = true + } + ) { db.getTreesNames() } + if (names != null) { + if (names.size > 0) + treeName = names[0] + } + }, onFail = { db = it isDBEnable = false From a4dd1862d410acaaff0519abeb4237d7c1673e8a Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 01:17:23 +0300 Subject: [PATCH 261/296] feat(Neo4jDialog): Add closing of database on exit --- app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt index b9fafc96..769ed224 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/Neo4jDialogs.kt @@ -181,6 +181,7 @@ fun Neo4jIODialog( } } } else { + db.close() onCloseRequest() } } From 0774dcf3d0538251e5a0002cad1323bbb30d4b23 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 01:36:20 +0300 Subject: [PATCH 262/296] feat(Neo4jIO): Change skipped IOException to HandledIOException --- app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 0ceb28c4..4ee9e997 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -251,7 +251,7 @@ class Neo4jIO() : Closeable { val name = nmRecord["name"].asString() res.add(name) } catch (ex: Uncoercible) { - throw IOException("Invalid tree label in the database", ex) + throw HandledIOException("Invalid tree label in the database", ex) } } return res From b7f27767c6f8bf64224c57fcf0f9ee6b39091ea9 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 01:40:36 +0300 Subject: [PATCH 263/296] refactor(Neo4jIO): Refactor map types --- app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 4ee9e997..db694db6 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -66,7 +66,7 @@ class Neo4jIO() : Closeable { "REMOVE r:$NEW_NODE", mutableMapOf( "treeName" to treeName - ) as Map? + ) as Map )// connect tree and root } @@ -119,7 +119,7 @@ class Neo4jIO() : Closeable { "DETACH DELETE t, n", mutableMapOf( "treeName" to treeName - ) as Map? + ) as Map ) // delete tree and all its nodes } @@ -150,7 +150,7 @@ class Neo4jIO() : Closeable { "isBlack" to (curNode.col == RBNode.Colour.BLACK), "lkey" to lkey, "rkey" to rkey - ) as Map? + ) as Map ) exportRBNode(tx, curNode.left, nodes) exportRBNode(tx, curNode.right, nodes) @@ -171,7 +171,7 @@ class Neo4jIO() : Closeable { " l.key AS lKey, r.key AS rKey", mutableMapOf( "treeName" to treeName - ) as Map? + ) as Map ) // for all nodes get their properties + keys of their children return parseRBNodes(nodeAndKeysRecords, treeController) } From 2da8843ed90039710298d0cdf041274918d039a2 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 19:00:38 +0300 Subject: [PATCH 264/296] refactor(ExitDialog): Rename param (again) --- app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt index a7ff9848..d3e131aa 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/ExitDialog.kt @@ -15,12 +15,12 @@ import androidx.compose.ui.window.rememberDialogState @Composable fun ExitDialog( - isDialogOpened: Boolean, + showExitDialog: Boolean, onCloseRequest: () -> Unit, onExitRequest: () -> Unit, additionalButtons: @Composable () -> Unit ) { - if (isDialogOpened) { + if (showExitDialog) { Dialog( title = "Confirm exit", onCloseRequest = { onCloseRequest() }, From 395fcede46708c3b1dfaf5d8fb278c723a39f0f4 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 18:28:11 +0300 Subject: [PATCH 265/296] refactor(SQLiteIO): Change the order of defining functions --- .../org/tree/app/controller/io/SQLiteIO.kt | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 067305c3..cd830771 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -39,6 +39,28 @@ class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with class SQLiteIO { private var amountOfNodesToHandle = 0 + + fun exportTree(treeController: TreeController>>, file: File) { + try { + Files.createDirectories(file.toPath().parent) + } catch (ex: SecurityException) { + throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) + } + Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") + transaction { + try { + SchemaUtils.drop(Nodes) + SchemaUtils.create(Nodes) + } catch (ex: ExposedSQLException) { + throw HandledIOException("File is not a database", ex) + } + val root = treeController.tree.root + if (root != null) { + parseNodesForExport(root, null, treeController) + } + } + } + fun importTree(file: File): TreeController>> { Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") val treeController = TreeController(BinSearchTree()) @@ -60,28 +82,28 @@ class SQLiteIO { return treeController } - fun exportTree(treeController: TreeController>>, file: File) { - try { - Files.createDirectories(file.toPath().parent) - } catch (ex: SecurityException) { - throw HandledIOException("Directory ${file.toPath().parent} cannot be created: no access", ex) + private fun parseNodesForExport( + curNode: Node>, + parNode: Node>?, + treeController: TreeController>> + ) { + InstanceOfNode.new { + key = curNode.elem.key + parentKey = parNode?.elem?.key + value = curNode.elem.v.toString() + x = treeController.nodes[curNode]?.x?.value ?: 0 + y = treeController.nodes[curNode]?.y?.value ?: 0 } - Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") - transaction { - try { - SchemaUtils.drop(Nodes) - SchemaUtils.create(Nodes) - } catch (ex: ExposedSQLException) { - throw HandledIOException("File is not a database", ex) - } - val root = treeController.tree.root - if (root != null) { - parseNodesForExport(root, null, treeController) - } + val leftChild = curNode.left + if (leftChild != null) { + parseNodesForExport(leftChild, curNode, treeController) + } + val rightChild = curNode.right + if (rightChild != null) { + parseNodesForExport(rightChild, curNode, treeController) } } - private fun parseRootForImport( setOfNodes: MutableSet, treeController: TreeController>> @@ -152,28 +174,6 @@ class SQLiteIO { } } - private fun parseNodesForExport( - curNode: Node>, - parNode: Node>?, - treeController: TreeController>> - ) { - InstanceOfNode.new { - key = curNode.elem.key - parentKey = parNode?.elem?.key - value = curNode.elem.v.toString() - x = treeController.nodes[curNode]?.x?.value ?: 0 - y = treeController.nodes[curNode]?.y?.value ?: 0 - } - val leftChild = curNode.left - if (leftChild != null) { - parseNodesForExport(leftChild, curNode, treeController) - } - val rightChild = curNode.right - if (rightChild != null) { - parseNodesForExport(rightChild, curNode, treeController) - } - } - private fun addCoordinatesToNode( node: Node>, x: Int, y: Int, treeController: TreeController>> From a73ae09af26693b5a8156f7779d8d9a2cb1c3891 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 20:10:50 +0300 Subject: [PATCH 266/296] refactor(SQLiteIO): Actions with setOfNodes moved out of transactions --- .../org/tree/app/controller/io/SQLiteIO.kt | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index cd830771..06d2e89c 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -22,7 +22,7 @@ import java.sql.SQLException object Nodes : IntIdTable() { val key = integer("key") val parentKey = integer("parentKey").nullable() - val value = text("value") + val value = text("value", eagerLoading = true) val x = integer("x") val y = integer("y") } @@ -62,22 +62,11 @@ class SQLiteIO { } fun importTree(file: File): TreeController>> { - Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") val treeController = TreeController(BinSearchTree()) - transaction { - try { - if (Nodes.exists()) { - val setOfNodes = InstanceOfNode.all().toMutableSet() - val amountOfNodes = setOfNodes.count() - if (amountOfNodes > 0) { - parseRootForImport(setOfNodes, treeController) - } - } else { - throw HandledIOException("Database without a Nodes table") - } - } catch (ex: SQLException) { - throw HandledIOException("File is not a SQLite database", ex) - } + val setOfNodes = getSetOfNodesFromDB(file) + val amountOfNodes = setOfNodes.count() + if (amountOfNodes > 0) { + parseRootForImport(setOfNodes, treeController) } return treeController } @@ -104,6 +93,21 @@ class SQLiteIO { } } + private fun getSetOfNodesFromDB(file: File): MutableSet { + Database.connect("jdbc:sqlite:${file.path}", "org.sqlite.JDBC") + return transaction { + try { + if (Nodes.exists()) { + InstanceOfNode.all().toMutableSet() + } else { + throw HandledIOException("Database without a Nodes table") + } + } catch (ex: SQLException) { + throw HandledIOException("File is not a SQLite database", ex) + } + } + } + private fun parseRootForImport( setOfNodes: MutableSet, treeController: TreeController>> From 61ed1308b57a71faf8bd1fe8d387b7dfb564e988 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 20:24:48 +0300 Subject: [PATCH 267/296] refactor(SQLiteIO): Remove class property, replace count method with a size property --- app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 06d2e89c..08a719a3 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -38,8 +38,6 @@ class InstanceOfNode(id: EntityID) : IntEntity(id) { // A separate row with } class SQLiteIO { - private var amountOfNodesToHandle = 0 - fun exportTree(treeController: TreeController>>, file: File) { try { Files.createDirectories(file.toPath().parent) @@ -120,7 +118,6 @@ class SQLiteIO { val parsedX = node.x val parsedY = node.y val bst = treeController.tree - amountOfNodesToHandle = setOfNodes.count() bst.insert(KVP(parsedKey, parsedValue)) val root = bst.root if (root != null) { @@ -143,7 +140,7 @@ class SQLiteIO { curNode: Node>, treeController: TreeController>> ) { - if (amountOfNodesToHandle <= 0) { + if (setOfNodes.size == 0) { return } val nodes = setOfNodes.elementAt(0) @@ -158,7 +155,6 @@ class SQLiteIO { val newNode = Node(KVP(parsedKey, parsedValue)) addCoordinatesToNode(newNode, parsedX, parsedY, treeController) setOfNodes.remove(nodes) - amountOfNodesToHandle-- if (parsedKey < parsedParentKey) { if (curNode.left != null) throw HandledIOException("Incorrect binary tree: there are at least two left children of node with key = ${curNode.elem.key}") curNode.left = newNode From 73c81392f6965c57d119fddc8a013e2d3d6ef77e Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:18:38 +0300 Subject: [PATCH 268/296] refactor: Add kdoc to TemplateNode --- .../binaryTree/templates/TemplateBSTree.kt | 2 +- .../tree/binaryTree/templates/TemplateNode.kt | 38 +++++++++++++------ .../kotlin/org/tree/binaryTree/NodeTest.kt | 6 +-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index a3f35f70..fd0d11b5 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -135,6 +135,6 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { - return root?.traversal(order) ?: mutableListOf() + return root?.traverse(order) ?: mutableListOf() } } diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt index 4c06b43b..943db79c 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt @@ -1,7 +1,15 @@ package org.tree.binaryTree.templates -abstract class TemplateNode, NODE_T : TemplateNode>(v: T) { - var elem: T = v +/** + * This class is template class for creating your own nodes. + * @param T the type of element stored in the node + * @param NODE_T the type of your node + * + * @property elem the element stored in the node. + * @property left the left child node of this node, or null if this node does not have a left child node. + * @property right the right child node of this node, or null if this node does not have a right child node. + */ +abstract class TemplateNode, NODE_T : TemplateNode>(var elem: T) { var left: NODE_T? = null var right: NODE_T? = null @@ -27,30 +35,38 @@ abstract class TemplateNode, NODE_T : TemplateNode> return res } - //Traversal + /** + * @property INORDER first the left child, then the parent and the right child + * @property PREORDER first the parent, then the left child and the right child + * @property POSTORDER first the left child, then the right child and the parent + */ enum class Traversal { INORDER, PREORDER, POSTORDER } - private fun traversal(res: MutableList, cs: Traversal) { - if (cs == Traversal.PREORDER) { + private fun traverse(res: MutableList, traversalOrder: Traversal) { + if (traversalOrder == Traversal.PREORDER) { res.add(elem) } - left?.traversal(res, cs) - if (cs == Traversal.INORDER) { + left?.traverse(res, traversalOrder) + if (traversalOrder == Traversal.INORDER) { res.add(elem) } - right?.traversal(res, cs) - if (cs == Traversal.POSTORDER) { + right?.traverse(res, traversalOrder) + if (traversalOrder == Traversal.POSTORDER) { res.add(elem) } } - fun traversal(order: Traversal): MutableList { + /** + * @return a list of nodes' elements in the specified order + * @param order specified traversal order + */ + fun traverse(order: Traversal): MutableList { val res: MutableList = mutableListOf() - traversal(res, order) + traverse(res, order) return res } } diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index cfd34f22..63cdd3be 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -24,21 +24,21 @@ class NodeTest { @Test fun preorderTraversalTest() { - val act = root?.traversal(TemplateNode.Traversal.PREORDER) + val act = root?.traverse(TemplateNode.Traversal.PREORDER) val exp = listOf(40, 20, 15, 60, 67) MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test fun inorderTraversalTest() { - val act = root?.traversal(TemplateNode.Traversal.INORDER) + val act = root?.traverse(TemplateNode.Traversal.INORDER) val exp = listOf(15, 20, 40, 60, 67) MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } @Test fun postorderTraversalTest() { - val act = root?.traversal(TemplateNode.Traversal.POSTORDER) + val act = root?.traverse(TemplateNode.Traversal.POSTORDER) val exp = listOf(15, 20, 67, 60, 40) MatcherAssert.assertThat(act, Matchers.equalTo(exp)) } From 20f26a4123856fef0f94c19f11a53c548ba92468 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:21:05 +0300 Subject: [PATCH 269/296] refactor(TemplateNode): Remove countNullChildren() method --- .../binaryTree/templates/TemplateBSTree.kt | 7 ++- .../tree/binaryTree/templates/TemplateNode.kt | 11 ---- .../kotlin/org/tree/binaryTree/NodeTest.kt | 58 ------------------- 3 files changed, 6 insertions(+), 70 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index fd0d11b5..75eb371a 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -62,7 +62,12 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { val nxt = diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt index 943db79c..a5726df8 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt @@ -24,17 +24,6 @@ abstract class TemplateNode, NODE_T : TemplateNode> return res } - fun countNullChildren(): Int { - var res = 0 - if (left == null) { - res += 1 - } - if (right == null) { - res += 1 - } - return res - } - /** * @property INORDER first the left child, then the parent and the right child * @property PREORDER first the parent, then the left child and the right child diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index 63cdd3be..80941c76 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -101,62 +101,4 @@ class NodeTest { } } - - @DisplayName("Count null children tests") - @Nested - inner class CountNullChildrenTests { - - @Test - fun leftChildrenTest() { - root = generateNodeTree( - listOf( - 40, - 20, null - ) - ) - val act = root?.countNullChildren() - val exp = 1 - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - @Test - fun rightChildrenTest() { - root = generateNodeTree( - listOf( - 40, - null, 60 - ) - ) - val act = root?.countNullChildren() - val exp = 1 - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - @Test - fun noChildrenTest() { - root = generateNodeTree( - listOf( - 40, - null, null - ) - ) - val act = root?.countNullChildren() - val exp = 2 - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - @Test - fun twoChildrenTest() { - root = generateNodeTree( - listOf( - 40, - 20, 60 - ) - ) - val act = root?.countNullChildren() - val exp = 0 - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - } } From 59acbdfa07a0524844fe468d4a6d9ac4a55e43ad Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:23:48 +0300 Subject: [PATCH 270/296] refactor(TemplateNode): Remove getChild() method --- .../binaryTree/templates/TemplateBSTree.kt | 2 +- .../tree/binaryTree/templates/TemplateNode.kt | 11 ---- .../kotlin/org/tree/binaryTree/NodeTest.kt | 57 ------------------- 3 files changed, 1 insertion(+), 69 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index 75eb371a..320bc8f3 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -78,7 +78,7 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { - replaceNode(curNode, parentNode, curNode.getChild()) + replaceNode(curNode, parentNode, curNode.left ?: curNode.right) } else -> { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt index a5726df8..64964d71 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt @@ -13,17 +13,6 @@ abstract class TemplateNode, NODE_T : TemplateNode> var left: NODE_T? = null var right: NODE_T? = null - fun getChild(): NODE_T? { - val res = if (left != null) { - left - } else if (right != null) { - right - } else { - null - } - return res - } - /** * @property INORDER first the left child, then the parent and the right child * @property PREORDER first the parent, then the left child and the right child diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt index 80941c76..60e79d07 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/NodeTest.kt @@ -44,61 +44,4 @@ class NodeTest { } } - - @DisplayName("Get non null child tests") - @Nested - inner class GetNonNullChildTests { - - @Test - fun leftChildrenTest() { - root = generateNodeTree( - listOf( - 40, - 20, null - ) - ) - val act = root?.getChild() - val exp = root?.left - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - @Test - fun rightChildrenTest() { - root = generateNodeTree( - listOf( - 40, - null, 60 - ) - ) - val act = root?.getChild() - val exp = root?.right - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - @Test - fun noChildrenTest() { - root = generateNodeTree( - listOf( - 40, - null, null - ) - ) - val act = root?.getChild() - val exp = null - MatcherAssert.assertThat(act, Matchers.equalTo(exp)) - } - - @Test - fun twoChildrenTest() { - root = generateNodeTree( - listOf( - 40, - 20, 60 - ) - ) - val act = root?.getChild() - MatcherAssert.assertThat(act, Matchers.anyOf(Matchers.equalTo(root?.left), Matchers.equalTo(root?.right))) - } - - } } From c58743becc1f7193c44e68087971a84953567f12 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:31:00 +0300 Subject: [PATCH 271/296] refactor(TemplateNode): Rename elem to element --- app/src/main/kotlin/org/tree/app/App.kt | 4 ++-- .../org/tree/app/controller/io/JsonIO.kt | 4 ++-- .../org/tree/app/controller/io/Neo4jIO.kt | 10 +++++----- .../org/tree/app/controller/io/SQLiteIO.kt | 14 +++++++------- app/src/main/kotlin/org/tree/app/view/Tree.kt | 5 +++-- .../binaryTree/templates/TemplateBSTree.kt | 18 +++++++++--------- .../templates/TemplateBalanceBSTree.kt | 10 +++++----- .../tree/binaryTree/templates/TemplateNode.kt | 10 +++++----- .../kotlin/org/tree/binaryTree/trees/RBTree.kt | 10 +++++----- .../org/tree/binaryTree/BinSearchTreeTest.kt | 4 ++-- .../test/kotlin/org/tree/binaryTree/Util.kt | 8 ++++---- 11 files changed, 49 insertions(+), 48 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index e7eb09ba..6c2c96d4 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -95,7 +95,7 @@ fun main() = application { fun toTreeRoot() { val treeRoot = treeController.tree.root if (treeRoot != null) { - val coordinates = treeController.nodes[treeController.find(treeRoot.elem)] + val coordinates = treeController.nodes[treeController.find(treeRoot.element)] if (coordinates != null) { logString = "Moved to root." logColor = Color.Green @@ -208,7 +208,7 @@ fun main() = application { val key = convertKey(keyString) ?: return@FindRow val node = treeController.find(KVP(key)) if (node != null) { - logString = "Node with key = $key and value = \"${node.elem.v}\" found." + logString = "Node with key = $key and value = \"${node.element.v}\" found." logColor = Color.Green val coordinates = treeController.nodes[node] if (coordinates != null) { diff --git a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt index 609b7a10..308abdfe 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt @@ -34,8 +34,8 @@ class JsonIO { private fun AVLNode>.serialize(treeController: TreeController>>): JsonAVLNode { return JsonAVLNode( - key = this.elem.key, - value = this.elem.v, + key = this.element.key, + value = this.element.v, x = treeController.nodes[this]?.x?.value ?: 0, y = treeController.nodes[this]?.y?.value ?: 0, height = this.height, diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index db694db6..5f9b4894 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -129,11 +129,11 @@ class Neo4jIO() : Closeable { nodes: MutableMap>, NodeExtension> ) { if (curNode != null) { - val lkey = curNode.left?.elem?.key - val rkey = curNode.right?.elem?.key + val lkey = curNode.left?.element?.key + val rkey = curNode.right?.element?.key val ext = nodes[curNode] if (ext == null) { - throw IOException("Can't find coordinates for node with key ${curNode.elem.key}") + throw IOException("Can't find coordinates for node with key ${curNode.element.key}") } else { tx.run( "CREATE (:$RBNODE:$NEW_NODE {key : \$key, " + @@ -144,8 +144,8 @@ class Neo4jIO() : Closeable { "rkey: \$rkey" + "})", mutableMapOf( - "key" to curNode.elem.key, - "value" to (curNode.elem.v ?: ""), + "key" to curNode.element.key, + "value" to (curNode.element.v ?: ""), "x" to ext.x.value, "y" to ext.y.value, "isBlack" to (curNode.col == RBNode.Colour.BLACK), "lkey" to lkey, diff --git a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt index 08a719a3..de498c1d 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/SQLiteIO.kt @@ -75,9 +75,9 @@ class SQLiteIO { treeController: TreeController>> ) { InstanceOfNode.new { - key = curNode.elem.key - parentKey = parNode?.elem?.key - value = curNode.elem.v.toString() + key = curNode.element.key + parentKey = parNode?.element?.key + value = curNode.element.v.toString() x = treeController.nodes[curNode]?.x?.value ?: 0 y = treeController.nodes[curNode]?.y?.value ?: 0 } @@ -150,13 +150,13 @@ class SQLiteIO { val parsedValue = nodes.value val parsedX = nodes.x val parsedY = nodes.y - if (parsedKey == parsedParentKey) throw HandledIOException("Child with key = ${curNode.elem.key} is parent for himself") - if (parsedParentKey == curNode.elem.key) { + if (parsedKey == parsedParentKey) throw HandledIOException("Child with key = ${curNode.element.key} is parent for himself") + if (parsedParentKey == curNode.element.key) { val newNode = Node(KVP(parsedKey, parsedValue)) addCoordinatesToNode(newNode, parsedX, parsedY, treeController) setOfNodes.remove(nodes) if (parsedKey < parsedParentKey) { - if (curNode.left != null) throw HandledIOException("Incorrect binary tree: there are at least two left children of node with key = ${curNode.elem.key}") + if (curNode.left != null) throw HandledIOException("Incorrect binary tree: there are at least two left children of node with key = ${curNode.element.key}") curNode.left = newNode val leftChild = curNode.left if (leftChild != null) { @@ -164,7 +164,7 @@ class SQLiteIO { } parseNodesForImport(setOfNodes, curNode, treeController) } else { // When parsedKey is greater than parsedParentKey - if (curNode.right != null) throw HandledIOException("Incorrect binary tree: there are at least two right children of node with key = ${curNode.elem.key}") + if (curNode.right != null) throw HandledIOException("Incorrect binary tree: there are at least two right children of node with key = ${curNode.element.key}") curNode.right = newNode val rightChild = curNode.right if (rightChild != null) { diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index 86939aad..ee41c1b3 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -55,9 +55,10 @@ fun , NODE_T>> TreeView( var x by n.value.x var y by n.value.y val col = n.value.color - with(n.key.elem) { + with(n.key.element) { key(key) { - Node(x + offsetX.value, + Node( + x + offsetX.value, y + offsetY.value, key, v ?: "", diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index 320bc8f3..757edb32 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -13,14 +13,14 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.elem) { + } else if (newNode.element > curNode.element) { if (curNode.right == null) { curNode.right = newNode return curNode @@ -47,9 +47,9 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.elem) { + } else if (obj > curNode.element) { return find(curNode.right, obj) } else { return curNode @@ -72,9 +72,9 @@ abstract class TemplateBSTree, NODE_T : TemplateNode { val nxt = findNext(curNode) ?: throw IllegalArgumentException("Got null as next than right child isn't null") - val buf = nxt.elem - remove(nxt.elem) - curNode.elem = buf + val buf = nxt.element + remove(nxt.element) + curNode.element = buf } 1 -> { @@ -93,9 +93,9 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.elem) { + } else if (obj > curNode.element) { return remove(curNode.right, curNode, obj) } else { return deleteNode(curNode, parentNode) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index 8f268f25..3ff259be 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -31,7 +31,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode, NODE_T : TemplateNode curNode.elem) { + } else if (obj > curNode.element) { isRec = BalanceCase.Recursive.RECURSIVE_CALL targetNode = parentNode remove(curNode.right, curNode, obj) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt index 64964d71..31575916 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateNode.kt @@ -5,11 +5,11 @@ package org.tree.binaryTree.templates * @param T the type of element stored in the node * @param NODE_T the type of your node * - * @property elem the element stored in the node. + * @property element the element stored in the node. * @property left the left child node of this node, or null if this node does not have a left child node. * @property right the right child node of this node, or null if this node does not have a right child node. */ -abstract class TemplateNode, NODE_T : TemplateNode>(var elem: T) { +abstract class TemplateNode, NODE_T : TemplateNode>(var element: T) { var left: NODE_T? = null var right: NODE_T? = null @@ -26,15 +26,15 @@ abstract class TemplateNode, NODE_T : TemplateNode> private fun traverse(res: MutableList, traversalOrder: Traversal) { if (traversalOrder == Traversal.PREORDER) { - res.add(elem) + res.add(element) } left?.traverse(res, traversalOrder) if (traversalOrder == Traversal.INORDER) { - res.add(elem) + res.add(element) } right?.traverse(res, traversalOrder) if (traversalOrder == Traversal.POSTORDER) { - res.add(elem) + res.add(element) } } diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 295f3aa2..2c034988 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -6,12 +6,12 @@ import org.tree.binaryTree.templates.TemplateBalanceBSTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode != null) { - if (obj > curNode.elem) { + if (obj > curNode.element) { if (curNode.right == null) { return curNode } return findParentForNewNode(curNode.right, obj) - } else if (obj < curNode.elem) { + } else if (obj < curNode.element) { if (curNode.left == null) { return curNode } @@ -73,7 +73,7 @@ class RBTree> : TemplateBalanceBSTree>() { val grandParent = parentNode.parent if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree val unclePosition: BalancePosition - val uncle = if (parentNode.elem < grandParent.elem) { + val uncle = if (parentNode.element < grandParent.element) { unclePosition = BalancePosition.RIGHT_UNCLE grandParent.right } else { @@ -321,7 +321,7 @@ class RBTree> : TemplateBalanceBSTree>() { otherChild.col = RBNode.Colour.RED val grandParent = parentNode.parent if (grandParent != null) { - if (parentNode.elem < grandParent.elem) { + if (parentNode.element < grandParent.element) { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) @@ -355,7 +355,7 @@ class RBTree> : TemplateBalanceBSTree>() { otherChild.col = RBNode.Colour.RED val grandParent = parentNode.parent if (grandParent != null) { - if (parentNode.elem < grandParent.elem) { + if (parentNode.element < grandParent.element) { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt index 152c05bb..7c59a563 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/BinSearchTreeTest.kt @@ -185,7 +185,7 @@ class BinSearchTreeTest { assertThat(act != null, equalTo(exp)) if (act != null) { - assertThat(act.elem, equalTo(fndVal)) + assertThat(act.element, equalTo(fndVal)) } } @@ -248,7 +248,7 @@ class BinSearchTreeTest { assertThat(act != null, equalTo(exp)) if (act != null) { - assertThat(act.elem, equalTo(curVal)) + assertThat(act.element, equalTo(curVal)) } } } diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt index 9d343cb1..89671ff5 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -52,13 +52,13 @@ fun , NODE_T : TemplateNode> checkTreeNode(curNode: if (curNode != null) { val l = curNode.left if (l != null) { - assertThat(curNode.elem, greaterThanOrEqualTo(l.elem)) + assertThat(curNode.element, greaterThanOrEqualTo(l.element)) checkTreeNode(l) } val r = curNode.right if (r != null) { - assertThat(curNode.elem, lessThanOrEqualTo(r.elem)) + assertThat(curNode.element, lessThanOrEqualTo(r.element)) checkTreeNode(r) } } @@ -86,14 +86,14 @@ fun > checkRBTreeNode(curNode: RBNode?, parNode: RBNode? var lBlackHeight = 0 val l = curNode.left if (l != null) { - assertThat(curNode.elem, greaterThanOrEqualTo(l.elem)) + assertThat(curNode.element, greaterThanOrEqualTo(l.element)) lBlackHeight = checkRBTreeNode(l, curNode) } var rBlackHeight = 0 val r = curNode.right if (r != null) { - assertThat(curNode.elem, lessThanOrEqualTo(r.elem)) + assertThat(curNode.element, lessThanOrEqualTo(r.element)) rBlackHeight = checkRBTreeNode(r, curNode) } From 9384bb502d7b2ad4496710d9064db432a2fbfd82 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:40:21 +0300 Subject: [PATCH 272/296] refactor(RBNode): Fix color naming --- .../org/tree/app/controller/TreeController.kt | 2 +- .../org/tree/app/controller/io/Neo4jIO.kt | 8 +- .../main/kotlin/org/tree/binaryTree/Nodes.kt | 4 +- .../org/tree/binaryTree/trees/RBTree.kt | 134 +++++++++--------- .../test/kotlin/org/tree/binaryTree/Util.kt | 6 +- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index a0c61d0d..9fb62faa 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -78,7 +78,7 @@ class TreeController, NODE_T>>( fun getNodeCol(curNode: NODE_T): Color { return if (curNode is RBNode<*>) { - if (curNode.col == RBNode.Colour.BLACK) { + if (curNode.color == RBNode.Color.BLACK) { Color.DarkGray } else { Color.Red diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index 5f9b4894..bb8f3b92 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -147,7 +147,7 @@ class Neo4jIO() : Closeable { "key" to curNode.element.key, "value" to (curNode.element.v ?: ""), "x" to ext.x.value, "y" to ext.y.value, - "isBlack" to (curNode.col == RBNode.Colour.BLACK), + "isBlack" to (curNode.color == RBNode.Color.BLACK), "lkey" to lkey, "rkey" to rkey ) as Map @@ -194,10 +194,10 @@ class Neo4jIO() : Closeable { val y = nkRecord["y"].asInt() val isBlack = nkRecord["isBlack"].asBoolean() - node.col = if (isBlack) { - RBNode.Colour.BLACK + node.color = if (isBlack) { + RBNode.Color.BLACK } else { - RBNode.Colour.RED + RBNode.Color.RED } treeController.nodes[node] = diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index 62167092..ebbf3497 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -6,9 +6,9 @@ class Node>(v: T) : TemplateNode>(v) class RBNode>(p: RBNode?, v: T) : TemplateNode>(v) { var parent: RBNode? = p - var col: Colour = Colour.RED + var color: Color = Color.RED - enum class Colour { RED, BLACK } + enum class Color { RED, BLACK } } class AVLNode>(v: T) : TemplateNode>(v) { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 2c034988..1dc98fe2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -26,7 +26,7 @@ class RBTree> : TemplateBalanceBSTree>() { val newNode = RBNode(parentForObj, obj) if (parentForObj == null) { // in case of root insert | node already exist (nothing will be changed) if (root == null) { - newNode.col = RBNode.Colour.BLACK + newNode.color = RBNode.Color.BLACK } else { return null } @@ -69,7 +69,7 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun balanceInsert(parentNode: RBNode) { - if (parentNode.col == RBNode.Colour.RED) { + if (parentNode.color == RBNode.Color.RED) { val grandParent = parentNode.parent if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree val unclePosition: BalancePosition @@ -81,7 +81,7 @@ class RBTree> : TemplateBalanceBSTree>() { grandParent.left } if (uncle != null) { - if (uncle.col == RBNode.Colour.RED) { + if (uncle.color == RBNode.Color.RED) { balanceInsertCaseOfRedUncle(parentNode, grandParent, uncle) } else { balanceInsertCaseOfBLackUncle(parentNode, grandParent, unclePosition) @@ -96,12 +96,12 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceInsertCaseOfRedUncle(parentNode: RBNode, grandParent: RBNode, uncle: RBNode) { val grandGrandParent = grandParent.parent - uncle.col = RBNode.Colour.BLACK - parentNode.col = RBNode.Colour.BLACK + uncle.color = RBNode.Color.BLACK + parentNode.color = RBNode.Color.BLACK if (grandGrandParent != null) { - grandParent.col = RBNode.Colour.RED + grandParent.color = RBNode.Color.RED // https://skr.sh/sJ9LBQU2IGg, when y is curNode - if (grandGrandParent.col == RBNode.Colour.RED) { + if (grandGrandParent.color == RBNode.Color.RED) { balanceInsert(grandGrandParent) } } @@ -114,27 +114,27 @@ class RBTree> : TemplateBalanceBSTree>() { ) { // can be null uncle if (position == BalancePosition.LEFT_UNCLE) { val leftChild = parentNode.left - if (leftChild?.col == RBNode.Colour.RED) { + if (leftChild?.color == RBNode.Color.RED) { rotateRight(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } else { val rightChild = parentNode.right if (rightChild != null) { - parentNode.col = RBNode.Colour.BLACK - grandParent.col = RBNode.Colour.RED + parentNode.color = RBNode.Color.BLACK + grandParent.color = RBNode.Color.RED rotateLeft(grandParent, grandParent.parent) } } } else { val leftChild = parentNode.left - if (leftChild?.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK - grandParent.col = RBNode.Colour.RED + if (leftChild?.color == RBNode.Color.RED) { + parentNode.color = RBNode.Color.BLACK + grandParent.color = RBNode.Color.RED rotateRight(grandParent, grandParent.parent) } else { val rightChild = parentNode.right if (rightChild != null) { - if (rightChild.col == RBNode.Colour.RED) { + if (rightChild.color == RBNode.Color.RED) { rotateLeft(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } @@ -148,20 +148,20 @@ class RBTree> : TemplateBalanceBSTree>() { if (removedChild == BalanceCase.ChangedChild.RIGHT) { val rightChild = parentNode?.right if (rightChild != null) { - rightChild.col = RBNode.Colour.BLACK + rightChild.color = RBNode.Color.BLACK } } else if (removedChild == BalanceCase.ChangedChild.LEFT) { val leftChild = parentNode?.left if (leftChild != null) { - leftChild.col = RBNode.Colour.BLACK + leftChild.color = RBNode.Color.BLACK } } } private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { // balanced remove with 2 null children - if (getColourOfRemovedNode(parentNode) == RBNode.Colour.BLACK) { // red => no need to balance - if (parentNode.col == RBNode.Colour.RED) { // then other child is black + if (getColourOfRemovedNode(parentNode) == RBNode.Color.BLACK) { // red => no need to balance + if (parentNode.color == RBNode.Color.RED) { // then other child is black if (removedChild == BalanceCase.ChangedChild.RIGHT) { balanceRemove2InRightChildWithRedParent(parentNode) } else if (removedChild == BalanceCase.ChangedChild.LEFT) { @@ -182,18 +182,18 @@ class RBTree> : TemplateBalanceBSTree>() { if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK - leftChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild?.color == RBNode.Color.RED) { + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK + leftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(parentNode, parentNode.parent) - } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK + } else if (rightChildOfOtherChild?.color == RBNode.Color.RED) { + parentNode.color = RBNode.Color.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) } else { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK } } } @@ -203,18 +203,18 @@ class RBTree> : TemplateBalanceBSTree>() { if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild?.color == RBNode.Color.RED) { + parentNode.color = RBNode.Color.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK - rightChildOfOtherChild.col = RBNode.Colour.BLACK + } else if (rightChildOfOtherChild?.color == RBNode.Color.RED) { + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK + rightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(parentNode, parentNode.parent) } else { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK } } } @@ -222,7 +222,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InRightChildWithBlackParent(parentNode: RBNode) { val otherChild = parentNode.left if (otherChild != null) { - if (otherChild.col == RBNode.Colour.RED) { + if (otherChild.color == RBNode.Color.RED) { balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) } else { balanceRemove2InRightChildWithBlackParentBlackOtherChild(parentNode, otherChild) @@ -233,7 +233,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InLeftChildWithBlackParent(parentNode: RBNode) { val otherChild = parentNode.right if (otherChild != null) { - if (otherChild.col == RBNode.Colour.RED) { + if (otherChild.color == RBNode.Color.RED) { balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) } else { balanceRemove2InLeftChildWithBlackParentBlackOtherChild(parentNode, otherChild) @@ -251,19 +251,19 @@ class RBTree> : TemplateBalanceBSTree>() { rightChildOfOtherChild.left // https://skr.sh/sJD6DQ2ML5B val rightChildOfRightChildOfOtherChild = rightChildOfOtherChild.right - if (leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { - leftChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfRightChildOfOtherChild?.color == RBNode.Color.RED) { + leftChildOfRightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) - } else if (rightChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { - rightChildOfOtherChild.col = RBNode.Colour.RED - rightChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + } else if (rightChildOfRightChildOfOtherChild?.color == RBNode.Color.RED) { + rightChildOfOtherChild.color = RBNode.Color.RED + rightChildOfRightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(rightChildOfOtherChild, otherChild) balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) // case: leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED } else { - otherChild.col = RBNode.Colour.BLACK - rightChildOfOtherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.BLACK + rightChildOfOtherChild.color = RBNode.Color.RED rotateRight(parentNode, parentNode.parent) } } @@ -279,19 +279,19 @@ class RBTree> : TemplateBalanceBSTree>() { leftChildOfOtherChild.right // https://skr.sh/sJD6DQ2ML5B (inverted) val leftChildOfLeftChildOfOtherChild = leftChildOfOtherChild.left - if (rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { - rightChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + if (rightChildOfLeftChildOfOtherChild?.color == RBNode.Color.RED) { + rightChildOfLeftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - } else if (leftChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { - leftChildOfOtherChild.col = RBNode.Colour.RED - leftChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + } else if (leftChildOfLeftChildOfOtherChild?.color == RBNode.Color.RED) { + leftChildOfOtherChild.color = RBNode.Color.RED + leftChildOfLeftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(leftChildOfOtherChild, otherChild) balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) // case: rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED } else { - otherChild.col = RBNode.Colour.BLACK - leftChildOfOtherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.BLACK + leftChildOfOtherChild.color = RBNode.Color.RED rotateLeft(parentNode, parentNode.parent) } } @@ -303,8 +303,8 @@ class RBTree> : TemplateBalanceBSTree>() { ) { val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.col == RBNode.Colour.RED) { - rightChildOfOtherChild.col = RBNode.Colour.BLACK + if (rightChildOfOtherChild.color == RBNode.Color.RED) { + rightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) return @@ -312,13 +312,13 @@ class RBTree> : TemplateBalanceBSTree>() { } val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.col == RBNode.Colour.RED) { - leftChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild.color == RBNode.Color.RED) { + leftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(parentNode, parentNode.parent) return } } - otherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.RED val grandParent = parentNode.parent if (grandParent != null) { if (parentNode.element < grandParent.element) { @@ -337,8 +337,8 @@ class RBTree> : TemplateBalanceBSTree>() { ){ val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.col == RBNode.Colour.RED) { - leftChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild.color == RBNode.Color.RED) { + leftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) return @@ -346,13 +346,13 @@ class RBTree> : TemplateBalanceBSTree>() { } val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.col == RBNode.Colour.RED) { - rightChildOfOtherChild.col = RBNode.Colour.BLACK + if (rightChildOfOtherChild.color == RBNode.Color.RED) { + rightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(parentNode, parentNode.parent) return } } - otherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.RED val grandParent = parentNode.parent if (grandParent != null) { if (parentNode.element < grandParent.element) { @@ -364,18 +364,18 @@ class RBTree> : TemplateBalanceBSTree>() { return } - private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Colour { + private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Color { return if (getBlackHeight(parentNode.left) != getBlackHeight(parentNode.right)) { - RBNode.Colour.BLACK + RBNode.Color.BLACK } else { - RBNode.Colour.RED + RBNode.Color.RED } } private fun getBlackHeight(curNode: RBNode?, blackHeight: Int = 0): Int { var blackHeightVar = blackHeight if (curNode != null) { - if (curNode.col == RBNode.Colour.BLACK) { + if (curNode.color == RBNode.Color.BLACK) { blackHeightVar += 1 } return getBlackHeight(curNode.left, blackHeightVar) @@ -387,7 +387,7 @@ class RBTree> : TemplateBalanceBSTree>() { newNode?.parent = parentNode if (parentNode == null) { if (newNode != null) { - newNode.col = RBNode.Colour.BLACK + newNode.color = RBNode.Color.BLACK } } super.replaceNode(replacedNode, parentNode, newNode) @@ -398,7 +398,7 @@ class RBTree> : TemplateBalanceBSTree>() { curNode.left?.parent = parentNode curNode.left?.right?.parent = curNode if (curNode === root) { - curNode.left?.col = RBNode.Colour.BLACK + curNode.left?.color = RBNode.Color.BLACK } super.rotateRight(curNode, parentNode) } @@ -408,7 +408,7 @@ class RBTree> : TemplateBalanceBSTree>() { curNode.right?.parent = parentNode curNode.right?.left?.parent = curNode if (curNode === root) { - curNode.right?.col = RBNode.Colour.BLACK + curNode.right?.color = RBNode.Color.BLACK } super.rotateLeft(curNode, parentNode) } diff --git a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt index 89671ff5..68fd75fc 100644 --- a/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt +++ b/binaryTree/src/test/kotlin/org/tree/binaryTree/Util.kt @@ -76,8 +76,8 @@ fun > checkRBTreeNode(curNode: RBNode?, parNode: RBNode? var blackHeight = 0 if (curNode != null) { // two red nodes in row - if (parNode?.col == RBNode.Colour.RED) { - assertThat(curNode.col, equalTo(RBNode.Colour.BLACK)) + if (parNode?.color == RBNode.Color.RED) { + assertThat(curNode.color, equalTo(RBNode.Color.BLACK)) } // right parent @@ -101,7 +101,7 @@ fun > checkRBTreeNode(curNode: RBNode?, parNode: RBNode? assertThat(lBlackHeight, equalTo(rBlackHeight)) blackHeight = lBlackHeight - if (curNode.col == RBNode.Colour.BLACK) { + if (curNode.color == RBNode.Color.BLACK) { blackHeight += 1 } } From 4ee9bb36036d80b2df0b92fae5ad602bd5421158 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:40:59 +0300 Subject: [PATCH 273/296] refactor(TreeController): Remove unused private method height --- .../main/kotlin/org/tree/app/controller/TreeController.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 9fb62faa..13f8e47c 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -92,14 +92,6 @@ class TreeController, NODE_T>>( } } - private fun height(curNode: NODE_T?): Int { - return if (curNode == null) { - 0 - } else { - Integer.max(height(curNode.left), height(curNode.right)) + 1 - } - } - fun nodeType(): NODE_T? { return tree.root } From d4445c427c63a0c2bc895b200197b515fbd0fb6f Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 17:55:29 +0300 Subject: [PATCH 274/296] refactor(Nodes): Add kdoc --- .../main/kotlin/org/tree/binaryTree/Nodes.kt | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt index ebbf3497..80d65074 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/Nodes.kt @@ -1,16 +1,44 @@ package org.tree.binaryTree import org.tree.binaryTree.templates.TemplateNode +import org.tree.binaryTree.trees.AVLTree +import org.tree.binaryTree.trees.BinSearchTree +import org.tree.binaryTree.trees.RBTree -class Node>(v: T) : TemplateNode>(v) -class RBNode>(p: RBNode?, v: T) : TemplateNode>(v) { - var parent: RBNode? = p +/** + * Node for BinSearchTree + * + * @param value the value that the node will contain + * + * @see TemplateNode + * @see BinSearchTree + */ +class Node>(value: T) : TemplateNode>(value) + +/** + * Node for RBTree + * + * @param parent the parent node of the current node + * @param value the value that the node will contain + * + * @see TemplateNode + * @see RBTree + */ +class RBNode>(var parent: RBNode?, value: T) : TemplateNode>(value) { var color: Color = Color.RED enum class Color { RED, BLACK } } -class AVLNode>(v: T) : TemplateNode>(v) { +/** + * Node for AVLTree + * + * @param value the value that the node will contain + * + * @see TemplateNode + * @see AVLTree + */ +class AVLNode>(value: T) : TemplateNode>(value) { var height: Int = 1 } From 9601ae5fe83eaf6f73320e049b31c39d8bbeb7c2 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 18:51:36 +0300 Subject: [PATCH 275/296] refactor(TemplateBSTree): Add kdoc and change some names --- .../binaryTree/templates/TemplateBSTree.kt | 111 ++++++++++++++---- .../templates/TemplateBalanceBSTree.kt | 12 +- .../org/tree/binaryTree/trees/AVLTree.kt | 4 +- .../tree/binaryTree/trees/BinSearchTree.kt | 4 +- .../org/tree/binaryTree/trees/RBTree.kt | 6 +- 5 files changed, 101 insertions(+), 36 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt index 757edb32..f18c3eba 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBSTree.kt @@ -1,9 +1,22 @@ package org.tree.binaryTree.templates +/** + * This class is template class for creating your own binary search trees. + * @param T the type of element stored in the tree's nodes + * @param NODE_T the type of nodes in the tree + * + * @property root the root node of the tree + */ abstract class TemplateBSTree, NODE_T : TemplateNode> { var root: NODE_T? = null // Insert + /** + * Insert [newNode] into the subtree of the [curNode]. + * + * @return the parent of the inserted node, + * null if node with the same element already in tree or if inserted node is root + */ protected open fun insertNode(curNode: NODE_T?, newNode: NODE_T): NODE_T? { if (curNode == null) { if (root === curNode) { @@ -28,40 +41,68 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.element) { - return find(curNode.right, obj) + if (element < curNode.element) { + return find(curNode.left, element) + } else if (element > curNode.element) { + return find(curNode.right, element) } else { return curNode } } - fun find(obj: T): NODE_T? { - return find(root, obj) + /** + * Find node with the given [element] into tree. + * + * @return the found node or null if the node was not found + */ + fun find(element: T): NODE_T? { + return find(root, element) } - //Remove - protected open fun deleteNode(curNode: NODE_T, parentNode: NODE_T?): Int { + // Remove + /** + * Delete [curNode] with [parentNode] as parent from tree. + * + * @return the count of null children of deleted node + */ + protected fun deleteNode(curNode: NODE_T, parentNode: NODE_T?): Int { var res = 0 if (curNode.left == null) res += 1 @@ -88,25 +129,41 @@ abstract class TemplateBSTree, NODE_T : TemplateNode curNode.element) { - return remove(curNode.right, curNode, obj) + if (element < curNode.element) { + return remove(curNode.left, curNode, element) + } else if (element > curNode.element) { + return remove(curNode.right, curNode, element) } else { return deleteNode(curNode, parentNode) } } - fun remove(obj: T): Boolean { - return remove(root, null, obj) != null + /** + * Remove [element] from tree. + * + * @return true if the element has been successfully removed; false if it was not present in the tree. + */ + fun remove(element: T): Boolean { + return remove(root, null, element) != null } - //Additional + // Additional + /** + * Find next node after [curNode] + * + * @return node with minimal element that greater than element of current node; + * null if element of current node is the greatest + */ protected fun findNext(curNode: NODE_T): NODE_T? { var res = curNode.right if (res == null) { @@ -121,6 +178,9 @@ abstract class TemplateBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode { return root?.traverse(order) ?: mutableListOf() } diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index 3ff259be..4d177873 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -77,7 +77,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode, NODE_T : TemplateNode curNode.element) { + remove(curNode.left, curNode, element) + } else if (element > curNode.element) { isRec = BalanceCase.Recursive.RECURSIVE_CALL targetNode = parentNode - remove(curNode.right, curNode, obj) + remove(curNode.right, curNode, element) } else { isRec = BalanceCase.Recursive.END targetNode = parentNode @@ -100,7 +100,7 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode> : TemplateBalanceBSTree>() { fixHeight(curNode) } - override fun insert(curNode: AVLNode?, obj: T): AVLNode? { - return super.insertNode(curNode, AVLNode(obj)) + override fun insert(curNode: AVLNode?, element: T): AVLNode? { + return super.insertNode(curNode, AVLNode(element)) } private fun balanceNode(curNode: AVLNode, parentNode: AVLNode?) { diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt index 5303d180..8c4a9e46 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/BinSearchTree.kt @@ -4,7 +4,7 @@ import org.tree.binaryTree.Node import org.tree.binaryTree.templates.TemplateBSTree class BinSearchTree> : TemplateBSTree>() { - override fun insert(curNode: Node?, obj: T): Node? { - return insertNode(curNode, Node(obj)) + override fun insert(curNode: Node?, element: T): Node? { + return insertNode(curNode, Node(element)) } } diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 1dc98fe2..ff5b833b 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -21,9 +21,9 @@ class RBTree> : TemplateBalanceBSTree>() { return null } - override fun insert(curNode: RBNode?, obj: T): RBNode? { - val parentForObj = findParentForNewNode(curNode, obj) - val newNode = RBNode(parentForObj, obj) + override fun insert(curNode: RBNode?, element: T): RBNode? { + val parentForObj = findParentForNewNode(curNode, element) + val newNode = RBNode(parentForObj, element) if (parentForObj == null) { // in case of root insert | node already exist (nothing will be changed) if (root == null) { newNode.color = RBNode.Color.BLACK From fa922202c61ea3476e43d579f162aa53db0bc1c6 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 19:17:51 +0300 Subject: [PATCH 276/296] refactor(TemplateBalanceTree): Refactor getBalanceRemoveType() method --- .../org/tree/binaryTree/templates/TemplateBalanceBSTree.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index 4d177873..c20e55ef 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -20,11 +20,12 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode BalanceCase.OpType.REMOVE_2 1 -> BalanceCase.OpType.REMOVE_1 - else -> BalanceCase.OpType.REMOVE_0 + 0 -> BalanceCase.OpType.REMOVE_0 + else -> throw IllegalArgumentException("Expected number was <= 2, because in a binary tree a node can have no more than two children") } } From c7734c7b2f9f73d8cc99ddcec5c37ea0a4460a02 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 20:43:01 +0300 Subject: [PATCH 277/296] refactor(TemplateBalanceTree): Add kdoc and change some names --- .../templates/TemplateBalanceBSTree.kt | 87 ++++++++++++++----- .../org/tree/binaryTree/trees/AVLTree.kt | 4 +- .../org/tree/binaryTree/trees/RBTree.kt | 10 +-- 3 files changed, 73 insertions(+), 28 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt index c20e55ef..ba412bb2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/templates/TemplateBalanceBSTree.kt @@ -1,75 +1,114 @@ package org.tree.binaryTree.templates - +/** + * This class is template class for creating your own balance binary search trees. + * @param T the type of element stored in the tree's nodes + * @param NODE_T the type of nodes in the tree + * + * @property balance abstract method that should balance your tree + */ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode> : TemplateBSTree() { //Balance + /** + * @property ChangedChild what child was changed + * @property Recursive was call recursive or last + * @property OperationType from what method was called + */ protected class BalanceCase { - // LEFT - left child was changed - // RIGHT - right child was changed - // ROOT - root was changed + /** + * @property LEFT left child was changed + * @property RIGHT right child was changed + * @property ROOT root was changed + */ enum class ChangedChild { LEFT, RIGHT, ROOT } - // RECURSIVE_CALL - the function was called recursively for traverse - // END - the last, significant call + /** + * @property RECURSIVE_CALL the function was called recursively for traverse + * @property END the last, significant call + */ enum class Recursive { RECURSIVE_CALL, END } - // INSERT - with the insert method - // REMOVE_X - with the remove method when current node had X null children - enum class OpType { INSERT, REMOVE_0, REMOVE_1, REMOVE_2 } + /** + * @property INSERT called from the insert method + * @property REMOVE_0 called from the remove method when current node had 0 null children + * @property REMOVE_1 called from the remove method when current node had 1 null children + * @property REMOVE_2 called from the remove method when current node had 2 null children + */ + enum class OperationType { INSERT, REMOVE_0, REMOVE_1, REMOVE_2 } } + /** + * @return remove type from [BalanceCase.OperationType] based on the [nullChildrenCount] + * + * @throws IllegalArgumentException if [nullChildrenCount] < 0 or > 2 + */ protected fun getBalanceRemoveType(nullChildrenCount: Int): BalanceCase.OperationType { return when (nullChildrenCount) { - 2 -> BalanceCase.OpType.REMOVE_2 - 1 -> BalanceCase.OpType.REMOVE_1 - 0 -> BalanceCase.OpType.REMOVE_0 + 2 -> BalanceCase.OperationType.REMOVE_2 + 1 -> BalanceCase.OperationType.REMOVE_1 + 0 -> BalanceCase.OperationType.REMOVE_0 else -> throw IllegalArgumentException("Expected number was <= 2, because in a binary tree a node can have no more than two children") } } - protected fun getDirectionChangedChild(curNode: NODE_T?, obj: T): BalanceCase.ChangedChild { + /** + * @return what child of [curNode] should have [element] + */ + protected fun getDirectionChangedChild(curNode: NODE_T?, element: T): BalanceCase.ChangedChild { return if (curNode == null) { BalanceCase.ChangedChild.ROOT - } else if (obj < curNode.element) { + } else if (element < curNode.element) { BalanceCase.ChangedChild.LEFT } else { BalanceCase.ChangedChild.RIGHT } } - // curNode - this is parent of changed node - // if curNode == null -> changed node = root + /** + * This method automatic called after inserting or removing element from tree + * + * @param curNode - this is parent of changed node, if curNode is null => changed node is root + * @property changedChild what child was changed + * @property operationType from what method was called + * @property recursive was call recursive or last + */ protected abstract fun balance( curNode: NODE_T?, changedChild: BalanceCase.ChangedChild, - operationType: BalanceCase.OpType, + operationType: BalanceCase.OperationType, recursive: BalanceCase.Recursive ) + /** + * Insert [newNode] into the subtree of the [curNode]. And after call [balance] with right arguments. + * + * @return the parent of the inserted node, + * null if node with the same element already in tree or if inserted node is root + */ override fun insertNode(curNode: NODE_T?, newNode: NODE_T): NODE_T? { val parNode = super.insertNode(curNode, newNode) - if (curNode != null) { // STTK: maybe if cur_node = root, and root = null + if (curNode != null) { if (curNode === parNode) { balance( curNode, getDirectionChangedChild(curNode, newNode.element), - BalanceCase.OpType.INSERT, + BalanceCase.OperationType.INSERT, BalanceCase.Recursive.END ) } else { balance( curNode, getDirectionChangedChild(curNode, newNode.element), - BalanceCase.OpType.INSERT, + BalanceCase.OperationType.INSERT, BalanceCase.Recursive.RECURSIVE_CALL ) if (curNode === root) { balance( null, BalanceCase.ChangedChild.ROOT, - BalanceCase.OpType.INSERT, + BalanceCase.OperationType.INSERT, BalanceCase.Recursive.RECURSIVE_CALL ) } @@ -78,6 +117,12 @@ abstract class TemplateBalanceBSTree, NODE_T : TemplateNode> : TemplateBalanceBSTree>() { override fun balance( curNode: AVLNode?, changedChild: BalanceCase.ChangedChild, - operationType: BalanceCase.OpType, + operationType: BalanceCase.OperationType, recursive: BalanceCase.Recursive ) { - if (operationType == BalanceCase.OpType.REMOVE_0) return + if (operationType == BalanceCase.OperationType.REMOVE_0) return if (curNode == null) { root?.let { balanceNode(it, curNode) } return diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index ff5b833b..30140fa6 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -37,26 +37,26 @@ class RBTree> : TemplateBalanceBSTree>() { override fun balance( curNode: RBNode?, changedChild: BalanceCase.ChangedChild, - operationType: BalanceCase.OpType, + operationType: BalanceCase.OperationType, recursive: BalanceCase.Recursive ) { if (recursive == BalanceCase.Recursive.END) { if (curNode != null) { when (operationType) { - BalanceCase.OpType.INSERT -> { // curNode is parent Node of inserted Node + BalanceCase.OperationType.INSERT -> { // curNode is parent Node of inserted Node balanceInsert(curNode) } - BalanceCase.OpType.REMOVE_0 -> { + BalanceCase.OperationType.REMOVE_0 -> { // does nothing } - BalanceCase.OpType.REMOVE_1 -> { + BalanceCase.OperationType.REMOVE_1 -> { balanceRemove1(curNode, changedChild) } - BalanceCase.OpType.REMOVE_2 -> { + BalanceCase.OperationType.REMOVE_2 -> { balanceRemove2(curNode, changedChild) } } From 204c0dba5f0dd9909e1f5b17bfa0e7dcb3e930f1 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 22:26:45 +0300 Subject: [PATCH 278/296] fix(Neo4jIO): Change skipped IOException to HandledIOException --- .../org/tree/app/controller/io/Neo4jIO.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt index bb8f3b92..19695903 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/Neo4jIO.kt @@ -13,7 +13,6 @@ import org.tree.binaryTree.KVP import org.tree.binaryTree.RBNode import org.tree.binaryTree.trees.RBTree import java.io.Closeable -import java.io.IOException class Neo4jIO() : Closeable { private var driver: Driver? = null @@ -33,8 +32,8 @@ class Neo4jIO() : Closeable { fun exportRBTree( treeController: TreeController>>, treeName: String = "Tree" - ) { // when we have treeView, fun will be rewritten - val session = driver?.session() ?: throw IOException("Driver is not open") + ) { + val session = driver?.session() ?: throw HandledIOException("Driver is not open") val root = treeController.tree.root handleTransactionException { session.executeWrite { tx -> @@ -75,8 +74,8 @@ class Neo4jIO() : Closeable { } - fun importRBTree(treeName: String = "Tree"): TreeController>> { // when we have treeView, fun will be rewritten - val session = driver?.session() ?: throw IOException("Driver is not open") + fun importRBTree(treeName: String = "Tree"): TreeController>> { + val session = driver?.session() ?: throw HandledIOException("Driver is not open") val res: TreeController>> = handleTransactionException { session.executeRead { tx -> @@ -91,7 +90,7 @@ class Neo4jIO() : Closeable { } fun removeTree(treeName: String = "Tree") { - val session = driver?.session() ?: throw IOException("Driver is not open") + val session = driver?.session() ?: throw HandledIOException("Driver is not open") handleTransactionException { session.executeWrite { tx -> deleteTree(tx, treeName) @@ -101,7 +100,7 @@ class Neo4jIO() : Closeable { } fun getTreesNames(): MutableList { - val session = driver?.session() ?: throw IOException("Driver is not open") + val session = driver?.session() ?: throw HandledIOException("Driver is not open") val res: MutableList = handleTransactionException { session.executeRead { tx -> val nameRecords = tx.run("MATCH (t: $TREE) RETURN t.name AS name") @@ -133,7 +132,7 @@ class Neo4jIO() : Closeable { val rkey = curNode.right?.element?.key val ext = nodes[curNode] if (ext == null) { - throw IOException("Can't find coordinates for node with key ${curNode.element.key}") + throw HandledIOException("Can't find coordinates for node with key ${curNode.element.key}") } else { tx.run( "CREATE (:$RBNODE:$NEW_NODE {key : \$key, " + @@ -216,7 +215,7 @@ class Neo4jIO() : Closeable { key2nk[key] = NodeAndKeys(node, lkey, rkey) } catch (ex: Uncoercible) { - throw IOException("Invalid nodes label in the database", ex) + throw HandledIOException("Invalid nodes label in the database", ex) } } val nks = key2nk.values.toTypedArray() @@ -238,7 +237,7 @@ class Neo4jIO() : Closeable { } } if (key2nk.values.size != 1) { - throw IOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") + throw HandledIOException("Found ${key2nk.values.size} nodes without parents in database, expected only 1 node") } treeController.tree.root = key2nk.values.first().nd } From 9ebdb0515d8da2eeb80babc175094f6feac8c191 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 21:09:52 +0300 Subject: [PATCH 279/296] fix: Remove unused code --- app/src/main/kotlin/org/tree/app/App.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 6c2c96d4..37b0bdef 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -108,7 +108,6 @@ fun main() = application { } } - remember { toTreeRoot() } // will be removed in future MenuBar { Menu("File", mnemonic = 'F') { From 74e5866f54cb2bbd93cbc5ced35b5fe6bfc9043b Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 21:10:19 +0300 Subject: [PATCH 280/296] fix: Remove legacy code --- app/src/main/kotlin/org/tree/app/view/Node.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 7611ecfe..5477c00c 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -22,25 +22,6 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import org.tree.binaryTree.KVP -import org.tree.binaryTree.templates.TemplateNode - -class NodeView, NODE_T>>(nd: NODE_T) { // legacy code - var node: NODE_T = nd - var x: Double = 0.0 - var y: Double = 0.0 - var l: NodeView? = null - var r: NodeView? = null - - init { - node.left?.let { - l = NodeView(it) - } - node.right?.let { - r = NodeView(it) - } - } -} @OptIn(ExperimentalFoundationApi::class) @Composable From b7bc5fb27f96206d0b42796384d32266c6402d41 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 21:38:55 +0300 Subject: [PATCH 281/296] fix: Fix resizable line problem --- app/src/main/kotlin/org/tree/app/App.kt | 11 +++++++---- app/src/main/kotlin/org/tree/app/view/Tree.kt | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 37b0bdef..087f0f94 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -52,12 +52,12 @@ val appDataController = AppDataController() fun main() = application { val icon = painterResource("icon.png") var showExitDialog by remember { mutableStateOf(false) } - + val windowState = rememberWindowState(width = 800.dp, height = 600.dp) Window( onCloseRequest = { showExitDialog = true }, title = "Trees", - state = rememberWindowState(width = 800.dp, height = 600.dp), - icon = icon + state = windowState, + icon = icon, ) { var throwException by remember { mutableStateOf(false) } var exceptionContent by remember { mutableStateOf("Nothing...") } @@ -267,7 +267,10 @@ fun main() = application { .draggable( orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, state = rememberDraggableState { delta -> - widthOfPanel += delta.toInt() + val newWight = widthOfPanel + delta.toInt() + if (windowState.size.width > (newWight + 10).dp) { + widthOfPanel = newWight + } } ) ) diff --git a/app/src/main/kotlin/org/tree/app/view/Tree.kt b/app/src/main/kotlin/org/tree/app/view/Tree.kt index ee41c1b3..2f628389 100644 --- a/app/src/main/kotlin/org/tree/app/view/Tree.kt +++ b/app/src/main/kotlin/org/tree/app/view/Tree.kt @@ -23,9 +23,12 @@ fun , NODE_T>> TreeView( offsetX: MutableState, offsetY: MutableState, ) { - Box(contentAlignment = Alignment.Center, + Box( + contentAlignment = Alignment.Center, modifier = Modifier.background(Color.White, shape = RoundedCornerShape(16.dp)).clipToBounds().fillMaxSize() - .pointerInput(offsetX, offsetY) { + .pointerInput( + offsetX, offsetY + ) { detectDragGestures { change, dragAmount -> change.consume() offsetX.value += dragAmount.x.toInt() From 49b04fd77c8013193c98baaa7afb9f90151737fa Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 22:12:38 +0300 Subject: [PATCH 282/296] fix: Fix colors of tree nodes --- .../main/kotlin/org/tree/app/controller/TreeController.kt | 4 ++-- app/src/main/kotlin/org/tree/app/view/Node.kt | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt index 13f8e47c..2a58322f 100644 --- a/app/src/main/kotlin/org/tree/app/controller/TreeController.kt +++ b/app/src/main/kotlin/org/tree/app/controller/TreeController.kt @@ -84,9 +84,9 @@ class TreeController, NODE_T>>( Color.Red } } else if (curNode is AVLNode<*>) { - Color.Cyan + Color(0xff875bff) } else if (curNode is Node<*>) { - Color.Yellow + Color(0xffffb74b) } else { Color.Gray } diff --git a/app/src/main/kotlin/org/tree/app/view/Node.kt b/app/src/main/kotlin/org/tree/app/view/Node.kt index 5477c00c..ada1e6a3 100644 --- a/app/src/main/kotlin/org/tree/app/view/Node.kt +++ b/app/src/main/kotlin/org/tree/app/view/Node.kt @@ -65,7 +65,13 @@ fun Node( } } ) { - AutoSizeText(fontSize = 16.sp, text = key.toString(), maxLines = 1, softWrap = false) + AutoSizeText( + fontSize = 16.sp, + color = Color.White, + text = key.toString(), + maxLines = 1, + softWrap = false + ) } } } From 700ebb86af91da5c13cf10b49d9bbd665fe7722d Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 22:20:47 +0300 Subject: [PATCH 283/296] fix: Fix typo --- app/src/main/kotlin/org/tree/app/App.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 087f0f94..7713e027 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -267,9 +267,9 @@ fun main() = application { .draggable( orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, state = rememberDraggableState { delta -> - val newWight = widthOfPanel + delta.toInt() - if (windowState.size.width > (newWight + 10).dp) { - widthOfPanel = newWight + val newWidth = widthOfPanel + delta.toInt() + if (windowState.size.width > (newWidth + 10).dp) { + widthOfPanel = newWidth } } ) From 5384ee46f620361ee9d8a25ba3ad8cc4179d85a3 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Tue, 2 May 2023 23:47:14 +0300 Subject: [PATCH 284/296] struct: Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 303d3e7d..5ef92661 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,15 @@ Program for binary tree visualization. --- +![gui-example](https://user-images.githubusercontent.com/66139162/235777850-84e8d881-cbc0-429d-a74e-b9b3cbf388fe.png) ## Features You can: - **Create** 3 different types of search trees: **AVL**, **RedBlack** and **Common binary tree** -- **Save** it with 3 different ways: **Neo4j**, **Plain text** and **SQLite** +- **Edit** trees: **insert** nodes, **move** nodes and **remove** them +- **Save** it with 3 different ways: **Neo4j**, **Json** and **SQLite** - Or **load** your own tree from these "databases" - Use our [library](#Library) to **implement** binary search trees in your own project @@ -25,7 +27,7 @@ You can: ### Build -To build and run this application locally, you'll need Git, Gradle and JDK installed on your computer. From your command +To build and run this application locally, you'll need Git and JDK installed on your computer. From your command line: ```bash From b31fd0744f04d42cbe9fc63c05d0c5b1a2846292 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 22:29:53 +0300 Subject: [PATCH 285/296] refactor(RBTree): Add separators of lines --- binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 30140fa6..1635e1f2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -2,7 +2,9 @@ package org.tree.binaryTree.trees import org.tree.binaryTree.RBNode import org.tree.binaryTree.templates.TemplateBalanceBSTree + // algorithm source: https://www.youtube.com/watch?v=T70nn4EyTrs&ab_channel=%D0%9B%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B9%D0%A4%D0%9F%D0%9C%D0%98 + class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode != null) { From c3a4f5af162fdef098e0d54e41f7ff7270e1aaca Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 22:52:23 +0300 Subject: [PATCH 286/296] refactor(RBTree): Remove separators of lines --- .../org/tree/binaryTree/trees/RBTree.kt | 167 +++++++++--------- 1 file changed, 81 insertions(+), 86 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 1635e1f2..6a07d92f 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -8,12 +8,12 @@ import org.tree.binaryTree.templates.TemplateBalanceBSTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode != null) { - if (obj > curNode.element) { + if (obj > curNode.elem) { if (curNode.right == null) { return curNode } return findParentForNewNode(curNode.right, obj) - } else if (obj < curNode.element) { + } else if (obj < curNode.elem) { if (curNode.left == null) { return curNode } @@ -23,12 +23,12 @@ class RBTree> : TemplateBalanceBSTree>() { return null } - override fun insert(curNode: RBNode?, element: T): RBNode? { - val parentForObj = findParentForNewNode(curNode, element) - val newNode = RBNode(parentForObj, element) + override fun insert(curNode: RBNode?, obj: T): RBNode? { + val parentForObj = findParentForNewNode(curNode, obj) + val newNode = RBNode(parentForObj, obj) if (parentForObj == null) { // in case of root insert | node already exist (nothing will be changed) if (root == null) { - newNode.color = RBNode.Color.BLACK + newNode.col = RBNode.Colour.BLACK } else { return null } @@ -39,26 +39,21 @@ class RBTree> : TemplateBalanceBSTree>() { override fun balance( curNode: RBNode?, changedChild: BalanceCase.ChangedChild, - operationType: BalanceCase.OperationType, + operationType: BalanceCase.OpType, recursive: BalanceCase.Recursive ) { if (recursive == BalanceCase.Recursive.END) { if (curNode != null) { when (operationType) { - BalanceCase.OperationType.INSERT -> { // curNode is parent Node of inserted Node + BalanceCase.OpType.INSERT -> { balanceInsert(curNode) - } - - BalanceCase.OperationType.REMOVE_0 -> { - // does nothing - } - - BalanceCase.OperationType.REMOVE_1 -> { + } // curNode is parent Node of inserted Node + BalanceCase.OpType.REMOVE_0 -> {} // does nothing + BalanceCase.OpType.REMOVE_1 -> { balanceRemove1(curNode, changedChild) - } - BalanceCase.OperationType.REMOVE_2 -> { + BalanceCase.OpType.REMOVE_2 -> { balanceRemove2(curNode, changedChild) } } @@ -71,11 +66,11 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun balanceInsert(parentNode: RBNode) { - if (parentNode.color == RBNode.Color.RED) { + if (parentNode.col == RBNode.Colour.RED) { val grandParent = parentNode.parent if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree val unclePosition: BalancePosition - val uncle = if (parentNode.element < grandParent.element) { + val uncle = if (parentNode.elem < grandParent.elem) { unclePosition = BalancePosition.RIGHT_UNCLE grandParent.right } else { @@ -83,7 +78,7 @@ class RBTree> : TemplateBalanceBSTree>() { grandParent.left } if (uncle != null) { - if (uncle.color == RBNode.Color.RED) { + if (uncle.col == RBNode.Colour.RED) { balanceInsertCaseOfRedUncle(parentNode, grandParent, uncle) } else { balanceInsertCaseOfBLackUncle(parentNode, grandParent, unclePosition) @@ -98,12 +93,12 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceInsertCaseOfRedUncle(parentNode: RBNode, grandParent: RBNode, uncle: RBNode) { val grandGrandParent = grandParent.parent - uncle.color = RBNode.Color.BLACK - parentNode.color = RBNode.Color.BLACK + uncle.col = RBNode.Colour.BLACK + parentNode.col = RBNode.Colour.BLACK if (grandGrandParent != null) { - grandParent.color = RBNode.Color.RED + grandParent.col = RBNode.Colour.RED // https://skr.sh/sJ9LBQU2IGg, when y is curNode - if (grandGrandParent.color == RBNode.Color.RED) { + if (grandGrandParent.col == RBNode.Colour.RED) { balanceInsert(grandGrandParent) } } @@ -116,27 +111,27 @@ class RBTree> : TemplateBalanceBSTree>() { ) { // can be null uncle if (position == BalancePosition.LEFT_UNCLE) { val leftChild = parentNode.left - if (leftChild?.color == RBNode.Color.RED) { + if (leftChild?.col == RBNode.Colour.RED) { rotateRight(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } else { val rightChild = parentNode.right if (rightChild != null) { - parentNode.color = RBNode.Color.BLACK - grandParent.color = RBNode.Color.RED + parentNode.col = RBNode.Colour.BLACK + grandParent.col = RBNode.Colour.RED rotateLeft(grandParent, grandParent.parent) } } } else { val leftChild = parentNode.left - if (leftChild?.color == RBNode.Color.RED) { - parentNode.color = RBNode.Color.BLACK - grandParent.color = RBNode.Color.RED + if (leftChild?.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK + grandParent.col = RBNode.Colour.RED rotateRight(grandParent, grandParent.parent) } else { val rightChild = parentNode.right if (rightChild != null) { - if (rightChild.color == RBNode.Color.RED) { + if (rightChild.col == RBNode.Colour.RED) { rotateLeft(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } @@ -150,20 +145,20 @@ class RBTree> : TemplateBalanceBSTree>() { if (removedChild == BalanceCase.ChangedChild.RIGHT) { val rightChild = parentNode?.right if (rightChild != null) { - rightChild.color = RBNode.Color.BLACK + rightChild.col = RBNode.Colour.BLACK } } else if (removedChild == BalanceCase.ChangedChild.LEFT) { val leftChild = parentNode?.left if (leftChild != null) { - leftChild.color = RBNode.Color.BLACK + leftChild.col = RBNode.Colour.BLACK } } } private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { // balanced remove with 2 null children - if (getColourOfRemovedNode(parentNode) == RBNode.Color.BLACK) { // red => no need to balance - if (parentNode.color == RBNode.Color.RED) { // then other child is black + if (getColourOfRemovedNode(parentNode) == RBNode.Colour.BLACK) { // red => no need to balance + if (parentNode.col == RBNode.Colour.RED) { // then other child is black if (removedChild == BalanceCase.ChangedChild.RIGHT) { balanceRemove2InRightChildWithRedParent(parentNode) } else if (removedChild == BalanceCase.ChangedChild.LEFT) { @@ -184,18 +179,18 @@ class RBTree> : TemplateBalanceBSTree>() { if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild?.color == RBNode.Color.RED) { - otherChild.color = RBNode.Color.RED - parentNode.color = RBNode.Color.BLACK - leftChildOfOtherChild.color = RBNode.Color.BLACK + if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK + leftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(parentNode, parentNode.parent) - } else if (rightChildOfOtherChild?.color == RBNode.Color.RED) { - parentNode.color = RBNode.Color.BLACK + } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) } else { - otherChild.color = RBNode.Color.RED - parentNode.color = RBNode.Color.BLACK + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK } } } @@ -205,18 +200,18 @@ class RBTree> : TemplateBalanceBSTree>() { if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild?.color == RBNode.Color.RED) { - parentNode.color = RBNode.Color.BLACK + if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { + parentNode.col = RBNode.Colour.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - } else if (rightChildOfOtherChild?.color == RBNode.Color.RED) { - otherChild.color = RBNode.Color.RED - parentNode.color = RBNode.Color.BLACK - rightChildOfOtherChild.color = RBNode.Color.BLACK + } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK + rightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(parentNode, parentNode.parent) } else { - otherChild.color = RBNode.Color.RED - parentNode.color = RBNode.Color.BLACK + otherChild.col = RBNode.Colour.RED + parentNode.col = RBNode.Colour.BLACK } } } @@ -224,7 +219,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InRightChildWithBlackParent(parentNode: RBNode) { val otherChild = parentNode.left if (otherChild != null) { - if (otherChild.color == RBNode.Color.RED) { + if (otherChild.col == RBNode.Colour.RED) { balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) } else { balanceRemove2InRightChildWithBlackParentBlackOtherChild(parentNode, otherChild) @@ -235,7 +230,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InLeftChildWithBlackParent(parentNode: RBNode) { val otherChild = parentNode.right if (otherChild != null) { - if (otherChild.color == RBNode.Color.RED) { + if (otherChild.col == RBNode.Colour.RED) { balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) } else { balanceRemove2InLeftChildWithBlackParentBlackOtherChild(parentNode, otherChild) @@ -253,19 +248,19 @@ class RBTree> : TemplateBalanceBSTree>() { rightChildOfOtherChild.left // https://skr.sh/sJD6DQ2ML5B val rightChildOfRightChildOfOtherChild = rightChildOfOtherChild.right - if (leftChildOfRightChildOfOtherChild?.color == RBNode.Color.RED) { - leftChildOfRightChildOfOtherChild.color = RBNode.Color.BLACK + if (leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { + leftChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) - } else if (rightChildOfRightChildOfOtherChild?.color == RBNode.Color.RED) { - rightChildOfOtherChild.color = RBNode.Color.RED - rightChildOfRightChildOfOtherChild.color = RBNode.Color.BLACK + } else if (rightChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { + rightChildOfOtherChild.col = RBNode.Colour.RED + rightChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(rightChildOfOtherChild, otherChild) balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) // case: leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED } else { - otherChild.color = RBNode.Color.BLACK - rightChildOfOtherChild.color = RBNode.Color.RED + otherChild.col = RBNode.Colour.BLACK + rightChildOfOtherChild.col = RBNode.Colour.RED rotateRight(parentNode, parentNode.parent) } } @@ -281,19 +276,19 @@ class RBTree> : TemplateBalanceBSTree>() { leftChildOfOtherChild.right // https://skr.sh/sJD6DQ2ML5B (inverted) val leftChildOfLeftChildOfOtherChild = leftChildOfOtherChild.left - if (rightChildOfLeftChildOfOtherChild?.color == RBNode.Color.RED) { - rightChildOfLeftChildOfOtherChild.color = RBNode.Color.BLACK + if (rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { + rightChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - } else if (leftChildOfLeftChildOfOtherChild?.color == RBNode.Color.RED) { - leftChildOfOtherChild.color = RBNode.Color.RED - leftChildOfLeftChildOfOtherChild.color = RBNode.Color.BLACK + } else if (leftChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { + leftChildOfOtherChild.col = RBNode.Colour.RED + leftChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(leftChildOfOtherChild, otherChild) balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) // case: rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED } else { - otherChild.color = RBNode.Color.BLACK - leftChildOfOtherChild.color = RBNode.Color.RED + otherChild.col = RBNode.Colour.BLACK + leftChildOfOtherChild.col = RBNode.Colour.RED rotateLeft(parentNode, parentNode.parent) } } @@ -305,8 +300,8 @@ class RBTree> : TemplateBalanceBSTree>() { ) { val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.color == RBNode.Color.RED) { - rightChildOfOtherChild.color = RBNode.Color.BLACK + if (rightChildOfOtherChild.col == RBNode.Colour.RED) { + rightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) return @@ -314,16 +309,16 @@ class RBTree> : TemplateBalanceBSTree>() { } val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.color == RBNode.Color.RED) { - leftChildOfOtherChild.color = RBNode.Color.BLACK + if (leftChildOfOtherChild.col == RBNode.Colour.RED) { + leftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(parentNode, parentNode.parent) return } } - otherChild.color = RBNode.Color.RED + otherChild.col = RBNode.Colour.RED val grandParent = parentNode.parent if (grandParent != null) { - if (parentNode.element < grandParent.element) { + if (parentNode.elem < grandParent.elem) { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) @@ -339,8 +334,8 @@ class RBTree> : TemplateBalanceBSTree>() { ){ val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.color == RBNode.Color.RED) { - leftChildOfOtherChild.color = RBNode.Color.BLACK + if (leftChildOfOtherChild.col == RBNode.Colour.RED) { + leftChildOfOtherChild.col = RBNode.Colour.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) return @@ -348,16 +343,16 @@ class RBTree> : TemplateBalanceBSTree>() { } val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.color == RBNode.Color.RED) { - rightChildOfOtherChild.color = RBNode.Color.BLACK + if (rightChildOfOtherChild.col == RBNode.Colour.RED) { + rightChildOfOtherChild.col = RBNode.Colour.BLACK rotateLeft(parentNode, parentNode.parent) return } } - otherChild.color = RBNode.Color.RED + otherChild.col = RBNode.Colour.RED val grandParent = parentNode.parent if (grandParent != null) { - if (parentNode.element < grandParent.element) { + if (parentNode.elem < grandParent.elem) { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) @@ -366,18 +361,18 @@ class RBTree> : TemplateBalanceBSTree>() { return } - private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Color { + private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Colour { return if (getBlackHeight(parentNode.left) != getBlackHeight(parentNode.right)) { - RBNode.Color.BLACK + RBNode.Colour.BLACK } else { - RBNode.Color.RED + RBNode.Colour.RED } } private fun getBlackHeight(curNode: RBNode?, blackHeight: Int = 0): Int { var blackHeightVar = blackHeight if (curNode != null) { - if (curNode.color == RBNode.Color.BLACK) { + if (curNode.col == RBNode.Colour.BLACK) { blackHeightVar += 1 } return getBlackHeight(curNode.left, blackHeightVar) @@ -389,7 +384,7 @@ class RBTree> : TemplateBalanceBSTree>() { newNode?.parent = parentNode if (parentNode == null) { if (newNode != null) { - newNode.color = RBNode.Color.BLACK + newNode.col = RBNode.Colour.BLACK } } super.replaceNode(replacedNode, parentNode, newNode) @@ -400,7 +395,7 @@ class RBTree> : TemplateBalanceBSTree>() { curNode.left?.parent = parentNode curNode.left?.right?.parent = curNode if (curNode === root) { - curNode.left?.color = RBNode.Color.BLACK + curNode.left?.col = RBNode.Colour.BLACK } super.rotateRight(curNode, parentNode) } @@ -410,7 +405,7 @@ class RBTree> : TemplateBalanceBSTree>() { curNode.right?.parent = parentNode curNode.right?.left?.parent = curNode if (curNode === root) { - curNode.right?.color = RBNode.Color.BLACK + curNode.right?.col = RBNode.Colour.BLACK } super.rotateLeft(curNode, parentNode) } From a55d1faaaf50b227459ca994c2122c70adb7d4d0 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 23:09:07 +0300 Subject: [PATCH 287/296] refactor(RBTree): Simplify checks for null --- .../org/tree/binaryTree/trees/RBTree.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 6a07d92f..df3e586c 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -141,22 +141,22 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun balanceRemove1(parentNode: RBNode?, removedChild: BalanceCase.ChangedChild) { - // balanced remove with 1 non-null child - if (removedChild == BalanceCase.ChangedChild.RIGHT) { - val rightChild = parentNode?.right - if (rightChild != null) { - rightChild.col = RBNode.Colour.BLACK + /** Balanced remove with 1 non-null child */ + when (removedChild) { + BalanceCase.ChangedChild.RIGHT -> { + parentNode?.right?.run { col = RBNode.Colour.BLACK } } - } else if (removedChild == BalanceCase.ChangedChild.LEFT) { - val leftChild = parentNode?.left - if (leftChild != null) { - leftChild.col = RBNode.Colour.BLACK + + BalanceCase.ChangedChild.LEFT -> { + parentNode?.left?.run { col = RBNode.Colour.BLACK } } + + else -> {} } } private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { - // balanced remove with 2 null children + /** Balanced remove with 2 null children */ if (getColourOfRemovedNode(parentNode) == RBNode.Colour.BLACK) { // red => no need to balance if (parentNode.col == RBNode.Colour.RED) { // then other child is black if (removedChild == BalanceCase.ChangedChild.RIGHT) { @@ -331,7 +331,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InLeftChildWithBlackParentBlackOtherChild( parentNode: RBNode, otherChild: RBNode - ){ + ) { val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { if (leftChildOfOtherChild.col == RBNode.Colour.RED) { From 766473789b1a17b07d5342d796b1ef3241395a19 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 23:12:26 +0300 Subject: [PATCH 288/296] refactor(RBTree): Remove extra `return`'s --- binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index df3e586c..ce2902a2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -324,7 +324,6 @@ class RBTree> : TemplateBalanceBSTree>() { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) } } - return } @@ -358,7 +357,6 @@ class RBTree> : TemplateBalanceBSTree>() { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) } } - return } private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Colour { From aa87335900c93e6851a1300fd6bc2e46a932a517 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 2 May 2023 23:22:48 +0300 Subject: [PATCH 289/296] refactor(RBTree): Optimize `getBlackHeight` method --- .../kotlin/org/tree/binaryTree/trees/RBTree.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index ce2902a2..ec4c6d92 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -368,14 +368,17 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun getBlackHeight(curNode: RBNode?, blackHeight: Int = 0): Int { - var blackHeightVar = blackHeight if (curNode != null) { - if (curNode.col == RBNode.Colour.BLACK) { - blackHeightVar += 1 - } - return getBlackHeight(curNode.left, blackHeightVar) + return getBlackHeight( + curNode.left, + if (curNode.col == RBNode.Colour.BLACK) { + blackHeight + 1 + } else { + blackHeight + } + ) } - return blackHeightVar + return blackHeight } override fun replaceNode(replacedNode: RBNode, parentNode: RBNode?, newNode: RBNode?) { From 1cdbc40d5f029ca4f4296a5d4c9e971a8c05af1c Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 May 2023 00:00:02 +0300 Subject: [PATCH 290/296] feat(README): Update Readme --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5ef92661..3788afa6 100644 --- a/README.md +++ b/README.md @@ -99,12 +99,13 @@ At SQLite database we store BinSearchTree. There are 2 types of objects with the - `x` *int* - node position at ui - `y` *int* - node position at ui -We have functions: - -- `exportTree(TreeController, file)` - writes information stored in TreeController object to a `file` in preorder order. -- `importTree(file)` - reads the tree stored in the `file` and returns TreeController object. **Nodes must be stored in preorder order (check root -> then check left child -> then check right child).** -If there are some extra tree nodes, that can't fit in the tree, then `importTree` will throw `HandledIOException`. +We have methods: +- `exportTree(TreeController, file.sqlite)` - writes information stored in TreeController object to a `file` in preorder + order. +- `importTree(file.sqlite)` - reads the tree stored in the `file` and returns TreeController object. **Nodes must be + stored in preorder order (check root -> then check left child -> then check right child).** + If there are some extra tree nodes, that can't fit in the tree, then `importTree` will throw `HandledIOException`. ### Neo4j @@ -208,7 +209,7 @@ class MyCoolTree> : TemplateBalanceBSTree>() { override fun balance( curNode: CoolNode?, changedChild: BalanceCase.ChangedChild, - operationType: BalanceCase.OpType, + operationType: BalanceCase.OperationType, recursive: BalanceCase.Recursive ) { when (changedChild) { From c6f48cfa9c93b66557f2d9b7baee7aa232dd91be Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 3 May 2023 00:31:44 +0300 Subject: [PATCH 291/296] fix(RBTree): Update names --- .../org/tree/binaryTree/trees/RBTree.kt | 160 +++++++++--------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index ec4c6d92..676debc2 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -8,12 +8,12 @@ import org.tree.binaryTree.templates.TemplateBalanceBSTree class RBTree> : TemplateBalanceBSTree>() { private fun findParentForNewNode(curNode: RBNode?, obj: T): RBNode? { if (curNode != null) { - if (obj > curNode.elem) { + if (obj > curNode.element) { if (curNode.right == null) { return curNode } return findParentForNewNode(curNode.right, obj) - } else if (obj < curNode.elem) { + } else if (obj < curNode.element) { if (curNode.left == null) { return curNode } @@ -23,12 +23,12 @@ class RBTree> : TemplateBalanceBSTree>() { return null } - override fun insert(curNode: RBNode?, obj: T): RBNode? { - val parentForObj = findParentForNewNode(curNode, obj) - val newNode = RBNode(parentForObj, obj) + override fun insert(curNode: RBNode?, element: T): RBNode? { + val parentForObj = findParentForNewNode(curNode, element) + val newNode = RBNode(parentForObj, element) if (parentForObj == null) { // in case of root insert | node already exist (nothing will be changed) if (root == null) { - newNode.col = RBNode.Colour.BLACK + newNode.color = RBNode.Color.BLACK } else { return null } @@ -39,21 +39,21 @@ class RBTree> : TemplateBalanceBSTree>() { override fun balance( curNode: RBNode?, changedChild: BalanceCase.ChangedChild, - operationType: BalanceCase.OpType, + operationType: BalanceCase.OperationType, recursive: BalanceCase.Recursive ) { if (recursive == BalanceCase.Recursive.END) { if (curNode != null) { when (operationType) { - BalanceCase.OpType.INSERT -> { + BalanceCase.OperationType.INSERT -> { balanceInsert(curNode) } // curNode is parent Node of inserted Node - BalanceCase.OpType.REMOVE_0 -> {} // does nothing - BalanceCase.OpType.REMOVE_1 -> { + BalanceCase.OperationType.REMOVE_0 -> {} // does nothing + BalanceCase.OperationType.REMOVE_1 -> { balanceRemove1(curNode, changedChild) } - BalanceCase.OpType.REMOVE_2 -> { + BalanceCase.OperationType.REMOVE_2 -> { balanceRemove2(curNode, changedChild) } } @@ -66,11 +66,11 @@ class RBTree> : TemplateBalanceBSTree>() { } private fun balanceInsert(parentNode: RBNode) { - if (parentNode.col == RBNode.Colour.RED) { + if (parentNode.color == RBNode.Color.RED) { val grandParent = parentNode.parent if (grandParent != null) { // in case when grandparent is null, there is no need to balance a tree val unclePosition: BalancePosition - val uncle = if (parentNode.elem < grandParent.elem) { + val uncle = if (parentNode.element < grandParent.element) { unclePosition = BalancePosition.RIGHT_UNCLE grandParent.right } else { @@ -78,7 +78,7 @@ class RBTree> : TemplateBalanceBSTree>() { grandParent.left } if (uncle != null) { - if (uncle.col == RBNode.Colour.RED) { + if (uncle.color == RBNode.Color.RED) { balanceInsertCaseOfRedUncle(parentNode, grandParent, uncle) } else { balanceInsertCaseOfBLackUncle(parentNode, grandParent, unclePosition) @@ -93,12 +93,12 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceInsertCaseOfRedUncle(parentNode: RBNode, grandParent: RBNode, uncle: RBNode) { val grandGrandParent = grandParent.parent - uncle.col = RBNode.Colour.BLACK - parentNode.col = RBNode.Colour.BLACK + uncle.color = RBNode.Color.BLACK + parentNode.color = RBNode.Color.BLACK if (grandGrandParent != null) { - grandParent.col = RBNode.Colour.RED + grandParent.color = RBNode.Color.RED // https://skr.sh/sJ9LBQU2IGg, when y is curNode - if (grandGrandParent.col == RBNode.Colour.RED) { + if (grandGrandParent.color == RBNode.Color.RED) { balanceInsert(grandGrandParent) } } @@ -111,27 +111,27 @@ class RBTree> : TemplateBalanceBSTree>() { ) { // can be null uncle if (position == BalancePosition.LEFT_UNCLE) { val leftChild = parentNode.left - if (leftChild?.col == RBNode.Colour.RED) { + if (leftChild?.color == RBNode.Color.RED) { rotateRight(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } else { val rightChild = parentNode.right if (rightChild != null) { - parentNode.col = RBNode.Colour.BLACK - grandParent.col = RBNode.Colour.RED + parentNode.color = RBNode.Color.BLACK + grandParent.color = RBNode.Color.RED rotateLeft(grandParent, grandParent.parent) } } } else { val leftChild = parentNode.left - if (leftChild?.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK - grandParent.col = RBNode.Colour.RED + if (leftChild?.color == RBNode.Color.RED) { + parentNode.color = RBNode.Color.BLACK + grandParent.color = RBNode.Color.RED rotateRight(grandParent, grandParent.parent) } else { val rightChild = parentNode.right if (rightChild != null) { - if (rightChild.col == RBNode.Colour.RED) { + if (rightChild.color == RBNode.Color.RED) { rotateLeft(parentNode, grandParent) parentNode.parent?.let { balanceInsert(it) } } @@ -144,11 +144,11 @@ class RBTree> : TemplateBalanceBSTree>() { /** Balanced remove with 1 non-null child */ when (removedChild) { BalanceCase.ChangedChild.RIGHT -> { - parentNode?.right?.run { col = RBNode.Colour.BLACK } + parentNode?.right?.run { color = RBNode.Color.BLACK } } BalanceCase.ChangedChild.LEFT -> { - parentNode?.left?.run { col = RBNode.Colour.BLACK } + parentNode?.left?.run { color = RBNode.Color.BLACK } } else -> {} @@ -157,8 +157,8 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { /** Balanced remove with 2 null children */ - if (getColourOfRemovedNode(parentNode) == RBNode.Colour.BLACK) { // red => no need to balance - if (parentNode.col == RBNode.Colour.RED) { // then other child is black + if (getColourOfRemovedNode(parentNode) == RBNode.Color.BLACK) { // red => no need to balance + if (parentNode.color == RBNode.Color.RED) { // then other child is black if (removedChild == BalanceCase.ChangedChild.RIGHT) { balanceRemove2InRightChildWithRedParent(parentNode) } else if (removedChild == BalanceCase.ChangedChild.LEFT) { @@ -179,18 +179,18 @@ class RBTree> : TemplateBalanceBSTree>() { if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK - leftChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild?.color == RBNode.Color.RED) { + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK + leftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(parentNode, parentNode.parent) - } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK + } else if (rightChildOfOtherChild?.color == RBNode.Color.RED) { + parentNode.color = RBNode.Color.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) } else { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK } } } @@ -200,18 +200,18 @@ class RBTree> : TemplateBalanceBSTree>() { if (otherChild != null) { val leftChildOfOtherChild = otherChild.left val rightChildOfOtherChild = otherChild.right - if (leftChildOfOtherChild?.col == RBNode.Colour.RED) { - parentNode.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild?.color == RBNode.Color.RED) { + parentNode.color = RBNode.Color.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - } else if (rightChildOfOtherChild?.col == RBNode.Colour.RED) { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK - rightChildOfOtherChild.col = RBNode.Colour.BLACK + } else if (rightChildOfOtherChild?.color == RBNode.Color.RED) { + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK + rightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(parentNode, parentNode.parent) } else { - otherChild.col = RBNode.Colour.RED - parentNode.col = RBNode.Colour.BLACK + otherChild.color = RBNode.Color.RED + parentNode.color = RBNode.Color.BLACK } } } @@ -219,7 +219,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InRightChildWithBlackParent(parentNode: RBNode) { val otherChild = parentNode.left if (otherChild != null) { - if (otherChild.col == RBNode.Colour.RED) { + if (otherChild.color == RBNode.Color.RED) { balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) } else { balanceRemove2InRightChildWithBlackParentBlackOtherChild(parentNode, otherChild) @@ -230,7 +230,7 @@ class RBTree> : TemplateBalanceBSTree>() { private fun balanceRemove2InLeftChildWithBlackParent(parentNode: RBNode) { val otherChild = parentNode.right if (otherChild != null) { - if (otherChild.col == RBNode.Colour.RED) { + if (otherChild.color == RBNode.Color.RED) { balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) } else { balanceRemove2InLeftChildWithBlackParentBlackOtherChild(parentNode, otherChild) @@ -248,19 +248,19 @@ class RBTree> : TemplateBalanceBSTree>() { rightChildOfOtherChild.left // https://skr.sh/sJD6DQ2ML5B val rightChildOfRightChildOfOtherChild = rightChildOfOtherChild.right - if (leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { - leftChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfRightChildOfOtherChild?.color == RBNode.Color.RED) { + leftChildOfRightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) - } else if (rightChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED) { - rightChildOfOtherChild.col = RBNode.Colour.RED - rightChildOfRightChildOfOtherChild.col = RBNode.Colour.BLACK + } else if (rightChildOfRightChildOfOtherChild?.color == RBNode.Color.RED) { + rightChildOfOtherChild.color = RBNode.Color.RED + rightChildOfRightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(rightChildOfOtherChild, otherChild) balanceRemove2InRightChildWithBlackParentRedOtherChild(parentNode, otherChild) // case: leftChildOfRightChildOfOtherChild?.col == RBNode.Colour.RED } else { - otherChild.col = RBNode.Colour.BLACK - rightChildOfOtherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.BLACK + rightChildOfOtherChild.color = RBNode.Color.RED rotateRight(parentNode, parentNode.parent) } } @@ -276,19 +276,19 @@ class RBTree> : TemplateBalanceBSTree>() { leftChildOfOtherChild.right // https://skr.sh/sJD6DQ2ML5B (inverted) val leftChildOfLeftChildOfOtherChild = leftChildOfOtherChild.left - if (rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { - rightChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + if (rightChildOfLeftChildOfOtherChild?.color == RBNode.Color.RED) { + rightChildOfLeftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) - } else if (leftChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED) { - leftChildOfOtherChild.col = RBNode.Colour.RED - leftChildOfLeftChildOfOtherChild.col = RBNode.Colour.BLACK + } else if (leftChildOfLeftChildOfOtherChild?.color == RBNode.Color.RED) { + leftChildOfOtherChild.color = RBNode.Color.RED + leftChildOfLeftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(leftChildOfOtherChild, otherChild) balanceRemove2InLeftChildWithBlackParentRedOtherChild(parentNode, otherChild) // case: rightChildOfLeftChildOfOtherChild?.col == RBNode.Colour.RED } else { - otherChild.col = RBNode.Colour.BLACK - leftChildOfOtherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.BLACK + leftChildOfOtherChild.color = RBNode.Color.RED rotateLeft(parentNode, parentNode.parent) } } @@ -300,8 +300,8 @@ class RBTree> : TemplateBalanceBSTree>() { ) { val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.col == RBNode.Colour.RED) { - rightChildOfOtherChild.col = RBNode.Colour.BLACK + if (rightChildOfOtherChild.color == RBNode.Color.RED) { + rightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(otherChild, parentNode) rotateRight(parentNode, parentNode.parent) return @@ -309,16 +309,16 @@ class RBTree> : TemplateBalanceBSTree>() { } val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.col == RBNode.Colour.RED) { - leftChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild.color == RBNode.Color.RED) { + leftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(parentNode, parentNode.parent) return } } - otherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.RED val grandParent = parentNode.parent if (grandParent != null) { - if (parentNode.elem < grandParent.elem) { + if (parentNode.element < grandParent.element) { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) @@ -333,8 +333,8 @@ class RBTree> : TemplateBalanceBSTree>() { ) { val leftChildOfOtherChild = otherChild.left if (leftChildOfOtherChild != null) { - if (leftChildOfOtherChild.col == RBNode.Colour.RED) { - leftChildOfOtherChild.col = RBNode.Colour.BLACK + if (leftChildOfOtherChild.color == RBNode.Color.RED) { + leftChildOfOtherChild.color = RBNode.Color.BLACK rotateRight(otherChild, parentNode) rotateLeft(parentNode, parentNode.parent) return @@ -342,16 +342,16 @@ class RBTree> : TemplateBalanceBSTree>() { } val rightChildOfOtherChild = otherChild.right if (rightChildOfOtherChild != null) { - if (rightChildOfOtherChild.col == RBNode.Colour.RED) { - rightChildOfOtherChild.col = RBNode.Colour.BLACK + if (rightChildOfOtherChild.color == RBNode.Color.RED) { + rightChildOfOtherChild.color = RBNode.Color.BLACK rotateLeft(parentNode, parentNode.parent) return } } - otherChild.col = RBNode.Colour.RED + otherChild.color = RBNode.Color.RED val grandParent = parentNode.parent if (grandParent != null) { - if (parentNode.elem < grandParent.elem) { + if (parentNode.element < grandParent.element) { balanceRemove2(grandParent, BalanceCase.ChangedChild.LEFT) } else { balanceRemove2(grandParent, BalanceCase.ChangedChild.RIGHT) @@ -359,11 +359,11 @@ class RBTree> : TemplateBalanceBSTree>() { } } - private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Colour { + private fun getColourOfRemovedNode(parentNode: RBNode): RBNode.Color { return if (getBlackHeight(parentNode.left) != getBlackHeight(parentNode.right)) { - RBNode.Colour.BLACK + RBNode.Color.BLACK } else { - RBNode.Colour.RED + RBNode.Color.RED } } @@ -371,7 +371,7 @@ class RBTree> : TemplateBalanceBSTree>() { if (curNode != null) { return getBlackHeight( curNode.left, - if (curNode.col == RBNode.Colour.BLACK) { + if (curNode.color == RBNode.Color.BLACK) { blackHeight + 1 } else { blackHeight @@ -385,7 +385,7 @@ class RBTree> : TemplateBalanceBSTree>() { newNode?.parent = parentNode if (parentNode == null) { if (newNode != null) { - newNode.col = RBNode.Colour.BLACK + newNode.color = RBNode.Color.BLACK } } super.replaceNode(replacedNode, parentNode, newNode) @@ -396,7 +396,7 @@ class RBTree> : TemplateBalanceBSTree>() { curNode.left?.parent = parentNode curNode.left?.right?.parent = curNode if (curNode === root) { - curNode.left?.col = RBNode.Colour.BLACK + curNode.left?.color = RBNode.Color.BLACK } super.rotateRight(curNode, parentNode) } @@ -406,7 +406,7 @@ class RBTree> : TemplateBalanceBSTree>() { curNode.right?.parent = parentNode curNode.right?.left?.parent = curNode if (curNode === root) { - curNode.right?.col = RBNode.Colour.BLACK + curNode.right?.color = RBNode.Color.BLACK } super.rotateLeft(curNode, parentNode) } From ed0b0bf6ebe0d845de7e6897f2f5e9b37e0a8965 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 3 May 2023 00:34:10 +0300 Subject: [PATCH 292/296] refactor(RBTree): Move docs comments --- .../src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt index 676debc2..d8d7d17c 100644 --- a/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt +++ b/binaryTree/src/main/kotlin/org/tree/binaryTree/trees/RBTree.kt @@ -140,8 +140,8 @@ class RBTree> : TemplateBalanceBSTree>() { } } + /** Balanced remove with 1 non-null child */ private fun balanceRemove1(parentNode: RBNode?, removedChild: BalanceCase.ChangedChild) { - /** Balanced remove with 1 non-null child */ when (removedChild) { BalanceCase.ChangedChild.RIGHT -> { parentNode?.right?.run { color = RBNode.Color.BLACK } @@ -155,8 +155,8 @@ class RBTree> : TemplateBalanceBSTree>() { } } + /** Balanced remove with 2 null children */ private fun balanceRemove2(parentNode: RBNode, removedChild: BalanceCase.ChangedChild) { - /** Balanced remove with 2 null children */ if (getColourOfRemovedNode(parentNode) == RBNode.Color.BLACK) { // red => no need to balance if (parentNode.color == RBNode.Color.RED) { // then other child is black if (removedChild == BalanceCase.ChangedChild.RIGHT) { From 4d0b2582df0d134def5ebf7162e2b8655b7815a6 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 3 May 2023 00:58:54 +0300 Subject: [PATCH 293/296] fix: Fix resizable line problem again --- app/src/main/kotlin/org/tree/app/App.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 7713e027..73eb192e 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -268,7 +268,7 @@ fun main() = application { orientation = androidx.compose.foundation.gestures.Orientation.Horizontal, state = rememberDraggableState { delta -> val newWidth = widthOfPanel + delta.toInt() - if (windowState.size.width > (newWidth + 10).dp) { + if ((windowState.size.width > (newWidth + 10).dp) && (newWidth > 0)) { widthOfPanel = newWidth } } From ad22d508f2c241b18639440a9d9029bed247c7ee Mon Sep 17 00:00:00 2001 From: David Date: Wed, 3 May 2023 01:13:54 +0300 Subject: [PATCH 294/296] fix(README): Fix bug with extension of file --- .../kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt index 69a234e1..0c180c00 100644 --- a/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt +++ b/app/src/main/kotlin/org/tree/app/view/dialogs/io/FilesIOHandler.kt @@ -58,9 +58,9 @@ enum class Mode { fun selectFile(fileExtension: String, mode: Mode): String? { val fd = if (mode == Mode.IMPORT) { - FileDialog(ComposeWindow(), "Choose .sqlite file to import", FileDialog.LOAD) + FileDialog(ComposeWindow(), "Choose .$fileExtension file to import", FileDialog.LOAD) } else { - FileDialog(ComposeWindow(), "Choose .sqlite file to export", FileDialog.SAVE) + FileDialog(ComposeWindow(), "Choose .$fileExtension file to export", FileDialog.SAVE) } fd.directory = "C:\\" fd.file = "*.$fileExtension" From 009e06ece7458dc4ba65c8e9416e98f8b8216093 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 3 May 2023 01:17:32 +0300 Subject: [PATCH 295/296] fix(JsonIO): Add exception catch --- .../org/tree/app/controller/io/JsonIO.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt index 308abdfe..69f1570a 100644 --- a/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt +++ b/app/src/main/kotlin/org/tree/app/controller/io/JsonIO.kt @@ -1,16 +1,17 @@ package org.tree.app.controller.io +import NodeExtension +import TreeController +import androidx.compose.runtime.mutableStateOf import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import TreeController import org.tree.binaryTree.AVLNode import org.tree.binaryTree.KVP -import java.io.File -import NodeExtension -import androidx.compose.runtime.mutableStateOf import org.tree.binaryTree.trees.AVLTree +import java.io.File import java.io.FileNotFoundException import java.nio.file.Files @@ -76,7 +77,14 @@ class JsonIO { } val treeController = TreeController(AVLTree()) - val jsonTree = Json.decodeFromString(json) + val jsonTree = try { + Json.decodeFromString(json) + } catch (ex: IllegalArgumentException) { + throw HandledIOException("This file is not json tree format", ex) + } catch (ex: SerializationException) { + throw HandledIOException("Something go wrong during tree import", ex) + } + treeController.tree.root = jsonTree.root?.deserialize(treeController) return treeController From 7306a6b1261efb82eb81497674afd8e424c47ba8 Mon Sep 17 00:00:00 2001 From: Efremov Alexey Date: Wed, 3 May 2023 01:57:59 +0300 Subject: [PATCH 296/296] fix: Add exception catching on export --- app/src/main/kotlin/org/tree/app/App.kt | 40 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/org/tree/app/App.kt b/app/src/main/kotlin/org/tree/app/App.kt index 73eb192e..dc8d9383 100644 --- a/app/src/main/kotlin/org/tree/app/App.kt +++ b/app/src/main/kotlin/org/tree/app/App.kt @@ -145,10 +145,14 @@ fun main() = application { Item( "Bin Search Tree", onClick = { - @Suppress("UNCHECKED_CAST") - exportBST( - treeController as TreeController>> - ) + handleIOException(onCatch = { ex -> + exceptionContent = "Failed to import tree from file: ${ex.message}" + throwException = true + }) { + @Suppress("UNCHECKED_CAST") exportBST( + treeController as TreeController>> + ) + } }, enabled = (treeController.nodeType() is Node<*>?) ) @@ -160,10 +164,14 @@ fun main() = application { Item( "AVL Tree", onClick = { - @Suppress("UNCHECKED_CAST") - exportAVLT( - treeController as TreeController>> - ) + handleIOException(onCatch = { ex -> + exceptionContent = "Failed to import tree from file: ${ex.message}" + throwException = true + }) { + @Suppress("UNCHECKED_CAST") exportAVLT( + treeController as TreeController>> + ) + } }, enabled = (treeController.nodeType() is AVLNode<*>?) ) @@ -316,16 +324,24 @@ fun main() = application { ) { if (treeController.nodeType() is Node<*>?) { Button(onClick = { - @Suppress("UNCHECKED_CAST") - exportBST(treeController as TreeController>>) + handleIOException(onCatch = { ex -> + exceptionContent = "Failed to import tree from file: ${ex.message}" + throwException = true + }) { + @Suppress("UNCHECKED_CAST") exportBST(treeController as TreeController>>) + } }) { Text("Save as Bin Search Tree") } } if (treeController.nodeType() is AVLNode<*>?) { Button(onClick = { - @Suppress("UNCHECKED_CAST") - exportAVLT(treeController as TreeController>>) + handleIOException(onCatch = { ex -> + exceptionContent = "Failed to import tree from file: ${ex.message}" + throwException = true + }) { + @Suppress("UNCHECKED_CAST") exportAVLT(treeController as TreeController>>) + } }) { Text("Save as AVL Tree") }