From 3de69519c3ea232c9190883a7c088938753e4f40 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:06:32 +0000 Subject: [PATCH 01/52] Setting up GitHub Classroom Feedback From 5a8edc87c8225edce5343a5310935d6ce0fdbd09 Mon Sep 17 00:00:00 2001 From: Muhammet Sultanov <106592675+VersusXX@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:11:55 +0300 Subject: [PATCH 02/52] Create LICENSE Created and added MIT LICENSE. --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..951ff97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 spbu-coding-2023 + +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 d538a94177272d362060b51434079a32c63fac4c Mon Sep 17 00:00:00 2001 From: VersusXX Date: Fri, 15 Mar 2024 20:30:28 +0300 Subject: [PATCH 03/52] Initialize project Added initial project files. Added README and gitignore --- .gitignore | 42 ++++ LICENSE => LICENSE.txt | 0 README.md | 45 +++++ build.gradle.kts | 21 ++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 234 +++++++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle.kts | 5 + src/main/kotlin/Main.kt | 5 + 11 files changed, 448 insertions(+) create mode 100644 .gitignore rename LICENSE => LICENSE.txt (100%) create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/Main.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1a9996 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +[![MIT License][license-shield]][license-url] + + + +## О проекте + +Добро пожаловать в библиотеку Kotlin Binary Tree! Эта библиотека разработана для предоставления разработчикам +эффективных и гибких реализаций трех различных типов бинарных деревьев: Binary Tree, AVL Tree и Red-Black Tree + +## Создано с использованием + +- Kotlin 1.9.22: Библиотека написана на Kotlin, используя его лаконичный синтаксис и мощные возможности. +- JDK 21: Построено с использованием Java Development Kit версии 21, обеспечивая совместимость с современными + средами Java и используя последние усовершенствования Java. + +## Главные особенности + +- #### Три Варианта Бинарных Деревьев: Выберите из различных типов бинарных деревьев, чтобы удовлетворить свои конкретные требования: + +- Binary Tree: Фундаментальная структура бинарного дерева, предоставляющая простые возможности вставки, удаления и поиска. +- AVL Tree: Самобалансирующееся бинарное дерево поиска, обеспечивающее оптимальную производительность для операций вставки, удаления и поиска. +- Red-Black Tree: Еще одно самобалансирующееся бинарное дерево поиска с логарифмической высотой, обеспечивающее эффективные операции для больших наборов данных. + +- #### Универсальные Операции: Каждая реализация дерева поддерживает основные операции для управления структурами деревьев: + - Поиск: Быстро находите узлы в дереве на основе ключевых значений. + - Вставка: Добавляйте новые узлы, сохраняя целостность и баланс дерева. + - Удаление: Удаляйте узлы из дерева, не нарушая его структурных свойств. + - Вывод на консоль: Визуализируйте структуру дерева через печать в консоли, помогая в отладке и визуализации задач. + +[//]: # (## Usage) + + + +## Лицензия + +Распространяется по лицензии MIT. См. файл `LICENSE.txt` для получения дополнительной информации. + +## Авторы + +- [AlexandrKudrya](https://github.com/AlexandrKudrya) +- [7gambit7](https://github.com/7gambit7) +- [VersusXX](https://github.com/VersusXX) + +[license-shield]: https://img.shields.io/github/license/othneildrew/[license-url]: +[license-url]: https://github.com/spbu-coding-2023/trees-11/blob/main/LICENSE.txt \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..af1182c --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + kotlin("jvm") version "1.9.22" +} + +group = "org.example" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 zcmb5WV{~QRw(p$^Dz@00IL3?^hro$gg*4VI_WAaTyVM5Foj~O|-84 z$;06hMwt*rV;^8iB z1~&0XWpYJmG?Ts^K9PC62H*`G}xom%S%yq|xvG~FIfP=9*f zZoDRJBm*Y0aId=qJ?7dyb)6)JGWGwe)MHeNSzhi)Ko6J<-m@v=a%NsP537lHe0R* z`If4$aaBA#S=w!2z&m>{lpTy^Lm^mg*3?M&7HFv}7K6x*cukLIGX;bQG|QWdn{%_6 zHnwBKr84#B7Z+AnBXa16a?or^R?+>$4`}{*a_>IhbjvyTtWkHw)|ay)ahWUd-qq$~ zMbh6roVsj;_qnC-R{G+Cy6bApVOinSU-;(DxUEl!i2)1EeQ9`hrfqj(nKI7?Z>Xur zoJz-a`PxkYit1HEbv|jy%~DO^13J-ut986EEG=66S}D3!L}Efp;Bez~7tNq{QsUMm zh9~(HYg1pA*=37C0}n4g&bFbQ+?-h-W}onYeE{q;cIy%eZK9wZjSwGvT+&Cgv z?~{9p(;bY_1+k|wkt_|N!@J~aoY@|U_RGoWX<;p{Nu*D*&_phw`8jYkMNpRTWx1H* z>J-Mi_!`M468#5Aix$$u1M@rJEIOc?k^QBc?T(#=n&*5eS#u*Y)?L8Ha$9wRWdH^3D4|Ps)Y?m0q~SiKiSfEkJ!=^`lJ(%W3o|CZ zSrZL-Xxc{OrmsQD&s~zPfNJOpSZUl%V8tdG%ei}lQkM+z@-4etFPR>GOH9+Y_F<3=~SXln9Kb-o~f>2a6Xz@AS3cn^;c_>lUwlK(n>z?A>NbC z`Ud8^aQy>wy=$)w;JZzA)_*Y$Z5hU=KAG&htLw1Uh00yE!|Nu{EZkch zY9O6x7Y??>!7pUNME*d!=R#s)ghr|R#41l!c?~=3CS8&zr6*aA7n9*)*PWBV2w+&I zpW1-9fr3j{VTcls1>ua}F*bbju_Xq%^v;-W~paSqlf zolj*dt`BBjHI)H9{zrkBo=B%>8}4jeBO~kWqO!~Thi!I1H(in=n^fS%nuL=X2+s!p}HfTU#NBGiwEBF^^tKU zbhhv+0dE-sbK$>J#t-J!B$TMgN@Wh5wTtK2BG}4BGfsZOoRUS#G8Cxv|6EI*n&Xxq zt{&OxCC+BNqz$9b0WM7_PyBJEVObHFh%%`~!@MNZlo*oXDCwDcFwT~Rls!aApL<)^ zbBftGKKBRhB!{?fX@l2_y~%ygNFfF(XJzHh#?`WlSL{1lKT*gJM zs>bd^H9NCxqxn(IOky5k-wALFowQr(gw%|`0991u#9jXQh?4l|l>pd6a&rx|v=fPJ z1mutj{YzpJ_gsClbWFk(G}bSlFi-6@mwoQh-XeD*j@~huW4(8ub%^I|azA)h2t#yG z7e_V_<4jlM3D(I+qX}yEtqj)cpzN*oCdYHa!nm%0t^wHm)EmFP*|FMw!tb@&`G-u~ zK)=Sf6z+BiTAI}}i{*_Ac$ffr*Wrv$F7_0gJkjx;@)XjYSh`RjAgrCck`x!zP>Ifu z&%he4P|S)H*(9oB4uvH67^0}I-_ye_!w)u3v2+EY>eD3#8QR24<;7?*hj8k~rS)~7 zSXs5ww)T(0eHSp$hEIBnW|Iun<_i`}VE0Nc$|-R}wlSIs5pV{g_Dar(Zz<4X3`W?K z6&CAIl4U(Qk-tTcK{|zYF6QG5ArrEB!;5s?tW7 zrE3hcFY&k)+)e{+YOJ0X2uDE_hd2{|m_dC}kgEKqiE9Q^A-+>2UonB+L@v3$9?AYw zVQv?X*pK;X4Ovc6Ev5Gbg{{Eu*7{N3#0@9oMI~}KnObQE#Y{&3mM4`w%wN+xrKYgD zB-ay0Q}m{QI;iY`s1Z^NqIkjrTlf`B)B#MajZ#9u41oRBC1oM1vq0i|F59> z#StM@bHt|#`2)cpl_rWB($DNJ3Lap}QM-+A$3pe}NyP(@+i1>o^fe-oxX#Bt`mcQc zb?pD4W%#ep|3%CHAYnr*^M6Czg>~L4?l16H1OozM{P*en298b+`i4$|w$|4AHbzqB zHpYUsHZET$Z0ztC;U+0*+amF!@PI%^oUIZy{`L{%O^i{Xk}X0&nl)n~tVEpcAJSJ} zverw15zP1P-O8h9nd!&hj$zuwjg?DoxYIw{jWM zW5_pj+wFy8Tsa9g<7Qa21WaV&;ejoYflRKcz?#fSH_)@*QVlN2l4(QNk| z4aPnv&mrS&0|6NHq05XQw$J^RR9T{3SOcMKCXIR1iSf+xJ0E_Wv?jEc*I#ZPzyJN2 zUG0UOXHl+PikM*&g$U@g+KbG-RY>uaIl&DEtw_Q=FYq?etc!;hEC_}UX{eyh%dw2V zTTSlap&5>PY{6I#(6`j-9`D&I#|YPP8a;(sOzgeKDWsLa!i-$frD>zr-oid!Hf&yS z!i^cr&7tN}OOGmX2)`8k?Tn!!4=tz~3hCTq_9CdiV!NIblUDxHh(FJ$zs)B2(t5@u z-`^RA1ShrLCkg0)OhfoM;4Z{&oZmAec$qV@ zGQ(7(!CBk<5;Ar%DLJ0p0!ResC#U<+3i<|vib1?{5gCebG7$F7URKZXuX-2WgF>YJ^i zMhHDBsh9PDU8dlZ$yJKtc6JA#y!y$57%sE>4Nt+wF1lfNIWyA`=hF=9Gj%sRwi@vd z%2eVV3y&dvAgyuJ=eNJR+*080dbO_t@BFJO<@&#yqTK&+xc|FRR;p;KVk@J3$S{p` zGaMj6isho#%m)?pOG^G0mzOAw0z?!AEMsv=0T>WWcE>??WS=fII$t$(^PDPMU(P>o z_*0s^W#|x)%tx8jIgZY~A2yG;US0m2ZOQt6yJqW@XNY_>_R7(Nxb8Ged6BdYW6{prd!|zuX$@Q2o6Ona8zzYC1u!+2!Y$Jc9a;wy+pXt}o6~Bu1oF1c zp7Y|SBTNi@=I(K%A60PMjM#sfH$y*c{xUgeSpi#HB`?|`!Tb&-qJ3;vxS!TIzuTZs-&%#bAkAyw9m4PJgvey zM5?up*b}eDEY+#@tKec)-c(#QF0P?MRlD1+7%Yk*jW;)`f;0a-ZJ6CQA?E%>i2Dt7T9?s|9ZF|KP4;CNWvaVKZ+Qeut;Jith_y{v*Ny6Co6!8MZx;Wgo z=qAi%&S;8J{iyD&>3CLCQdTX*$+Rx1AwA*D_J^0>suTgBMBb=*hefV+Ars#mmr+YsI3#!F@Xc1t4F-gB@6aoyT+5O(qMz*zG<9Qq*f0w^V!03rpr*-WLH}; zfM{xSPJeu6D(%8HU%0GEa%waFHE$G?FH^kMS-&I3)ycx|iv{T6Wx}9$$D&6{%1N_8 z_CLw)_9+O4&u94##vI9b-HHm_95m)fa??q07`DniVjAy`t7;)4NpeyAY(aAk(+T_O z1om+b5K2g_B&b2DCTK<>SE$Ode1DopAi)xaJjU>**AJK3hZrnhEQ9E`2=|HHe<^tv z63e(bn#fMWuz>4erc47}!J>U58%<&N<6AOAewyzNTqi7hJc|X{782&cM zHZYclNbBwU6673=!ClmxMfkC$(CykGR@10F!zN1Se83LR&a~$Ht&>~43OX22mt7tcZUpa;9@q}KDX3O&Ugp6< zLZLfIMO5;pTee1vNyVC$FGxzK2f>0Z-6hM82zKg44nWo|n}$Zk6&;5ry3`(JFEX$q zK&KivAe${e^5ZGc3a9hOt|!UOE&OocpVryE$Y4sPcs4rJ>>Kbi2_subQ9($2VN(3o zb~tEzMsHaBmBtaHAyES+d3A(qURgiskSSwUc9CfJ@99&MKp2sooSYZu+-0t0+L*!I zYagjOlPgx|lep9tiU%ts&McF6b0VE57%E0Ho%2oi?=Ks+5%aj#au^OBwNwhec zta6QAeQI^V!dF1C)>RHAmB`HnxyqWx?td@4sd15zPd*Fc9hpDXP23kbBenBxGeD$k z;%0VBQEJ-C)&dTAw_yW@k0u?IUk*NrkJ)(XEeI z9Y>6Vel>#s_v@=@0<{4A{pl=9cQ&Iah0iD0H`q)7NeCIRz8zx;! z^OO;1+IqoQNak&pV`qKW+K0^Hqp!~gSohcyS)?^P`JNZXw@gc6{A3OLZ?@1Uc^I2v z+X!^R*HCm3{7JPq{8*Tn>5;B|X7n4QQ0Bs79uTU%nbqOJh`nX(BVj!#f;#J+WZxx4 z_yM&1Y`2XzhfqkIMO7tB3raJKQS+H5F%o83bM+hxbQ zeeJm=Dvix$2j|b4?mDacb67v-1^lTp${z=jc1=j~QD>7c*@+1?py>%Kj%Ejp7Y-!? z8iYRUlGVrQPandAaxFfks53@2EC#0)%mrnmGRn&>=$H$S8q|kE_iWko4`^vCS2aWg z#!`RHUGyOt*k?bBYu3*j3u0gB#v(3tsije zgIuNNWNtrOkx@Pzs;A9un+2LX!zw+p3_NX^Sh09HZAf>m8l@O*rXy_82aWT$Q>iyy zqO7Of)D=wcSn!0+467&!Hl))eff=$aneB?R!YykdKW@k^_uR!+Q1tR)+IJb`-6=jj zymzA>Sv4>Z&g&WWu#|~GcP7qP&m*w-S$)7Xr;(duqCTe7p8H3k5>Y-n8438+%^9~K z3r^LIT_K{i7DgEJjIocw_6d0!<;wKT`X;&vv+&msmhAAnIe!OTdybPctzcEzBy88_ zWO{6i4YT%e4^WQZB)KHCvA(0tS zHu_Bg+6Ko%a9~$EjRB90`P(2~6uI@SFibxct{H#o&y40MdiXblu@VFXbhz>Nko;7R z70Ntmm-FePqhb%9gL+7U8@(ch|JfH5Fm)5${8|`Lef>LttM_iww6LW2X61ldBmG0z zax3y)njFe>j*T{i0s8D4=L>X^j0)({R5lMGVS#7(2C9@AxL&C-lZQx~czI7Iv+{%1 z2hEG>RzX4S8x3v#9sgGAnPzptM)g&LB}@%E>fy0vGSa(&q0ch|=ncKjNrK z`jA~jObJhrJ^ri|-)J^HUyeZXz~XkBp$VhcTEcTdc#a2EUOGVX?@mYx#Vy*!qO$Jv zQ4rgOJ~M*o-_Wptam=~krnmG*p^j!JAqoQ%+YsDFW7Cc9M%YPiBOrVcD^RY>m9Pd< zu}#9M?K{+;UIO!D9qOpq9yxUquQRmQNMo0pT`@$pVt=rMvyX)ph(-CCJLvUJy71DI zBk7oc7)-%ngdj~s@76Yse3L^gV0 z2==qfp&Q~L(+%RHP0n}+xH#k(hPRx(!AdBM$JCfJ5*C=K3ts>P?@@SZ_+{U2qFZb>4kZ{Go37{# zSQc+-dq*a-Vy4?taS&{Ht|MLRiS)Sn14JOONyXqPNnpq&2y~)6wEG0oNy>qvod$FF z`9o&?&6uZjhZ4_*5qWVrEfu(>_n2Xi2{@Gz9MZ8!YmjYvIMasE9yVQL10NBrTCczq zcTY1q^PF2l!Eraguf{+PtHV3=2A?Cu&NN&a8V(y;q(^_mFc6)%Yfn&X&~Pq zU1?qCj^LF(EQB1F`8NxNjyV%fde}dEa(Hx=r7$~ts2dzDwyi6ByBAIx$NllB4%K=O z$AHz1<2bTUb>(MCVPpK(E9wlLElo(aSd(Os)^Raum`d(g9Vd_+Bf&V;l=@mM=cC>) z)9b0enb)u_7V!!E_bl>u5nf&Rl|2r=2F3rHMdb7y9E}}F82^$Rf+P8%dKnOeKh1vs zhH^P*4Ydr^$)$h@4KVzxrHyy#cKmWEa9P5DJ|- zG;!Qi35Tp7XNj60=$!S6U#!(${6hyh7d4q=pF{`0t|N^|L^d8pD{O9@tF~W;#Je*P z&ah%W!KOIN;SyAEhAeTafJ4uEL`(RtnovM+cb(O#>xQnk?dzAjG^~4$dFn^<@-Na3 z395;wBnS{t*H;Jef2eE!2}u5Ns{AHj>WYZDgQJt8v%x?9{MXqJsGP|l%OiZqQ1aB! z%E=*Ig`(!tHh>}4_z5IMpg{49UvD*Pp9!pxt_gdAW%sIf3k6CTycOT1McPl=_#0?8 zVjz8Hj*Vy9c5-krd-{BQ{6Xy|P$6LJvMuX$* zA+@I_66_ET5l2&gk9n4$1M3LN8(yEViRx&mtd#LD}AqEs?RW=xKC(OCWH;~>(X6h!uDxXIPH06xh z*`F4cVlbDP`A)-fzf>MuScYsmq&1LUMGaQ3bRm6i7OsJ|%uhTDT zlvZA1M}nz*SalJWNT|`dBm1$xlaA>CCiQ zK`xD-RuEn>-`Z?M{1%@wewf#8?F|(@1e0+T4>nmlSRrNK5f)BJ2H*$q(H>zGD0>eL zQ!tl_Wk)k*e6v^m*{~A;@6+JGeWU-q9>?+L_#UNT%G?4&BnOgvm9@o7l?ov~XL+et zbGT)|G7)KAeqb=wHSPk+J1bdg7N3$vp(ekjI1D9V$G5Cj!=R2w=3*4!z*J-r-cyeb zd(i2KmX!|Lhey!snRw z?#$Gu%S^SQEKt&kep)up#j&9}e+3=JJBS(s>MH+|=R(`8xK{mmndWo_r`-w1#SeRD&YtAJ#GiVI*TkQZ}&aq<+bU2+coU3!jCI6E+Ad_xFW*ghnZ$q zAoF*i&3n1j#?B8x;kjSJD${1jdRB;)R*)Ao!9bd|C7{;iqDo|T&>KSh6*hCD!rwv= zyK#F@2+cv3=|S1Kef(E6Niv8kyLVLX&e=U;{0x{$tDfShqkjUME>f8d(5nzSkY6@! z^-0>DM)wa&%m#UF1F?zR`8Y3X#tA!*7Q$P3lZJ%*KNlrk_uaPkxw~ zxZ1qlE;Zo;nb@!SMazSjM>;34ROOoygo%SF);LL>rRonWwR>bmSd1XD^~sGSu$Gg# zFZ`|yKU0%!v07dz^v(tY%;So(e`o{ZYTX`hm;@b0%8|H>VW`*cr8R%3n|ehw2`(9B+V72`>SY}9^8oh$En80mZK9T4abVG*to;E z1_S6bgDOW?!Oy1LwYy=w3q~KKdbNtyH#d24PFjX)KYMY93{3-mPP-H>@M-_>N~DDu zENh~reh?JBAK=TFN-SfDfT^=+{w4ea2KNWXq2Y<;?(gf(FgVp8Zp-oEjKzB%2Iqj;48GmY3h=bcdYJ}~&4tS`Q1sb=^emaW$IC$|R+r-8V- zf0$gGE(CS_n4s>oicVk)MfvVg#I>iDvf~Ov8bk}sSxluG!6#^Z_zhB&U^`eIi1@j( z^CK$z^stBHtaDDHxn+R;3u+>Lil^}fj?7eaGB z&5nl^STqcaBxI@v>%zG|j))G(rVa4aY=B@^2{TFkW~YP!8!9TG#(-nOf^^X-%m9{Z zCC?iC`G-^RcBSCuk=Z`(FaUUe?hf3{0C>>$?Vs z`2Uud9M+T&KB6o4o9kvdi^Q=Bw!asPdxbe#W-Oaa#_NP(qpyF@bVxv5D5))srkU#m zj_KA+#7sqDn*Ipf!F5Byco4HOSd!Ui$l94|IbW%Ny(s1>f4|Mv^#NfB31N~kya9!k zWCGL-$0ZQztBate^fd>R!hXY_N9ZjYp3V~4_V z#eB)Kjr8yW=+oG)BuNdZG?jaZlw+l_ma8aET(s+-x+=F-t#Qoiuu1i`^x8Sj>b^U} zs^z<()YMFP7CmjUC@M=&lA5W7t&cxTlzJAts*%PBDAPuqcV5o7HEnqjif_7xGt)F% zGx2b4w{@!tE)$p=l3&?Bf#`+!-RLOleeRk3 z7#pF|w@6_sBmn1nECqdunmG^}pr5(ZJQVvAt$6p3H(16~;vO>?sTE`Y+mq5YP&PBo zvq!7#W$Gewy`;%6o^!Dtjz~x)T}Bdk*BS#=EY=ODD&B=V6TD2z^hj1m5^d6s)D*wk zu$z~D7QuZ2b?5`p)E8e2_L38v3WE{V`bVk;6fl#o2`) z99JsWhh?$oVRn@$S#)uK&8DL8>An0&S<%V8hnGD7Z^;Y(%6;^9!7kDQ5bjR_V+~wp zfx4m3z6CWmmZ<8gDGUyg3>t8wgJ5NkkiEm^(sedCicP^&3D%}6LtIUq>mXCAt{9eF zNXL$kGcoUTf_Lhm`t;hD-SE)m=iBnxRU(NyL}f6~1uH)`K!hmYZjLI%H}AmEF5RZt z06$wn63GHnApHXZZJ}s^s)j9(BM6e*7IBK6Bq(!)d~zR#rbxK9NVIlgquoMq z=eGZ9NR!SEqP6=9UQg#@!rtbbSBUM#ynF);zKX+|!Zm}*{H z+j=d?aZ2!?@EL7C~%B?6ouCKLnO$uWn;Y6Xz zX8dSwj732u(o*U3F$F=7xwxm>E-B+SVZH;O-4XPuPkLSt_?S0)lb7EEg)Mglk0#eS z9@jl(OnH4juMxY+*r03VDfPx_IM!Lmc(5hOI;`?d37f>jPP$?9jQQIQU@i4vuG6MagEoJrQ=RD7xt@8E;c zeGV*+Pt+t$@pt!|McETOE$9k=_C!70uhwRS9X#b%ZK z%q(TIUXSS^F0`4Cx?Rk07C6wI4!UVPeI~-fxY6`YH$kABdOuiRtl73MqG|~AzZ@iL&^s?24iS;RK_pdlWkhcF z@Wv-Om(Aealfg)D^adlXh9Nvf~Uf@y;g3Y)i(YP zEXDnb1V}1pJT5ZWyw=1i+0fni9yINurD=EqH^ciOwLUGi)C%Da)tyt=zq2P7pV5-G zR7!oq28-Fgn5pW|nlu^b!S1Z#r7!Wtr{5J5PQ>pd+2P7RSD?>(U7-|Y z7ZQ5lhYIl_IF<9?T9^IPK<(Hp;l5bl5tF9>X-zG14_7PfsA>6<$~A338iYRT{a@r_ zuXBaT=`T5x3=s&3=RYx6NgG>No4?5KFBVjE(swfcivcIpPQFx5l+O;fiGsOrl5teR z_Cm+;PW}O0Dwe_(4Z@XZ)O0W-v2X><&L*<~*q3dg;bQW3g7)a#3KiQP>+qj|qo*Hk z?57>f2?f@`=Fj^nkDKeRkN2d$Z@2eNKpHo}ksj-$`QKb6n?*$^*%Fb3_Kbf1(*W9K>{L$mud2WHJ=j0^=g30Xhg8$#g^?36`p1fm;;1@0Lrx+8t`?vN0ZorM zSW?rhjCE8$C|@p^sXdx z|NOHHg+fL;HIlqyLp~SSdIF`TnSHehNCU9t89yr@)FY<~hu+X`tjg(aSVae$wDG*C zq$nY(Y494R)hD!i1|IIyP*&PD_c2FPgeY)&mX1qujB1VHPG9`yFQpLFVQ0>EKS@Bp zAfP5`C(sWGLI?AC{XEjLKR4FVNw(4+9b?kba95ukgR1H?w<8F7)G+6&(zUhIE5Ef% z=fFkL3QKA~M@h{nzjRq!Y_t!%U66#L8!(2-GgFxkD1=JRRqk=n%G(yHKn%^&$dW>; zSjAcjETMz1%205se$iH_)ZCpfg_LwvnsZQAUCS#^FExp8O4CrJb6>JquNV@qPq~3A zZ<6dOU#6|8+fcgiA#~MDmcpIEaUO02L5#T$HV0$EMD94HT_eXLZ2Zi&(! z&5E>%&|FZ`)CN10tM%tLSPD*~r#--K(H-CZqIOb99_;m|D5wdgJ<1iOJz@h2Zkq?} z%8_KXb&hf=2Wza(Wgc;3v3TN*;HTU*q2?#z&tLn_U0Nt!y>Oo>+2T)He6%XuP;fgn z-G!#h$Y2`9>Jtf}hbVrm6D70|ERzLAU>3zoWhJmjWfgM^))T+2u$~5>HF9jQDkrXR z=IzX36)V75PrFjkQ%TO+iqKGCQ-DDXbaE;C#}!-CoWQx&v*vHfyI>$HNRbpvm<`O( zlx9NBWD6_e&J%Ous4yp~s6)Ghni!I6)0W;9(9$y1wWu`$gs<$9Mcf$L*piP zPR0Av*2%ul`W;?-1_-5Zy0~}?`e@Y5A&0H!^ApyVTT}BiOm4GeFo$_oPlDEyeGBbh z1h3q&Dx~GmUS|3@4V36&$2uO8!Yp&^pD7J5&TN{?xphf*-js1fP?B|`>p_K>lh{ij zP(?H%e}AIP?_i^f&Li=FDSQ`2_NWxL+BB=nQr=$ zHojMlXNGauvvwPU>ZLq!`bX-5F4jBJ&So{kE5+ms9UEYD{66!|k~3vsP+mE}x!>%P za98bAU0!h0&ka4EoiDvBM#CP#dRNdXJcb*(%=<(g+M@<)DZ!@v1V>;54En?igcHR2 zhubQMq}VSOK)onqHfczM7YA@s=9*ow;k;8)&?J3@0JiGcP! zP#00KZ1t)GyZeRJ=f0^gc+58lc4Qh*S7RqPIC6GugG1gXe$LIQMRCo8cHf^qXgAa2 z`}t>u2Cq1CbSEpLr~E=c7~=Qkc9-vLE%(v9N*&HF`(d~(0`iukl5aQ9u4rUvc8%m) zr2GwZN4!s;{SB87lJB;veebPmqE}tSpT>+`t?<457Q9iV$th%i__Z1kOMAswFldD6 ztbOvO337S5o#ZZgN2G99_AVqPv!?Gmt3pzgD+Hp3QPQ`9qJ(g=kjvD+fUSS3upJn! zqoG7acIKEFRX~S}3|{EWT$kdz#zrDlJU(rPkxjws_iyLKU8+v|*oS_W*-guAb&Pj1 z35Z`3z<&Jb@2Mwz=KXucNYdY#SNO$tcVFr9KdKm|%^e-TXzs6M`PBper%ajkrIyUe zp$vVxVs9*>Vp4_1NC~Zg)WOCPmOxI1V34QlG4!aSFOH{QqSVq1^1)- z0P!Z?tT&E-ll(pwf0?=F=yOzik=@nh1Clxr9}Vij89z)ePDSCYAqw?lVI?v?+&*zH z)p$CScFI8rrwId~`}9YWPFu0cW1Sf@vRELs&cbntRU6QfPK-SO*mqu|u~}8AJ!Q$z znzu}50O=YbjwKCuSVBs6&CZR#0FTu)3{}qJJYX(>QPr4$RqWiwX3NT~;>cLn*_&1H zaKpIW)JVJ>b{uo2oq>oQt3y=zJjb%fU@wLqM{SyaC6x2snMx-}ivfU<1- znu1Lh;i$3Tf$Kh5Uk))G!D1UhE8pvx&nO~w^fG)BC&L!_hQk%^p`Kp@F{cz>80W&T ziOK=Sq3fdRu*V0=S53rcIfWFazI}Twj63CG(jOB;$*b`*#B9uEnBM`hDk*EwSRdwP8?5T?xGUKs=5N83XsR*)a4|ijz|c{4tIU+4j^A5C<#5 z*$c_d=5ml~%pGxw#?*q9N7aRwPux5EyqHVkdJO=5J>84!X6P>DS8PTTz>7C#FO?k#edkntG+fJk8ZMn?pmJSO@`x-QHq;7^h6GEXLXo1TCNhH z8ZDH{*NLAjo3WM`xeb=X{((uv3H(8&r8fJJg_uSs_%hOH%JDD?hu*2NvWGYD+j)&` zz#_1%O1wF^o5ryt?O0n;`lHbzp0wQ?rcbW(F1+h7_EZZ9{>rePvLAPVZ_R|n@;b$;UchU=0j<6k8G9QuQf@76oiE*4 zXOLQ&n3$NR#p4<5NJMVC*S);5x2)eRbaAM%VxWu9ohlT;pGEk7;002enCbQ>2r-us z3#bpXP9g|mE`65VrN`+3mC)M(eMj~~eOf)do<@l+fMiTR)XO}422*1SL{wyY(%oMpBgJagtiDf zz>O6(m;};>Hi=t8o{DVC@YigqS(Qh+ix3Rwa9aliH}a}IlOCW1@?%h_bRbq-W{KHF z%Vo?-j@{Xi@=~Lz5uZP27==UGE15|g^0gzD|3x)SCEXrx`*MP^FDLl%pOi~~Il;dc z^hrwp9sYeT7iZ)-ajKy@{a`kr0-5*_!XfBpXwEcFGJ;%kV$0Nx;apKrur zJN2J~CAv{Zjj%FolyurtW8RaFmpn&zKJWL>(0;;+q(%(Hx!GMW4AcfP0YJ*Vz!F4g z!ZhMyj$BdXL@MlF%KeInmPCt~9&A!;cRw)W!Hi@0DY(GD_f?jeV{=s=cJ6e}JktJw zQORnxxj3mBxfrH=x{`_^Z1ddDh}L#V7i}$njUFRVwOX?qOTKjfPMBO4y(WiU<)epb zvB9L=%jW#*SL|Nd_G?E*_h1^M-$PG6Pc_&QqF0O-FIOpa4)PAEPsyvB)GKasmBoEt z?_Q2~QCYGH+hW31x-B=@5_AN870vY#KB~3a*&{I=f);3Kv7q4Q7s)0)gVYx2#Iz9g(F2;=+Iy4 z6KI^8GJ6D@%tpS^8boU}zpi=+(5GfIR)35PzrbuXeL1Y1N%JK7PG|^2k3qIqHfX;G zQ}~JZ-UWx|60P5?d1e;AHx!_;#PG%d=^X(AR%i`l0jSpYOpXoKFW~7ip7|xvN;2^? zsYC9fanpO7rO=V7+KXqVc;Q5z%Bj})xHVrgoR04sA2 zl~DAwv=!(()DvH*=lyhIlU^hBkA0$e*7&fJpB0|oB7)rqGK#5##2T`@_I^|O2x4GO z;xh6ROcV<9>?e0)MI(y++$-ksV;G;Xe`lh76T#Htuia+(UrIXrf9?

L(tZ$0BqX1>24?V$S+&kLZ`AodQ4_)P#Q3*4xg8}lMV-FLwC*cN$< zt65Rf%7z41u^i=P*qO8>JqXPrinQFapR7qHAtp~&RZ85$>ob|Js;GS^y;S{XnGiBc zGa4IGvDl?x%gY`vNhv8wgZnP#UYI-w*^4YCZnxkF85@ldepk$&$#3EAhrJY0U)lR{F6sM3SONV^+$;Zx8BD&Eku3K zKNLZyBni3)pGzU0;n(X@1fX8wYGKYMpLmCu{N5-}epPDxClPFK#A@02WM3!myN%bkF z|GJ4GZ}3sL{3{qXemy+#Uk{4>Kf8v11;f8I&c76+B&AQ8udd<8gU7+BeWC`akUU~U zgXoxie>MS@rBoyY8O8Tc&8id!w+_ooxcr!1?#rc$-|SBBtH6S?)1e#P#S?jFZ8u-Bs&k`yLqW|{j+%c#A4AQ>+tj$Y z^CZajspu$F%73E68Lw5q7IVREED9r1Ijsg#@DzH>wKseye>hjsk^{n0g?3+gs@7`i zHx+-!sjLx^fS;fY!ERBU+Q zVJ!e0hJH%P)z!y%1^ZyG0>PN@5W~SV%f>}c?$H8r;Sy-ui>aruVTY=bHe}$e zi&Q4&XK!qT7-XjCrDaufT@>ieQ&4G(SShUob0Q>Gznep9fR783jGuUynAqc6$pYX; z7*O@@JW>O6lKIk0G00xsm|=*UVTQBB`u1f=6wGAj%nHK_;Aqmfa!eAykDmi-@u%6~ z;*c!pS1@V8r@IX9j&rW&d*}wpNs96O2Ute>%yt{yv>k!6zfT6pru{F1M3P z2WN1JDYqoTB#(`kE{H676QOoX`cnqHl1Yaru)>8Ky~VU{)r#{&s86Vz5X)v15ULHA zAZDb{99+s~qI6;-dQ5DBjHJP@GYTwn;Dv&9kE<0R!d z8tf1oq$kO`_sV(NHOSbMwr=To4r^X$`sBW4$gWUov|WY?xccQJN}1DOL|GEaD_!@& z15p?Pj+>7d`@LvNIu9*^hPN)pwcv|akvYYq)ks%`G>!+!pW{-iXPZsRp8 z35LR;DhseQKWYSD`%gO&k$Dj6_6q#vjWA}rZcWtQr=Xn*)kJ9kacA=esi*I<)1>w^ zO_+E>QvjP)qiSZg9M|GNeLtO2D7xT6vsj`88sd!94j^AqxFLi}@w9!Y*?nwWARE0P znuI_7A-saQ+%?MFA$gttMV-NAR^#tjl_e{R$N8t2NbOlX373>e7Ox=l=;y#;M7asp zRCz*CLnrm$esvSb5{T<$6CjY zmZ(i{Rs_<#pWW>(HPaaYj`%YqBra=Ey3R21O7vUbzOkJJO?V`4-D*u4$Me0Bx$K(lYo`JO}gnC zx`V}a7m-hLU9Xvb@K2ymioF)vj12<*^oAqRuG_4u%(ah?+go%$kOpfb`T96P+L$4> zQ#S+sA%VbH&mD1k5Ak7^^dZoC>`1L%i>ZXmooA!%GI)b+$D&ziKrb)a=-ds9xk#~& z7)3iem6I|r5+ZrTRe_W861x8JpD`DDIYZNm{$baw+$)X^Jtjnl0xlBgdnNY}x%5za zkQ8E6T<^$sKBPtL4(1zi_Rd(tVth*3Xs!ulflX+70?gb&jRTnI8l+*Aj9{|d%qLZ+ z>~V9Z;)`8-lds*Zgs~z1?Fg?Po7|FDl(Ce<*c^2=lFQ~ahwh6rqSjtM5+$GT>3WZW zj;u~w9xwAhOc<kF}~`CJ68 z?(S5vNJa;kriPlim33{N5`C{9?NWhzsna_~^|K2k4xz1`xcui*LXL-1#Y}Hi9`Oo!zQ>x-kgAX4LrPz63uZ+?uG*84@PKq-KgQlMNRwz=6Yes) zY}>YN+qP}nwr$(CZQFjUOI=-6J$2^XGvC~EZ+vrqWaOXB$k?%Suf5k=4>AveC1aJ! ziaW4IS%F$_Babi)kA8Y&u4F7E%99OPtm=vzw$$ zEz#9rvn`Iot_z-r3MtV>k)YvErZ<^Oa${`2>MYYODSr6?QZu+be-~MBjwPGdMvGd!b!elsdi4% z`37W*8+OGulab8YM?`KjJ8e+jM(tqLKSS@=jimq3)Ea2EB%88L8CaM+aG7;27b?5` z4zuUWBr)f)k2o&xg{iZ$IQkJ+SK>lpq4GEacu~eOW4yNFLU!Kgc{w4&D$4ecm0f}~ zTTzquRW@`f0}|IILl`!1P+;69g^upiPA6F{)U8)muWHzexRenBU$E^9X-uIY2%&1w z_=#5*(nmxJ9zF%styBwivi)?#KMG96-H@hD-H_&EZiRNsfk7mjBq{L%!E;Sqn!mVX*}kXhwH6eh;b42eD!*~upVG@ z#smUqz$ICm!Y8wY53gJeS|Iuard0=;k5i5Z_hSIs6tr)R4n*r*rE`>38Pw&lkv{_r!jNN=;#?WbMj|l>cU(9trCq; z%nN~r^y7!kH^GPOf3R}?dDhO=v^3BeP5hF|%4GNQYBSwz;x({21i4OQY->1G=KFyu z&6d`f2tT9Yl_Z8YACZaJ#v#-(gcyeqXMhYGXb=t>)M@fFa8tHp2x;ODX=Ap@a5I=U z0G80^$N0G4=U(>W%mrrThl0DjyQ-_I>+1Tdd_AuB3qpYAqY54upwa3}owa|x5iQ^1 zEf|iTZxKNGRpI>34EwkIQ2zHDEZ=(J@lRaOH>F|2Z%V_t56Km$PUYu^xA5#5Uj4I4RGqHD56xT%H{+P8Ag>e_3pN$4m8n>i%OyJFPNWaEnJ4McUZPa1QmOh?t8~n& z&RulPCors8wUaqMHECG=IhB(-tU2XvHP6#NrLVyKG%Ee*mQ5Ps%wW?mcnriTVRc4J`2YVM>$ixSF2Xi+Wn(RUZnV?mJ?GRdw%lhZ+t&3s7g!~g{%m&i<6 z5{ib-<==DYG93I(yhyv4jp*y3#*WNuDUf6`vTM%c&hiayf(%=x@4$kJ!W4MtYcE#1 zHM?3xw63;L%x3drtd?jot!8u3qeqctceX3m;tWetK+>~q7Be$h>n6riK(5@ujLgRS zvOym)k+VAtyV^mF)$29Y`nw&ijdg~jYpkx%*^ z8dz`C*g=I?;clyi5|!27e2AuSa$&%UyR(J3W!A=ZgHF9OuKA34I-1U~pyD!KuRkjA zbkN!?MfQOeN>DUPBxoy5IX}@vw`EEB->q!)8fRl_mqUVuRu|C@KD-;yl=yKc=ZT0% zB$fMwcC|HE*0f8+PVlWHi>M`zfsA(NQFET?LrM^pPcw`cK+Mo0%8*x8@65=CS_^$cG{GZQ#xv($7J z??R$P)nPLodI;P!IC3eEYEHh7TV@opr#*)6A-;EU2XuogHvC;;k1aI8asq7ovoP!* z?x%UoPrZjj<&&aWpsbr>J$Er-7!E(BmOyEv!-mbGQGeJm-U2J>74>o5x`1l;)+P&~ z>}f^=Rx(ZQ2bm+YE0u=ZYrAV@apyt=v1wb?R@`i_g64YyAwcOUl=C!i>=Lzb$`tjv zOO-P#A+)t-JbbotGMT}arNhJmmGl-lyUpMn=2UacVZxmiG!s!6H39@~&uVokS zG=5qWhfW-WOI9g4!R$n7!|ViL!|v3G?GN6HR0Pt_L5*>D#FEj5wM1DScz4Jv@Sxnl zB@MPPmdI{(2D?;*wd>3#tjAirmUnQoZrVv`xM3hARuJksF(Q)wd4P$88fGYOT1p6U z`AHSN!`St}}UMBT9o7i|G`r$ zrB=s$qV3d6$W9@?L!pl0lf%)xs%1ko^=QY$ty-57=55PvP(^6E7cc zGJ*>m2=;fOj?F~yBf@K@9qwX0hA803Xw+b0m}+#a(>RyR8}*Y<4b+kpp|OS+!whP( zH`v{%s>jsQI9rd$*vm)EkwOm#W_-rLTHcZRek)>AtF+~<(did)*oR1|&~1|e36d-d zgtm5cv1O0oqgWC%Et@P4Vhm}Ndl(Y#C^MD03g#PH-TFy+7!Osv1z^UWS9@%JhswEq~6kSr2DITo59+; ze=ZC}i2Q?CJ~Iyu?vn|=9iKV>4j8KbxhE4&!@SQ^dVa-gK@YfS9xT(0kpW*EDjYUkoj! zE49{7H&E}k%5(>sM4uGY)Q*&3>{aitqdNnRJkbOmD5Mp5rv-hxzOn80QsG=HJ_atI-EaP69cacR)Uvh{G5dTpYG7d zbtmRMq@Sexey)||UpnZ?;g_KMZq4IDCy5}@u!5&B^-=6yyY{}e4Hh3ee!ZWtL*s?G zxG(A!<9o!CL+q?u_utltPMk+hn?N2@?}xU0KlYg?Jco{Yf@|mSGC<(Zj^yHCvhmyx z?OxOYoxbptDK()tsJ42VzXdINAMWL$0Gcw?G(g8TMB)Khw_|v9`_ql#pRd2i*?CZl z7k1b!jQB=9-V@h%;Cnl7EKi;Y^&NhU0mWEcj8B|3L30Ku#-9389Q+(Yet0r$F=+3p z6AKOMAIi|OHyzlHZtOm73}|ntKtFaXF2Fy|M!gOh^L4^62kGUoWS1i{9gsds_GWBc zLw|TaLP64z3z9?=R2|T6Xh2W4_F*$cq>MtXMOy&=IPIJ`;!Tw?PqvI2b*U1)25^<2 zU_ZPoxg_V0tngA0J+mm?3;OYw{i2Zb4x}NedZug!>EoN3DC{1i)Z{Z4m*(y{ov2%- zk(w>+scOO}MN!exSc`TN)!B=NUX`zThWO~M*ohqq;J2hx9h9}|s#?@eR!=F{QTrq~ zTcY|>azkCe$|Q0XFUdpFT=lTcyW##i;-e{}ORB4D?t@SfqGo_cS z->?^rh$<&n9DL!CF+h?LMZRi)qju!meugvxX*&jfD!^1XB3?E?HnwHP8$;uX{Rvp# zh|)hM>XDv$ZGg=$1{+_bA~u-vXqlw6NH=nkpyWE0u}LQjF-3NhATL@9rRxMnpO%f7 z)EhZf{PF|mKIMFxnC?*78(}{Y)}iztV12}_OXffJ;ta!fcFIVjdchyHxH=t%ci`Xd zX2AUB?%?poD6Zv*&BA!6c5S#|xn~DK01#XvjT!w!;&`lDXSJT4_j$}!qSPrb37vc{ z9^NfC%QvPu@vlxaZ;mIbn-VHA6miwi8qJ~V;pTZkKqqOii<1Cs}0i?uUIss;hM4dKq^1O35y?Yp=l4i zf{M!@QHH~rJ&X~8uATV><23zZUbs-J^3}$IvV_ANLS08>k`Td7aU_S1sLsfi*C-m1 z-e#S%UGs4E!;CeBT@9}aaI)qR-6NU@kvS#0r`g&UWg?fC7|b^_HyCE!8}nyh^~o@< zpm7PDFs9yxp+byMS(JWm$NeL?DNrMCNE!I^ko-*csB+dsf4GAq{=6sfyf4wb>?v1v zmb`F*bN1KUx-`ra1+TJ37bXNP%`-Fd`vVQFTwWpX@;s(%nDQa#oWhgk#mYlY*!d>( zE&!|ySF!mIyfING+#%RDY3IBH_fW$}6~1%!G`suHub1kP@&DoAd5~7J55;5_noPI6eLf{t;@9Kf<{aO0`1WNKd?<)C-|?C?)3s z>wEq@8=I$Wc~Mt$o;g++5qR+(6wt9GI~pyrDJ%c?gPZe)owvy^J2S=+M^ z&WhIE`g;;J^xQLVeCtf7b%Dg#Z2gq9hp_%g)-%_`y*zb; zn9`f`mUPN-Ts&fFo(aNTsXPA|J!TJ{0hZp0^;MYHLOcD=r_~~^ymS8KLCSeU3;^QzJNqS z5{5rEAv#l(X?bvwxpU;2%pQftF`YFgrD1jt2^~Mt^~G>T*}A$yZc@(k9orlCGv&|1 zWWvVgiJsCAtamuAYT~nzs?TQFt<1LSEx!@e0~@yd6$b5!Zm(FpBl;(Cn>2vF?k zOm#TTjFwd2D-CyA!mqR^?#Uwm{NBemP>(pHmM}9;;8`c&+_o3#E5m)JzfwN?(f-a4 zyd%xZc^oQx3XT?vcCqCX&Qrk~nu;fxs@JUoyVoi5fqpi&bUhQ2y!Ok2pzsFR(M(|U zw3E+kH_zmTRQ9dUMZWRE%Zakiwc+lgv7Z%|YO9YxAy`y28`Aw;WU6HXBgU7fl@dnt z-fFBV)}H-gqP!1;V@Je$WcbYre|dRdp{xt!7sL3Eoa%IA`5CAA%;Wq8PktwPdULo! z8!sB}Qt8#jH9Sh}QiUtEPZ6H0b*7qEKGJ%ITZ|vH)5Q^2m<7o3#Z>AKc%z7_u`rXA zqrCy{-{8;9>dfllLu$^M5L z-hXs))h*qz%~ActwkIA(qOVBZl2v4lwbM>9l70Y`+T*elINFqt#>OaVWoja8RMsep z6Or3f=oBnA3vDbn*+HNZP?8LsH2MY)x%c13@(XfuGR}R?Nu<|07{$+Lc3$Uv^I!MQ z>6qWgd-=aG2Y^24g4{Bw9ueOR)(9h`scImD=86dD+MnSN4$6 z^U*o_mE-6Rk~Dp!ANp#5RE9n*LG(Vg`1)g6!(XtDzsov$Dvz|Gv1WU68J$CkshQhS zCrc|cdkW~UK}5NeaWj^F4MSgFM+@fJd{|LLM)}_O<{rj z+?*Lm?owq?IzC%U%9EBga~h-cJbIu=#C}XuWN>OLrc%M@Gu~kFEYUi4EC6l#PR2JS zQUkGKrrS#6H7}2l0F@S11DP`@pih0WRkRJl#F;u{c&ZC{^$Z+_*lB)r)-bPgRFE;* zl)@hK4`tEP=P=il02x7-C7p%l=B`vkYjw?YhdJU9!P!jcmY$OtC^12w?vy3<<=tlY zUwHJ_0lgWN9vf>1%WACBD{UT)1qHQSE2%z|JHvP{#INr13jM}oYv_5#xsnv9`)UAO zuwgyV4YZ;O)eSc3(mka6=aRohi!HH@I#xq7kng?Acdg7S4vDJb6cI5fw?2z%3yR+| zU5v@Hm}vy;${cBp&@D=HQ9j7NcFaOYL zj-wV=eYF{|XTkFNM2uz&T8uH~;)^Zo!=KP)EVyH6s9l1~4m}N%XzPpduPg|h-&lL` zAXspR0YMOKd2yO)eMFFJ4?sQ&!`dF&!|niH*!^*Ml##o0M(0*uK9&yzekFi$+mP9s z>W9d%Jb)PtVi&-Ha!o~Iyh@KRuKpQ@)I~L*d`{O8!kRObjO7=n+Gp36fe!66neh+7 zW*l^0tTKjLLzr`x4`_8&on?mjW-PzheTNox8Hg7Nt@*SbE-%kP2hWYmHu#Fn@Q^J(SsPUz*|EgOoZ6byg3ew88UGdZ>9B2Tq=jF72ZaR=4u%1A6Vm{O#?@dD!(#tmR;eP(Fu z{$0O%=Vmua7=Gjr8nY%>ul?w=FJ76O2js&17W_iq2*tb!i{pt#`qZB#im9Rl>?t?0c zicIC}et_4d+CpVPx)i4~$u6N-QX3H77ez z?ZdvXifFk|*F8~L(W$OWM~r`pSk5}#F?j_5u$Obu9lDWIknO^AGu+Blk7!9Sb;NjS zncZA?qtASdNtzQ>z7N871IsPAk^CC?iIL}+{K|F@BuG2>qQ;_RUYV#>hHO(HUPpk@ z(bn~4|F_jiZi}Sad;_7`#4}EmD<1EiIxa48QjUuR?rC}^HRocq`OQPM@aHVKP9E#q zy%6bmHygCpIddPjE}q_DPC`VH_2m;Eey&ZH)E6xGeStOK7H)#+9y!%-Hm|QF6w#A( zIC0Yw%9j$s-#odxG~C*^MZ?M<+&WJ+@?B_QPUyTg9DJGtQN#NIC&-XddRsf3n^AL6 zT@P|H;PvN;ZpL0iv$bRb7|J{0o!Hq+S>_NrH4@coZtBJu#g8#CbR7|#?6uxi8d+$g z87apN>EciJZ`%Zv2**_uiET9Vk{pny&My;+WfGDw4EVL#B!Wiw&M|A8f1A@ z(yFQS6jfbH{b8Z-S7D2?Ixl`j0{+ZnpT=;KzVMLW{B$`N?Gw^Fl0H6lT61%T2AU**!sX0u?|I(yoy&Xveg7XBL&+>n6jd1##6d>TxE*Vj=8lWiG$4=u{1UbAa5QD>5_ z;Te^42v7K6Mmu4IWT6Rnm>oxrl~b<~^e3vbj-GCdHLIB_>59}Ya+~OF68NiH=?}2o zP(X7EN=quQn&)fK>M&kqF|<_*H`}c zk=+x)GU>{Af#vx&s?`UKUsz})g^Pc&?Ka@t5$n$bqf6{r1>#mWx6Ep>9|A}VmWRnowVo`OyCr^fHsf# zQjQ3Ttp7y#iQY8l`zEUW)(@gGQdt(~rkxlkefskT(t%@i8=|p1Y9Dc5bc+z#n$s13 zGJk|V0+&Ekh(F};PJzQKKo+FG@KV8a<$gmNSD;7rd_nRdc%?9)p!|B-@P~kxQG}~B zi|{0}@}zKC(rlFUYp*dO1RuvPC^DQOkX4<+EwvBAC{IZQdYxoq1Za!MW7%p7gGr=j zzWnAq%)^O2$eItftC#TTSArUyL$U54-O7e|)4_7%Q^2tZ^0-d&3J1}qCzR4dWX!)4 zzIEKjgnYgMus^>6uw4Jm8ga6>GBtMjpNRJ6CP~W=37~||gMo_p@GA@#-3)+cVYnU> zE5=Y4kzl+EbEh%dhQokB{gqNDqx%5*qBusWV%!iprn$S!;oN_6E3?0+umADVs4ako z?P+t?m?};gev9JXQ#Q&KBpzkHPde_CGu-y z<{}RRAx=xlv#mVi+Ibrgx~ujW$h{?zPfhz)Kp7kmYS&_|97b&H&1;J-mzrBWAvY} zh8-I8hl_RK2+nnf&}!W0P+>5?#?7>npshe<1~&l_xqKd0_>dl_^RMRq@-Myz&|TKZBj1=Q()) zF{dBjv5)h=&Z)Aevx}+i|7=R9rG^Di!sa)sZCl&ctX4&LScQ-kMncgO(9o6W6)yd< z@Rk!vkja*X_N3H=BavGoR0@u0<}m-7|2v!0+2h~S2Q&a=lTH91OJsvms2MT~ zY=c@LO5i`mLpBd(vh|)I&^A3TQLtr>w=zoyzTd=^f@TPu&+*2MtqE$Avf>l>}V|3-8Fp2hzo3y<)hr_|NO(&oSD z!vEjTWBxbKTiShVl-U{n*B3#)3a8$`{~Pk}J@elZ=>Pqp|MQ}jrGv7KrNcjW%TN_< zZz8kG{#}XoeWf7qY?D)L)8?Q-b@Na&>i=)(@uNo zr;cH98T3$Iau8Hn*@vXi{A@YehxDE2zX~o+RY`)6-X{8~hMpc#C`|8y> zU8Mnv5A0dNCf{Ims*|l-^ z(MRp{qoGohB34|ggDI*p!Aw|MFyJ|v+<+E3brfrI)|+l3W~CQLPbnF@G0)P~Ly!1TJLp}xh8uW`Q+RB-v`MRYZ9Gam3cM%{ zb4Cb*f)0deR~wtNb*8w-LlIF>kc7DAv>T0D(a3@l`k4TFnrO+g9XH7;nYOHxjc4lq zMmaW6qpgAgy)MckYMhl?>sq;-1E)-1llUneeA!ya9KM$)DaNGu57Z5aE>=VST$#vb zFo=uRHr$0M{-ha>h(D_boS4zId;3B|Tpqo|?B?Z@I?G(?&Iei+-{9L_A9=h=Qfn-U z1wIUnQe9!z%_j$F_{rf&`ZFSott09gY~qrf@g3O=Y>vzAnXCyL!@(BqWa)Zqt!#_k zfZHuwS52|&&)aK;CHq9V-t9qt0au{$#6c*R#e5n3rje0hic7c7m{kW$p(_`wB=Gw7 z4k`1Hi;Mc@yA7dp@r~?@rfw)TkjAW++|pkfOG}0N|2guek}j8Zen(!+@7?qt_7ndX zB=BG6WJ31#F3#Vk3=aQr8T)3`{=p9nBHlKzE0I@v`{vJ}h8pd6vby&VgFhzH|q;=aonunAXL6G2y(X^CtAhWr*jI zGjpY@raZDQkg*aMq}Ni6cRF z{oWv}5`nhSAv>usX}m^GHt`f(t8@zHc?K|y5Zi=4G*UG1Sza{$Dpj%X8 zzEXaKT5N6F5j4J|w#qlZP!zS7BT)9b+!ZSJdToqJts1c!)fwih4d31vfb{}W)EgcA zH2pZ^8_k$9+WD2n`6q5XbOy8>3pcYH9 z07eUB+p}YD@AH!}p!iKv><2QF-Y^&xx^PAc1F13A{nUeCDg&{hnix#FiO!fe(^&%Qcux!h znu*S!s$&nnkeotYsDthh1dq(iQrE|#f_=xVgfiiL&-5eAcC-> z5L0l|DVEM$#ulf{bj+Y~7iD)j<~O8CYM8GW)dQGq)!mck)FqoL^X zwNdZb3->hFrbHFm?hLvut-*uK?zXn3q1z|UX{RZ;-WiLoOjnle!xs+W0-8D)kjU#R z+S|A^HkRg$Ij%N4v~k`jyHffKaC~=wg=9)V5h=|kLQ@;^W!o2^K+xG&2n`XCd>OY5Ydi= zgHH=lgy++erK8&+YeTl7VNyVm9-GfONlSlVb3)V9NW5tT!cJ8d7X)!b-$fb!s76{t z@d=Vg-5K_sqHA@Zx-L_}wVnc@L@GL9_K~Zl(h5@AR#FAiKad8~KeWCo@mgXIQ#~u{ zgYFwNz}2b6Vu@CP0XoqJ+dm8px(5W5-Jpis97F`+KM)TuP*X8H@zwiVKDKGVp59pI zifNHZr|B+PG|7|Y<*tqap0CvG7tbR1R>jn70t1X`XJixiMVcHf%Ez*=xm1(CrTSDt z0cle!+{8*Ja&EOZ4@$qhBuKQ$U95Q%rc7tg$VRhk?3=pE&n+T3upZg^ZJc9~c2es% zh7>+|mrmA-p&v}|OtxqmHIBgUxL~^0+cpfkSK2mhh+4b=^F1Xgd2)}U*Yp+H?ls#z zrLxWg_hm}AfK2XYWr!rzW4g;+^^&bW%LmbtRai9f3PjU${r@n`JThy-cphbcwn)rq9{A$Ht`lmYKxOacy z6v2R(?gHhD5@&kB-Eg?4!hAoD7~(h>(R!s1c1Hx#s9vGPePUR|of32bS`J5U5w{F) z>0<^ktO2UHg<0{oxkdOQ;}coZDQph8p6ruj*_?uqURCMTac;>T#v+l1Tc~%^k-Vd@ zkc5y35jVNc49vZpZx;gG$h{%yslDI%Lqga1&&;mN{Ush1c7p>7e-(zp}6E7f-XmJb4nhk zb8zS+{IVbL$QVF8pf8}~kQ|dHJAEATmmnrb_wLG}-yHe>W|A&Y|;muy-d^t^<&)g5SJfaTH@P1%euONny=mxo+C z4N&w#biWY41r8k~468tvuYVh&XN&d#%QtIf9;iVXfWY)#j=l`&B~lqDT@28+Y!0E+MkfC}}H*#(WKKdJJq=O$vNYCb(ZG@p{fJgu;h z21oHQ(14?LeT>n5)s;uD@5&ohU!@wX8w*lB6i@GEH0pM>YTG+RAIWZD;4#F1&F%Jp zXZUml2sH0!lYJT?&sA!qwez6cXzJEd(1ZC~kT5kZSp7(@=H2$Azb_*W&6aA|9iwCL zdX7Q=42;@dspHDwYE?miGX#L^3xD&%BI&fN9^;`v4OjQXPBaBmOF1;#C)8XA(WFlH zycro;DS2?(G&6wkr6rqC>rqDv3nfGw3hmN_9Al>TgvmGsL8_hXx09};l9Ow@)F5@y z#VH5WigLDwZE4nh^7&@g{1FV^UZ%_LJ-s<{HN*2R$OPg@R~Z`c-ET*2}XB@9xvAjrK&hS=f|R8Gr9 zr|0TGOsI7RD+4+2{ZiwdVD@2zmg~g@^D--YL;6UYGSM8i$NbQr4!c7T9rg!8;TM0E zT#@?&S=t>GQm)*ua|?TLT2ktj#`|R<_*FAkOu2Pz$wEc%-=Y9V*$&dg+wIei3b*O8 z2|m$!jJG!J!ZGbbIa!(Af~oSyZV+~M1qGvelMzPNE_%5?c2>;MeeG2^N?JDKjFYCy z7SbPWH-$cWF9~fX%9~v99L!G(wi!PFp>rB!9xj7=Cv|F+7CsGNwY0Q_J%FID%C^CBZQfJ9K(HK%k31j~e#&?hQ zNuD6gRkVckU)v+53-fc} z7ZCzYN-5RG4H7;>>Hg?LU9&5_aua?A0)0dpew1#MMlu)LHe(M;OHjHIUl7|%%)YPo z0cBk;AOY00%Fe6heoN*$(b<)Cd#^8Iu;-2v@>cE-OB$icUF9EEoaC&q8z9}jMTT2I z8`9;jT%z0;dy4!8U;GW{i`)3!c6&oWY`J3669C!tM<5nQFFrFRglU8f)5Op$GtR-3 zn!+SPCw|04sv?%YZ(a7#L?vsdr7ss@WKAw&A*}-1S|9~cL%uA+E~>N6QklFE>8W|% zyX-qAUGTY1hQ-+um`2|&ji0cY*(qN!zp{YpDO-r>jPk*yuVSay<)cUt`t@&FPF_&$ zcHwu1(SQ`I-l8~vYyUxm@D1UEdFJ$f5Sw^HPH7b!9 zzYT3gKMF((N(v0#4f_jPfVZ=ApN^jQJe-X$`A?X+vWjLn_%31KXE*}5_}d8 zw_B1+a#6T1?>M{ronLbHIlEsMf93muJ7AH5h%;i99<~JX^;EAgEB1uHralD*!aJ@F zV2ruuFe9i2Q1C?^^kmVy921eb=tLDD43@-AgL^rQ3IO9%+vi_&R2^dpr}x{bCVPej z7G0-0o64uyWNtr*loIvslyo0%)KSDDKjfThe0hcqs)(C-MH1>bNGBDRTW~scy_{w} zp^aq8Qb!h9Lwielq%C1b8=?Z=&U)ST&PHbS)8Xzjh2DF?d{iAv)Eh)wsUnf>UtXN( zL7=$%YrZ#|^c{MYmhn!zV#t*(jdmYdCpwqpZ{v&L8KIuKn`@IIZfp!uo}c;7J57N` zAxyZ-uA4=Gzl~Ovycz%MW9ZL7N+nRo&1cfNn9(1H5eM;V_4Z_qVann7F>5f>%{rf= zPBZFaV@_Sobl?Fy&KXyzFDV*FIdhS5`Uc~S^Gjo)aiTHgn#<0C=9o-a-}@}xDor;D zZyZ|fvf;+=3MZd>SR1F^F`RJEZo+|MdyJYQAEauKu%WDol~ayrGU3zzbHKsnHKZ*z zFiwUkL@DZ>!*x05ql&EBq@_Vqv83&?@~q5?lVmffQZ+V-=qL+!u4Xs2Z2zdCQ3U7B&QR9_Iggy} z(om{Y9eU;IPe`+p1ifLx-XWh?wI)xU9ik+m#g&pGdB5Bi<`PR*?92lE0+TkRuXI)z z5LP!N2+tTc%cB6B1F-!fj#}>S!vnpgVU~3!*U1ej^)vjUH4s-bd^%B=ItQqDCGbrEzNQi(dJ`J}-U=2{7-d zK8k^Rlq2N#0G?9&1?HSle2vlkj^KWSBYTwx`2?9TU_DX#J+f+qLiZCqY1TXHFxXZqYMuD@RU$TgcnCC{_(vwZ-*uX)~go#%PK z@}2Km_5aQ~(<3cXeJN6|F8X_1@L%@xTzs}$_*E|a^_URF_qcF;Pfhoe?FTFwvjm1o z8onf@OY@jC2tVcMaZS;|T!Ks(wOgPpRzRnFS-^RZ4E!9dsnj9sFt609a|jJbb1Dt@ z<=Gal2jDEupxUSwWu6zp<<&RnAA;d&4gKVG0iu6g(DsST(4)z6R)zDpfaQ}v{5ARt zyhwvMtF%b-YazR5XLz+oh=mn;y-Mf2a8>7?2v8qX;19y?b>Z5laGHvzH;Nu9S`B8} zI)qN$GbXIQ1VL3lnof^6TS~rvPVg4V?Dl2Bb*K2z4E{5vy<(@@K_cN@U>R!>aUIRnb zL*)=787*cs#zb31zBC49x$`=fkQbMAef)L2$dR{)6BAz!t5U_B#1zZG`^neKSS22oJ#5B=gl%U=WeqL9REF2g zZnfCb0?quf?Ztj$VXvDSWoK`0L=Zxem2q}!XWLoT-kYMOx)!7fcgT35uC~0pySEme z`{wGWTkGr7>+Kb^n;W?BZH6ZP(9tQX%-7zF>vc2}LuWDI(9kh1G#7B99r4x6;_-V+k&c{nPUrR zAXJGRiMe~aup{0qzmLNjS_BC4cB#sXjckx{%_c&^xy{M61xEb>KW_AG5VFXUOjAG4 z^>Qlm9A#1N{4snY=(AmWzatb!ngqiqPbBZ7>Uhb3)dTkSGcL#&SH>iMO-IJBPua`u zo)LWZ>=NZLr758j{%(|uQuZ)pXq_4c!!>s|aDM9#`~1bzK3J1^^D#<2bNCccH7~-X}Ggi!pIIF>uFx%aPARGQsnC8ZQc8lrQ5o~smqOg>Ti^GNme94*w z)JZy{_{#$jxGQ&`M z!OMvZMHR>8*^>eS%o*6hJwn!l8VOOjZQJvh)@tnHVW&*GYPuxqXw}%M!(f-SQf`=L z5;=5w2;%82VMH6Xi&-K3W)o&K^+vJCepWZ-rW%+Dc6X3(){z$@4zjYxQ|}8UIojeC zYZpQ1dU{fy=oTr<4VX?$q)LP}IUmpiez^O&N3E_qPpchGTi5ZM6-2ScWlQq%V&R2Euz zO|Q0Hx>lY1Q1cW5xHv5!0OGU~PVEqSuy#fD72d#O`N!C;o=m+YioGu-wH2k6!t<~K zSr`E=W9)!g==~x9VV~-8{4ZN9{~-A9zJpRe%NGg$+MDuI-dH|b@BD)~>pPCGUNNzY zMDg||0@XGQgw`YCt5C&A{_+J}mvV9Wg{6V%2n#YSRN{AP#PY?1FF1#|vO_%e+#`|2*~wGAJaeRX6=IzFNeWhz6gJc8+(03Ph4y6ELAm=AkN7TOgMUEw*N{= z_)EIDQx5q22oUR+_b*tazu9+pX|n1c*IB-}{DqIj z-?E|ks{o3AGRNb;+iKcHkZvYJvFsW&83RAPs1Oh@IWy%l#5x2oUP6ZCtv+b|q>jsf zZ_9XO;V!>n`UxH1LvH8)L4?8raIvasEhkpQoJ`%!5rBs!0Tu(s_D{`4opB;57)pkX z4$A^8CsD3U5*!|bHIEqsn~{q+Ddj$ME@Gq4JXtgVz&7l{Ok!@?EA{B3P~NAqb9)4? zkQo30A^EbHfQ@87G5&EQTd`frrwL)&Yw?%-W@uy^Gn23%j?Y!Iea2xw<-f;esq zf%w5WN@E1}zyXtYv}}`U^B>W`>XPmdLj%4{P298|SisrE;7HvXX;A}Ffi8B#3Lr;1 zHt6zVb`8{#+e$*k?w8|O{Uh|&AG}|DG1PFo1i?Y*cQm$ZwtGcVgMwtBUDa{~L1KT-{jET4w60>{KZ27vXrHJ;fW{6| z=|Y4!&UX020wU1>1iRgB@Q#m~1^Z^9CG1LqDhYBrnx%IEdIty z!46iOoKlKs)c}newDG)rWUikD%j`)p z_w9Ph&e40=(2eBy;T!}*1p1f1SAUDP9iWy^u^Ubdj21Kn{46;GR+hwLO=4D11@c~V zI8x&(D({K~Df2E)Nx_yQvYfh4;MbMJ@Z}=Dt3_>iim~QZ*hZIlEs0mEb z_54+&*?wMD`2#vsQRN3KvoT>hWofI_Vf(^C1ff-Ike@h@saEf7g}<9T`W;HAne-Nd z>RR+&SP35w)xKn8^U$7))PsM!jKwYZ*RzEcG-OlTrX3}9a{q%#Un5E5W{{hp>w~;` zGky+3(vJvQyGwBo`tCpmo0mo((?nM8vf9aXrrY1Ve}~TuVkB(zeds^jEfI}xGBCM2 zL1|#tycSaWCurP+0MiActG3LCas@_@tao@(R1ANlwB$4K53egNE_;!&(%@Qo$>h`^1S_!hN6 z)vZtG$8fN!|BXBJ=SI>e(LAU(y(i*PHvgQ2llulxS8>qsimv7yL}0q_E5WiAz7)(f zC(ahFvG8&HN9+6^jGyLHM~$)7auppeWh_^zKk&C_MQ~8;N??OlyH~azgz5fe^>~7F zl3HnPN3z-kN)I$4@`CLCMQx3sG~V8hPS^}XDXZrQA>}mQPw%7&!sd(Pp^P=tgp-s^ zjl}1-KRPNWXgV_K^HkP__SR`S-|OF0bR-N5>I%ODj&1JUeAQ3$9i;B~$S6}*^tK?= z**%aCiH7y?xdY?{LgVP}S0HOh%0%LI$wRx;$T|~Y8R)Vdwa}kGWv8?SJVm^>r6+%I z#lj1aR94{@MP;t-scEYQWc#xFA30^}?|BeX*W#9OL;Q9#WqaaM546j5j29((^_8Nu z4uq}ESLr~r*O7E7$D{!k9W>`!SLoyA53i9QwRB{!pHe8um|aDE`Cg0O*{jmor)^t)3`>V>SWN-2VJcFmj^1?~tT=JrP`fVh*t zXHarp=8HEcR#vFe+1a%XXuK+)oFs`GDD}#Z+TJ}Ri`FvKO@ek2ayn}yaOi%(8p%2$ zpEu)v0Jym@f}U|-;}CbR=9{#<^z28PzkkTNvyKvJDZe+^VS2bES3N@Jq!-*}{oQlz z@8bgC_KnDnT4}d#&Cpr!%Yb?E!brx0!eVOw~;lLwUoz#Np%d$o%9scc3&zPm`%G((Le|6o1 zM(VhOw)!f84zG^)tZ1?Egv)d8cdNi+T${=5kV+j;Wf%2{3g@FHp^Gf*qO0q!u$=m9 zCaY`4mRqJ;FTH5`a$affE5dJrk~k`HTP_7nGTY@B9o9vvnbytaID;^b=Tzp7Q#DmD zC(XEN)Ktn39z5|G!wsVNnHi) z%^q94!lL|hF`IijA^9NR0F$@h7k5R^ljOW(;Td9grRN0Mb)l_l7##{2nPQ@?;VjXv zaLZG}yuf$r$<79rVPpXg?6iiieX|r#&`p#Con2i%S8*8F}(E) zI5E6c3tG*<;m~6>!&H!GJ6zEuhH7mkAzovdhLy;)q z{H2*8I^Pb}xC4s^6Y}6bJvMu=8>g&I)7!N!5QG$xseeU#CC?ZM-TbjsHwHgDGrsD= z{%f;@Sod+Ch66Ko2WF~;Ty)v>&x^aovCbCbD7>qF*!?BXmOV3(s|nxsb*Lx_2lpB7 zokUnzrk;P=T-&kUHO}td+Zdj!3n&NR?K~cRU zAXU!DCp?51{J4w^`cV#ye}(`SQhGQkkMu}O3M*BWt4UsC^jCFUy;wTINYmhD$AT;4 z?Xd{HaJjP`raZ39qAm;%beDbrLpbRf(mkKbANan7XsL>_pE2oo^$TgdidjRP!5-`% zv0d!|iKN$c0(T|L0C~XD0aS8t{*&#LnhE;1Kb<9&=c2B+9JeLvJr*AyyRh%@jHej=AetOMSlz^=!kxX>>B{2B1uIrQyfd8KjJ+DBy!h)~*(!|&L4^Q_07SQ~E zcemVP`{9CwFvPFu7pyVGCLhH?LhEVb2{7U+Z_>o25#+3<|8%1T^5dh}*4(kfJGry} zm%r#hU+__Z;;*4fMrX=Bkc@7|v^*B;HAl0((IBPPii%X9+u3DDF6%bI&6?Eu$8&aWVqHIM7mK6?Uvq$1|(-T|)IV<>e?!(rY zqkmO1MRaLeTR=)io(0GVtQT@s6rN%C6;nS3@eu;P#ry4q;^O@1ZKCJyp_Jo)Ty^QW z+vweTx_DLm{P-XSBj~Sl<%_b^$=}odJ!S2wAcxenmzFGX1t&Qp8Vxz2VT`uQsQYtdn&_0xVivIcxZ_hnrRtwq4cZSj1c-SG9 z7vHBCA=fd0O1<4*=lu$6pn~_pVKyL@ztw1swbZi0B?spLo56ZKu5;7ZeUml1Ws1?u zqMf1p{5myAzeX$lAi{jIUqo1g4!zWLMm9cfWcnw`k6*BR^?$2(&yW?>w;G$EmTA@a z6?y#K$C~ZT8+v{87n5Dm&H6Pb_EQ@V0IWmG9cG=O;(;5aMWWrIPzz4Q`mhK;qQp~a z+BbQrEQ+w{SeiuG-~Po5f=^EvlouB@_|4xQXH@A~KgpFHrwu%dwuCR)=B&C(y6J4J zvoGk9;lLs9%iA-IJGU#RgnZZR+@{5lYl8(e1h6&>Vc_mvg0d@);X zji4T|n#lB!>pfL|8tQYkw?U2bD`W{na&;*|znjmalA&f;*U++_aBYerq;&C8Kw7mI z7tsG*?7*5j&dU)Lje;^{D_h`%(dK|pB*A*1(Jj)w^mZ9HB|vGLkF1GEFhu&rH=r=8 zMxO42e{Si6$m+Zj`_mXb&w5Q(i|Yxyg?juUrY}78uo@~3v84|8dfgbPd0iQJRdMj< zncCNGdMEcsxu#o#B5+XD{tsg*;j-eF8`mp~K8O1J!Z0+>0=7O=4M}E?)H)ENE;P*F z$Ox?ril_^p0g7xhDUf(q652l|562VFlC8^r8?lQv;TMvn+*8I}&+hIQYh2 z1}uQQaag&!-+DZ@|C+C$bN6W;S-Z@)d1|en+XGvjbOxCa-qAF*LA=6s(Jg+g;82f$ z(Vb)8I)AH@cdjGFAR5Rqd0wiNCu!xtqWbcTx&5kslzTb^7A78~Xzw1($UV6S^VWiP zFd{Rimd-0CZC_Bu(WxBFW7+k{cOW7DxBBkJdJ;VsJ4Z@lERQr%3eVv&$%)b%<~ zCl^Y4NgO}js@u{|o~KTgH}>!* z_iDNqX2(As7T0xivMH|3SC1ivm8Q}6Ffcd7owUKN5lHAtzMM4<0v+ykUT!QiowO;`@%JGv+K$bBx@*S7C8GJVqQ_K>12}M`f_Ys=S zKFh}HM9#6Izb$Y{wYzItTy+l5U2oL%boCJn?R3?jP@n$zSIwlmyGq30Cw4QBO|14` zW5c);AN*J3&eMFAk$SR~2k|&+&Bc$e>s%c{`?d~85S-UWjA>DS5+;UKZ}5oVa5O(N zqqc@>)nee)+4MUjH?FGv%hm2{IlIF-QX}ym-7ok4Z9{V+ZHVZQl$A*x!(q%<2~iVv znUa+BX35&lCb#9VE-~Y^W_f;Xhl%vgjwdjzMy$FsSIj&ok}L+X`4>J=9BkN&nu^E*gbhj3(+D>C4E z@Fwq_=N)^bKFSHTzZk?-gNU$@l}r}dwGyh_fNi=9b|n}J>&;G!lzilbWF4B}BBq4f zYIOl?b)PSh#XTPp4IS5ZR_2C!E)Z`zH0OW%4;&~z7UAyA-X|sh9@~>cQW^COA9hV4 zXcA6qUo9P{bW1_2`eo6%hgbN%(G-F1xTvq!sc?4wN6Q4`e9Hku zFwvlAcRY?6h^Fj$R8zCNEDq8`=uZB8D-xn)tA<^bFFy}4$vA}Xq0jAsv1&5!h!yRA zU()KLJya5MQ`q&LKdH#fwq&(bNFS{sKlEh_{N%{XCGO+po#(+WCLmKW6&5iOHny>g z3*VFN?mx!16V5{zyuMWDVP8U*|BGT$(%IO|)?EF|OI*sq&RovH!N%=>i_c?K*A>>k zyg1+~++zY4Q)J;VWN0axhoIKx;l&G$gvj(#go^pZskEVj8^}is3Jw26LzYYVos0HX zRPvmK$dVxM8(Tc?pHFe0Z3uq){{#OK3i-ra#@+;*=ui8)y6hsRv z4Fxx1c1+fr!VI{L3DFMwXKrfl#Q8hfP@ajgEau&QMCxd{g#!T^;ATXW)nUg&$-n25 zruy3V!!;{?OTobo|0GAxe`Acn3GV@W=&n;~&9 zQM>NWW~R@OYORkJAo+eq1!4vzmf9K%plR4(tB@TR&FSbDoRgJ8qVcH#;7lQub*nq&?Z>7WM=oeEVjkaG zT#f)=o!M2DO5hLR+op>t0CixJCIeXH*+z{-XS|%jx)y(j&}Wo|3!l7{o)HU3m7LYyhv*xF&tq z%IN7N;D4raue&&hm0xM=`qv`+TK@;_xAcGKuK(2|75~ar2Yw)geNLSmVxV@x89bQu zpViVKKnlkwjS&&c|-X6`~xdnh}Ps)Hs z4VbUL^{XNLf7_|Oi>tA%?SG5zax}esF*FH3d(JH^Gvr7Rp*n=t7frH!U;!y1gJB^i zY_M$KL_}mW&XKaDEi9K-wZR|q*L32&m+2n_8lq$xRznJ7p8}V>w+d@?uB!eS3#u<} zIaqi!b!w}a2;_BfUUhGMy#4dPx>)_>yZ`ai?Rk`}d0>~ce-PfY-b?Csd(28yX22L% zI7XI>OjIHYTk_@Xk;Gu^F52^Gn6E1&+?4MxDS2G_#PQ&yXPXP^<-p|2nLTb@AAQEY zI*UQ9Pmm{Kat}wuazpjSyXCdnrD&|C1c5DIb1TnzF}f4KIV6D)CJ!?&l&{T)e4U%3HTSYqsQ zo@zWB1o}ceQSV)<4G<)jM|@@YpL+XHuWsr5AYh^Q{K=wSV99D~4RRU52FufmMBMmd z_H}L#qe(}|I9ZyPRD6kT>Ivj&2Y?qVZq<4bG_co_DP`sE*_Xw8D;+7QR$Uq(rr+u> z8bHUWbV19i#)@@G4bCco@Xb<8u~wVDz9S`#k@ciJtlu@uP1U0X?yov8v9U3VOig2t zL9?n$P3=1U_Emi$#slR>N5wH-=J&T=EdUHA}_Z zZIl3nvMP*AZS9{cDqFanrA~S5BqxtNm9tlu;^`)3X&V4tMAkJ4gEIPl= zoV!Gyx0N{3DpD@)pv^iS*dl2FwANu;1;%EDl}JQ7MbxLMAp>)UwNwe{=V}O-5C*>F zu?Ny+F64jZn<+fKjF01}8h5H_3pey|;%bI;SFg$w8;IC<8l|3#Lz2;mNNik6sVTG3 z+Su^rIE#40C4a-587$U~%KedEEw1%r6wdvoMwpmlXH$xPnNQN#f%Z7|p)nC>WsuO= z4zyqapLS<8(UJ~Qi9d|dQijb_xhA2)v>la)<1md5s^R1N&PiuA$^k|A<+2C?OiHbj z>Bn$~t)>Y(Zb`8hW7q9xQ=s>Rv81V+UiuZJc<23HplI88isqRCId89fb`Kt|CxVIg znWcwprwXnotO>3s&Oypkte^9yJjlUVVxSe%_xlzmje|mYOVPH^vjA=?6xd0vaj0Oz zwJ4OJNiFdnHJX3rw&inskjryukl`*fRQ#SMod5J|KroJRsVXa5_$q7whSQ{gOi*s0 z1LeCy|JBWRsDPn7jCb4s(p|JZiZ8+*ExC@Vj)MF|*Vp{B(ziccSn`G1Br9bV(v!C2 z6#?eqpJBc9o@lJ#^p-`-=`4i&wFe>2)nlPK1p9yPFzJCzBQbpkcR>={YtamIw)3nt z(QEF;+)4`>8^_LU)_Q3 zC5_7lgi_6y>U%m)m@}Ku4C}=l^J=<<7c;99ec3p{aR+v=diuJR7uZi%aQv$oP?dn?@6Yu_+*^>T0ptf(oobdL;6)N-I!TO`zg^Xbv3#L0I~sn@WGk-^SmPh5>W+LB<+1PU}AKa?FCWF|qMNELOgdxR{ zbqE7@jVe+FklzdcD$!(A$&}}H*HQFTJ+AOrJYnhh}Yvta(B zQ_bW4Rr;R~&6PAKwgLWXS{Bnln(vUI+~g#kl{r+_zbngT`Y3`^Qf=!PxN4IYX#iW4 zucW7@LLJA9Zh3(rj~&SyN_pjO8H&)|(v%!BnMWySBJV=eSkB3YSTCyIeJ{i;(oc%_hk{$_l;v>nWSB)oVeg+blh=HB5JSlG_r7@P z3q;aFoZjD_qS@zygYqCn=;Zxjo!?NK!%J$ z52lOP`8G3feEj+HTp@Tnn9X~nG=;tS+z}u{mQX_J0kxtr)O30YD%oo)L@wy`jpQYM z@M>Me=95k1p*FW~rHiV1CIfVc{K8r|#Kt(ApkXKsDG$_>76UGNhHExFCw#Ky9*B-z zNq2ga*xax!HMf_|Vp-86r{;~YgQKqu7%szk8$hpvi_2I`OVbG1doP(`gn}=W<8%Gn z%81#&WjkH4GV;4u43EtSW>K_Ta3Zj!XF?;SO3V#q=<=>Tc^@?A`i;&`-cYj|;^ zEo#Jl5zSr~_V-4}y8pnufXLa80vZY4z2ko7fj>DR)#z=wWuS1$$W!L?(y}YC+yQ|G z@L&`2upy3f>~*IquAjkVNU>}c10(fq#HdbK$~Q3l6|=@-eBbo>B9(6xV`*)sae58*f zym~RRVx;xoCG3`JV`xo z!lFw)=t2Hy)e!IFs?0~7osWk(d%^wxq&>_XD4+U#y&-VF%4z?XH^i4w`TxpF{`XhZ z%G}iEzf!T(l>g;W9<~K+)$g!{UvhW{E0Lis(S^%I8OF&%kr!gJ&fMOpM=&=Aj@wuL zBX?*6i51Qb$uhkwkFYkaD_UDE+)rh1c;(&Y=B$3)J&iJfQSx!1NGgPtK!$c9OtJuu zX(pV$bfuJpRR|K(dp@^j}i&HeJOh@|7lWo8^$*o~Xqo z5Sb+!EtJ&e@6F+h&+_1ETbg7LfP5GZjvIUIN3ibCOldAv z)>YdO|NH$x7AC8dr=<2ekiY1%fN*r~e5h6Yaw<{XIErujKV~tiyrvV_DV0AzEknC- zR^xKM3i<1UkvqBj3C{wDvytOd+YtDSGu!gEMg+!&|8BQrT*|p)(dwQLEy+ zMtMzij3zo40)CA!BKZF~yWg?#lWhqD3@qR)gh~D{uZaJO;{OWV8XZ_)J@r3=)T|kt zUS1pXr6-`!Z}w2QR7nP%d?ecf90;K_7C3d!UZ`N(TZoWNN^Q~RjVhQG{Y<%E1PpV^4 z-m-K+$A~-+VDABs^Q@U*)YvhY4Znn2^w>732H?NRK(5QSS$V@D7yz2BVX4)f5A04~$WbxGOam22>t&uD)JB8-~yiQW6ik;FGblY_I>SvB_z2?PS z*Qm&qbKI{H1V@YGWzpx`!v)WeLT02};JJo*#f$a*FH?IIad-^(;9XC#YTWN6;Z6+S zm4O1KH=#V@FJw7Pha0!9Vb%ZIM$)a`VRMoiN&C|$YA3~ZC*8ayZRY^fyuP6$n%2IU z$#XceYZeqLTXw(m$_z|33I$B4k~NZO>pP6)H_}R{E$i%USGy{l{-jOE;%CloYPEU+ zRFxOn4;7lIOh!7abb23YKD+_-?O z0FP9otcAh+oSj;=f#$&*ExUHpd&e#bSF%#8*&ItcL2H$Sa)?pt0Xtf+t)z$_u^wZi z44oE}r4kIZGy3!Mc8q$B&6JqtnHZ>Znn!Zh@6rgIu|yU+zG8q`q9%B18|T|oN3zMq z`l&D;U!OL~%>vo&q0>Y==~zLiCZk4v%s_7!9DxQ~id1LLE93gf*gg&2$|hB#j8;?3 z5v4S;oM6rT{Y;I+#FdmNw z){d%tNM<<#GN%n9ox7B=3#;u7unZ~tLB_vRZ52a&2=IM)2VkXm=L+Iqq~uk#Dug|x z>S84e+A7EiOY5lj*!q?6HDkNh~0g;0Jy(al!ZHHDtur9T$y-~)94HelX1NHjXWIM7UAe}$?jiz z9?P4`I0JM=G5K{3_%2jPLC^_Mlw?-kYYgb7`qGa3@dn|^1fRMwiyM@Ch z;CB&o7&&?c5e>h`IM;Wnha0QKnEp=$hA8TJgR-07N~U5(>9vJzeoFsSRBkDq=x(YgEMpb=l4TDD`2 zwVJpWGTA_u7}?ecW7s6%rUs&NXD3+n;jB86`X?8(l3MBo6)PdakI6V6a}22{)8ilT zM~T*mU}__xSy|6XSrJ^%lDAR3Lft%+yxC|ZUvSO_nqMX!_ul3;R#*{~4DA=h$bP)%8Yv9X zyp><|e8=_ttI}ZAwOd#dlnSjck#6%273{E$kJuCGu=I@O)&6ID{nWF5@gLb16sj|&Sb~+du4e4O_%_o`Ix4NRrAsyr1_}MuP94s>de8cH-OUkVPk3+K z&jW)It9QiU-ti~AuJkL`XMca8Oh4$SyJ=`-5WU<{cIh+XVH#e4d&zive_UHC!pN>W z3TB;Mn5i)9Qn)#6@lo4QpI3jFYc0~+jS)4AFz8fVC;lD^+idw^S~Qhq>Tg(!3$yLD zzktzoFrU@6s4wwCMz}edpF5i5Q1IMmEJQHzp(LAt)pgN3&O!&d?3W@6U4)I^2V{;- z6A(?zd93hS*uQmnh4T)nHnE{wVhh(=MMD(h(P4+^p83Om6t<*cUW>l(qJzr%5vp@K zN27ka(L{JX=1~e2^)F^i=TYj&;<7jyUUR2Bek^A8+3Up*&Xwc{)1nRR5CT8vG>ExV zHnF3UqXJOAno_?bnhCX-&kwI~Ti8t4`n0%Up>!U`ZvK^w2+0Cs-b9%w%4`$+To|k= zKtgc&l}P`*8IS>8DOe?EB84^kx4BQp3<7P{Pq}&p%xF_81pg!l2|u=&I{AuUgmF5n zJQCTLv}%}xbFGYtKfbba{CBo)lWW%Z>i(_NvLhoQZ*5-@2l&x>e+I~0Nld3UI9tdL zRzu8}i;X!h8LHVvN?C+|M81e>Jr38%&*9LYQec9Ax>?NN+9(_>XSRv&6hlCYB`>Qm z1&ygi{Y()OU4@D_jd_-7vDILR{>o|7-k)Sjdxkjgvi{@S>6GqiF|o`*Otr;P)kLHN zZkpts;0zw_6;?f(@4S1FN=m!4^mv~W+lJA`&7RH%2$)49z0A+8@0BCHtj|yH--AEL z0tW6G%X-+J+5a{5*WKaM0QDznf;V?L5&uQw+yegDNDP`hA;0XPYc6e0;Xv6|i|^F2WB)Z$LR|HR4 zTQsRAby9(^Z@yATyOgcfQw7cKyr^3Tz7lc7+JEwwzA7)|2x+PtEb>nD(tpxJQm)Kn zW9K_*r!L%~N*vS8<5T=iv|o!zTe9k_2jC_j*7ik^M_ zaf%k{WX{-;0*`t`G!&`eW;gChVXnJ-Rn)To8vW-?>>a%QU1v`ZC=U)f8iA@%JG0mZ zDqH;~mgBnrCP~1II<=V9;EBL)J+xzCoiRBaeH&J6rL!{4zIY8tZka?_FBeQeNO3q6 zyG_alW54Ba&wQf{&F1v-r1R6ID)PTsqjIBc+5MHkcW5Fnvi~{-FjKe)t1bl}Y;z@< z=!%zvpRua>>t_x}^}z0<7MI!H2v6|XAyR9!t50q-A)xk0nflgF4*OQlCGK==4S|wc zRMsSscNhRzHMBU8TdcHN!q^I}x0iXJ%uehac|Zs_B$p@CnF)HeXPpB_Za}F{<@6-4 zl%kml@}kHQ(ypD8FsPJ2=14xXJE|b20RUIgs!2|R3>LUMGF6X*B_I|$`Qg=;zm7C z{mEDy9dTmPbued7mlO@phdmAmJ7p@GR1bjCkMw6*G7#4+`k>fk1czdJUB!e@Q(~6# zwo%@p@V5RL0ABU2LH7Asq^quDUho@H>eTZH9f*no9fY0T zD_-9px3e}A!>>kv5wk91%C9R1J_Nh!*&Kk$J3KNxC}c_@zlgpJZ+5L)Nw|^p=2ue}CJtm;uj*Iqr)K})kA$xtNUEvX;4!Px*^&9T_`IN{D z{6~QY=Nau6EzpvufB^hflc#XIsSq0Y9(nf$d~6ZwK}fal92)fr%T3=q{0mP-EyP_G z)UR5h@IX}3Qll2b0oCAcBF>b*@Etu*aTLPU<%C>KoOrk=x?pN!#f_Og-w+;xbFgjQ zXp`et%lDBBh~OcFnMKMUoox0YwBNy`N0q~bSPh@+enQ=4RUw1) zpovN`QoV>vZ#5LvC;cl|6jPr}O5tu!Ipoyib8iXqy}TeJ;4+_7r<1kV0v5?Kv>fYp zg>9L`;XwXa&W7-jf|9~uP2iyF5`5AJ`Q~p4eBU$MCC00`rcSF>`&0fbd^_eqR+}mK z4n*PMMa&FOcc)vTUR zlDUAn-mh`ahi_`f`=39JYTNVjsTa_Y3b1GOIi)6dY)D}xeshB0T8Eov5%UhWd1)u}kjEQ|LDo{tqKKrYIfVz~@dp!! zMOnah@vp)%_-jDTUG09l+;{CkDCH|Q{NqX*uHa1YxFShy*1+;J`gywKaz|2Q{lG8x zP?KBur`}r`!WLKXY_K;C8$EWG>jY3UIh{+BLv0=2)KH%P}6xE2kg)%(-uA6lC?u8}{K(#P*c zE9C8t*u%j2r_{;Rpe1A{9nNXU;b_N0vNgyK!EZVut~}+R2rcbsHilqsOviYh-pYX= zHw@53nlmwYI5W5KP>&`dBZe0Jn?nAdC^HY1wlR6$u^PbpB#AS&5L6zqrXN&7*N2Q` z+Rae1EwS)H=aVSIkr8Ek^1jy2iS2o7mqm~Mr&g5=jjt7VxwglQ^`h#Mx+x2v|9ZAwE$i_9918MjJxTMr?n!bZ6n$}y11u8I9COTU`Z$Fi z!AeAQLMw^gp_{+0QTEJrhL424pVDp%wpku~XRlD3iv{vQ!lAf!_jyqd_h}+Tr1XG| z`*FT*NbPqvHCUsYAkFnM`@l4u_QH&bszpUK#M~XLJt{%?00GXY?u_{gj3Hvs!=N(I z(=AuWPijyoU!r?aFTsa8pLB&cx}$*%;K$e*XqF{~*rA-qn)h^!(-;e}O#B$|S~c+U zN4vyOK0vmtx$5K!?g*+J@G1NmlEI=pyZXZ69tAv=@`t%ag_Hk{LP~OH9iE)I= zaJ69b4kuCkV0V zo(M0#>phpQ_)@j;h%m{-a*LGi(72TP)ws2w*@4|C-3+;=5DmC4s7Lp95%n%@Ko zfdr3-a7m*dys9iIci$A=4NPJ`HfJ;hujLgU)ZRuJI`n;Pw|yksu!#LQnJ#dJysgNb z@@qwR^wrk(jbq4H?d!lNyy72~Dnn87KxsgQ!)|*m(DRM+eC$wh7KnS-mho3|KE)7h zK3k;qZ;K1Lj6uEXLYUYi)1FN}F@-xJ z@@3Hb84sl|j{4$3J}aTY@cbX@pzB_qM~APljrjju6P0tY{C@ zpUCOz_NFmALMv1*blCcwUD3?U6tYs+N%cmJ98D%3)%)Xu^uvzF zS5O!sc#X6?EwsYkvPo6A%O8&y8sCCQH<%f2togVwW&{M;PR!a(ZT_A+jVAbf{@5kL zB@Z(hb$3U{T_}SKA_CoQVU-;j>2J=L#lZ~aQCFg-d<9rzs$_gO&d5N6eFSc z1ml8)P*FSi+k@!^M9nDWR5e@ATD8oxtDu=36Iv2!;dZzidIS(PCtEuXAtlBb1;H%Z zwnC^Ek*D)EX4#Q>R$$WA2sxC_t(!!6Tr?C#@{3}n{<^o;9id1RA&-Pig1e-2B1XpG zliNjgmd3c&%A}s>qf{_j#!Z`fu0xIwm4L0)OF=u(OEmp;bLCIaZX$&J_^Z%4Sq4GZ zPn6sV_#+6pJmDN_lx@1;Zw6Md_p0w9h6mHtzpuIEwNn>OnuRSC2=>fP^Hqgc)xu^4 z<3!s`cORHJh#?!nKI`Et7{3C27+EuH)Gw1f)aoP|B3y?fuVfvpYYmmukx0ya-)TQX zR{ggy5cNf4X|g)nl#jC9p>7|09_S7>1D2GTRBUTW zAkQ=JMRogZqG#v;^=11O6@rPPwvJkr{bW-Qg8`q8GoD#K`&Y+S#%&B>SGRL>;ZunM@49!}Uy zN|bBCJ%sO;@3wl0>0gbl3L@1^O60ONObz8ZI7nder>(udj-jt`;yj^nTQ$L9`OU9W zX4alF#$|GiR47%x@s&LV>2Sz2R6?;2R~5k6V>)nz!o_*1Y!$p>BC5&?hJg_MiE6UBy>RkVZj`9UWbRkN-Hk!S`=BS3t3uyX6)7SF#)71*}`~Ogz z1rap5H6~dhBJ83;q-Y<5V35C2&F^JI-it(=5D#v!fAi9p#UwV~2tZQI+W(Dv?1t9? zfh*xpxxO{-(VGB>!Q&0%^YW_F!@aZS#ucP|YaD#>wd1Fv&Z*SR&mc;asi}1G) z_H>`!akh-Zxq9#io(7%;a$)w+{QH)Y$?UK1Dt^4)up!Szcxnu}kn$0afcfJL#IL+S z5gF_Y30j;{lNrG6m~$Ay?)*V9fZuU@3=kd40=LhazjFrau>(Y>SJNtOz>8x_X-BlA zIpl{i>OarVGj1v(4?^1`R}aQB&WCRQzS~;7R{tDZG=HhgrW@B`W|#cdyj%YBky)P= zpxuOZkW>S6%q7U{VsB#G(^FMsH5QuGXhb(sY+!-R8Bmv6Sx3WzSW<1MPPN1!&PurYky(@`bP9tz z52}LH9Q?+FF5jR6-;|+GVdRA!qtd;}*-h&iIw3Tq3qF9sDIb1FFxGbo&fbG5n8$3F zyY&PWL{ys^dTO}oZ#@sIX^BKW*bon=;te9j5k+T%wJ zNJtoN1~YVj4~YRrlZl)b&kJqp+Z`DqT!la$x&&IxgOQw#yZd-nBP3!7FijBXD|IsU8Zl^ zc6?MKpJQ+7ka|tZQLfchD$PD|;K(9FiLE|eUZX#EZxhG!S-63C$jWX1Yd!6-Yxi-u zjULIr|0-Q%D9jz}IF~S%>0(jOqZ(Ln<$9PxiySr&2Oic7vb<8q=46)Ln%Z|<*z5&> z3f~Zw@m;vR(bESB<=Jqkxn(=#hQw42l(7)h`vMQQTttz9XW6^|^8EK7qhju4r_c*b zJIi`)MB$w@9epwdIfnEBR+?~);yd6C(LeMC& zn&&N*?-g&BBJcV;8&UoZi4Lmxcj16ojlxR~zMrf=O_^i1wGb9X-0@6_rpjPYemIin zmJb+;lHe;Yp=8G)Q(L1bzH*}I>}uAqhj4;g)PlvD9_e_ScR{Ipq|$8NvAvLD8MYr}xl=bU~)f%B3E>r3Bu9_t|ThF3C5~BdOve zEbk^r&r#PT&?^V1cb{72yEWH}TXEE}w>t!cY~rA+hNOTK8FAtIEoszp!qqptS&;r$ zaYV-NX96-h$6aR@1xz6_E0^N49mU)-v#bwtGJm)ibygzJ8!7|WIrcb`$XH~^!a#s& z{Db-0IOTFq#9!^j!n_F}#Z_nX{YzBK8XLPVmc&X`fT7!@$U-@2KM9soGbmOSAmqV z{nr$L^MBo_u^Joyf0E^=eo{Rt0{{e$IFA(#*kP@SQd6lWT2-#>` zP1)7_@IO!9lk>Zt?#CU?cuhiLF&)+XEM9B)cS(gvQT!X3`wL*{fArTS;Ak`J<84du zALKPz4}3nlG8Fo^MH0L|oK2-4xIY!~Oux~1sw!+It)&D3p;+N8AgqKI`ld6v71wy8I!eP0o~=RVcFQR2Gr(eP_JbSytoQ$Yt}l*4r@A8Me94y z8cTDWhqlq^qoAhbOzGBXv^Wa4vUz$(7B!mX`T=x_ueKRRDfg&Uc-e1+z4x$jyW_Pm zp?U;-R#xt^Z8Ev~`m`iL4*c#65Nn)q#=Y0l1AuD&+{|8-Gsij3LUZXpM0Bx0u7WWm zH|%yE@-#XEph2}-$-thl+S;__ciBxSSzHveP%~v}5I%u!z_l_KoW{KRx2=eB33umE zIYFtu^5=wGU`Jab8#}cnYry@9p5UE#U|VVvx_4l49JQ;jQdp(uw=$^A$EA$LM%vmE zvdEOaIcp5qX8wX{mYf0;#51~imYYPn4=k&#DsKTxo{_Mg*;S495?OBY?#gv=edYC* z^O@-sd-qa+U24xvcbL0@C7_6o!$`)sVr-jSJE4XQUQ$?L7}2(}Eixqv;L8AdJAVqc zq}RPgpnDb@E_;?6K58r3h4-!4rT4Ab#rLHLX?eMOfluJk=3i1@Gt1i#iA=O`M0@x! z(HtJP9BMHXEzuD93m|B&woj0g6T?f#^)>J>|I4C5?Gam>n9!8CT%~aT;=oco5d6U8 zMXl(=W;$ND_8+DD*?|5bJ!;8ebESXMUKBAf7YBwNVJibGaJ*(2G`F%wx)grqVPjudiaq^Kl&g$8A2 zWMxMr@_$c}d+;_B`#kUX-t|4VKH&_f^^EP0&=DPLW)H)UzBG%%Tra*5 z%$kyZe3I&S#gfie^z5)!twG={3Cuh)FdeA!Kj<-9** zvT*5%Tb`|QbE!iW-XcOuy39>D3oe6x{>&<#E$o8Ac|j)wq#kQzz|ATd=Z0K!p2$QE zPu?jL8Lb^y3_CQE{*}sTDe!2!dtlFjq&YLY@2#4>XS`}v#PLrpvc4*@q^O{mmnr5D zmyJq~t?8>FWU5vZdE(%4cuZuao0GNjp3~Dt*SLaxI#g_u>hu@k&9Ho*#CZP~lFJHj z(e!SYlLigyc?&5-YxlE{uuk$9b&l6d`uIlpg_z15dPo*iU&|Khx2*A5Fp;8iK_bdP z?T6|^7@lcx2j0T@x>X7|kuuBSB7<^zeY~R~4McconTxA2flHC0_jFxmSTv-~?zVT| zG_|yDqa9lkF*B6_{j=T>=M8r<0s;@z#h)3BQ4NLl@`Xr__o7;~M&dL3J8fP&zLfDfy z);ckcTev{@OUlZ`bCo(-3? z1u1xD`PKgSg?RqeVVsF<1SLF;XYA@Bsa&cY!I48ZJn1V<3d!?s=St?TLo zC0cNr`qD*M#s6f~X>SCNVkva^9A2ZP>CoJ9bvgXe_c}WdX-)pHM5m7O zrHt#g$F0AO+nGA;7dSJ?)|Mo~cf{z2L)Rz!`fpi73Zv)H=a5K)*$5sf_IZypi($P5 zsPwUc4~P-J1@^3C6-r9{V-u0Z&Sl7vNfmuMY4yy*cL>_)BmQF!8Om9Dej%cHxbIzA zhtV0d{=%cr?;bpBPjt@4w=#<>k5ee=TiWAXM2~tUGfm z$s&!Dm0R^V$}fOR*B^kGaipi~rx~A2cS0;t&khV1a4u38*XRUP~f za!rZMtay8bsLt6yFYl@>-y^31(*P!L^^s@mslZy(SMsv9bVoX`O#yBgEcjCmGpyc* zeH$Dw6vB5P*;jor+JOX@;6K#+xc)Z9B8M=x2a@Wx-{snPGpRmOC$zpsqW*JCh@M2Y z#K+M(>=#d^>Of9C`))h<=Bsy)6zaMJ&x-t%&+UcpLjV`jo4R2025 zXaG8EA!0lQa)|dx-@{O)qP6`$rhCkoQqZ`^SW8g-kOwrwsK8 z3ms*AIcyj}-1x&A&vSq{r=QMyp3CHdWH35!sad#!Sm>^|-|afB+Q;|Iq@LFgqIp#Z zD1%H+3I?6RGnk&IFo|u+E0dCxXz4yI^1i!QTu7uvIEH>i3rR{srcST`LIRwdV1P;W z+%AN1NIf@xxvVLiSX`8ILA8MzNqE&7>%jMzGt9wm78bo9<;h*W84i29^w!>V>{N+S zd`5Zmz^G;f=icvoOZfK5#1ctx*~UwD=ab4DGQXehQ!XYnak*dee%YN$_ZPL%KZuz$ zD;$PpT;HM^$KwtQm@7uvT`i6>Hae1CoRVM2)NL<2-k2PiX=eAx+-6j#JI?M}(tuBW zkF%jjLR)O`gI2fcPBxF^HeI|DWwQWHVR!;;{BXXHskxh8F@BMDn`oEi-NHt;CLymW z=KSv5)3dyzec0T5B*`g-MQ<;gz=nIWKUi9ko<|4I(-E0k$QncH>E4l z**1w&#={&zv4Tvhgz#c29`m|;lU-jmaXFMC11 z*dlXDMEOG>VoLMc>!rApwOu2prKSi*!w%`yzGmS+k(zm*CsLK*wv{S_0WX^8A-rKy zbk^Gf_92^7iB_uUF)EE+ET4d|X|>d&mdN?x@vxKAQk`O+r4Qdu>XGy(a(19g;=jU} zFX{O*_NG>!$@jh!U369Lnc+D~qch3uT+_Amyi}*k#LAAwh}k8IPK5a-WZ81ufD>l> z$4cF}GSz>ce`3FAic}6W4Z7m9KGO?(eWqi@L|5Hq0@L|&2flN1PVl}XgQ2q*_n2s3 zt5KtowNkTYB5b;SVuoXA@i5irXO)A&%7?V`1@HGCB&)Wgk+l|^XXChq;u(nyPB}b3 zY>m5jkxpZgi)zfbgv&ec4Zqdvm+D<?Im*mXweS9H+V>)zF#Zp3)bhl$PbISY{5=_z!8&*Jv~NYtI-g!>fDs zmvL5O^U%!^VaKA9gvKw|5?-jk>~%CVGvctKmP$kpnpfN{D8@X*Aazi$txfa%vd-|E z>kYmV66W!lNekJPom29LdZ%(I+ZLZYTXzTg*to~m?7vp%{V<~>H+2}PQ?PPAq`36R z<%wR8v6UkS>Wt#hzGk#44W<%9S=nBfB);6clKwnxY}T*w21Qc3_?IJ@4gYzC7s;WP zVQNI(M=S=JT#xsZy7G`cR(BP9*je0bfeN8JN5~zY(DDs0t{LpHOIbN);?T-69Pf3R zSNe*&p2%AwXHL>__g+xd4Hlc_vu<25H?(`nafS%)3UPP7_4;gk-9ckt8SJRTv5v0M z_Hww`qPudL?ajIR&X*;$y-`<)6dxx1U~5eGS13CB!lX;3w7n&lDDiArbAhSycd}+b zya_3p@A`$kQy;|NJZ~s44Hqo7Hwt}X86NK=(ey>lgWTtGL6k@Gy;PbO!M%1~Wcn2k zUFP|*5d>t-X*RU8g%>|(wwj*~#l4z^Aatf^DWd1Wj#Q*AY0D^V@sC`M zjJc6qXu0I7Y*2;;gGu!plAFzG=J;1%eIOdn zQA>J&e05UN*7I5@yRhK|lbBSfJ+5Uq;!&HV@xfPZrgD}kE*1DSq^=%{o%|LChhl#0 zlMb<^a6ixzpd{kNZr|3jTGeEzuo}-eLT-)Q$#b{!vKx8Tg}swCni>{#%vDY$Ww$84 zew3c9BBovqb}_&BRo#^!G(1Eg((BScRZ}C)Oz?y`T5wOrv);)b^4XR8 zhJo7+<^7)qB>I;46!GySzdneZ>n_E1oWZY;kf94#)s)kWjuJN1c+wbVoNQcmnv}{> zN0pF+Sl3E}UQ$}slSZeLJrwT>Sr}#V(dVaezCQl2|4LN`7L7v&siYR|r7M(*JYfR$ zst3=YaDw$FSc{g}KHO&QiKxuhEzF{f%RJLKe3p*7=oo`WNP)M(9X1zIQPP0XHhY3c znrP{$4#Ol$A0s|4S7Gx2L23dv*Gv2o;h((XVn+9+$qvm}s%zi6nI-_s6?mG! zj{DV;qesJb&owKeEK?=J>UcAlYckA7Sl+I&IN=yasrZOkejir*kE@SN`fk<8Fgx*$ zy&fE6?}G)d_N`){P~U@1jRVA|2*69)KSe_}!~?+`Yb{Y=O~_+@!j<&oVQQMnhoIRU zA0CyF1OFfkK44n*JD~!2!SCPM;PRSk%1XL=0&rz00wxPs&-_eapJy#$h!eqY%nS0{ z!aGg58JIJPF3_ci%n)QSVpa2H`vIe$RD43;#IRfDV&Ibit z+?>HW4{2wOfC6Fw)}4x}i1maDxcE1qi@BS*qcxD2gE@h3#4cgU*D-&3z7D|tVZWt= z-Cy2+*Cm@P4GN_TPUtaVyVesbVDazF@)j8VJ4>XZv!f%}&eO1SvIgr}4`A*3#vat< z_MoByL(qW6L7SFZ#|Gc1fFN)L2PxY+{B8tJp+pxRyz*87)vXR}*=&ahXjBlQKguuf zX6x<<6fQulE^C*KH8~W%ptpaC0l?b=_{~*U4?5Vt;dgM4t_{&UZ1C2j?b>b+5}{IF_CUyvz-@QZPMlJ)r_tS$9kH%RPv#2_nMb zRLj5;chJ72*U`Z@Dqt4$@_+k$%|8m(HqLG!qT4P^DdfvGf&){gKnGCX#H0!;W=AGP zbA&Z`-__a)VTS}kKFjWGk z%|>yE?t*EJ!qeQ%dPk$;xIQ+P0;()PCBDgjJm6Buj{f^awNoVx+9<|lg3%-$G(*f) zll6oOkN|yamn1uyl2*N-lnqRI1cvs_JxLTeahEK=THV$Sz*gQhKNb*p0fNoda#-&F zB-qJgW^g}!TtM|0bS2QZekW7_tKu%GcJ!4?lObt0z_$mZ4rbQ0o=^curCs3bJK6sq z9fu-aW-l#>z~ca(B;4yv;2RZ?tGYAU)^)Kz{L|4oPj zdOf_?de|#yS)p2v8-N||+XL=O*%3+y)oI(HbM)Ds?q8~HPzIP(vs*G`iddbWq}! z(2!VjP&{Z1w+%eUq^ '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..8cebd10 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "temp" + diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..732adf1 --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,5 @@ +package org.example + +fun main() { + println("Hello World!") +} \ No newline at end of file From b4e70f15a33766b8b1d68ff27d074f2c2cbab07a Mon Sep 17 00:00:00 2001 From: VersusXX Date: Fri, 15 Mar 2024 20:44:47 +0300 Subject: [PATCH 04/52] fix: MIT licence badge link Fixed the incorrect MIT license link on the README badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1a9996..a15f997 100644 --- a/README.md +++ b/README.md @@ -41,5 +41,5 @@ - [7gambit7](https://github.com/7gambit7) - [VersusXX](https://github.com/VersusXX) -[license-shield]: https://img.shields.io/github/license/othneildrew/[license-url]: +[license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge: [license-url]: https://github.com/spbu-coding-2023/trees-11/blob/main/LICENSE.txt \ No newline at end of file From 242ba7435432540afb99f4f942b620437edaaf30 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Sat, 16 Mar 2024 02:33:04 +0700 Subject: [PATCH 05/52] First version of Nodes, Tree and BinaryTree. No exceptions and a lot of unsafe casts --- src/main/kotlin/BinaryTree.kt | 446 ++++++++++++++++++++++++++++++++++ src/main/kotlin/Main.kt | 17 +- 2 files changed, 461 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/BinaryTree.kt diff --git a/src/main/kotlin/BinaryTree.kt b/src/main/kotlin/BinaryTree.kt new file mode 100644 index 0000000..f06ed47 --- /dev/null +++ b/src/main/kotlin/BinaryTree.kt @@ -0,0 +1,446 @@ +import java.util.* +import kotlin.math.ceil +import kotlin.math.floor + + +open class Node, V, T> internal constructor( + val key: K, + var value: V, + internal var left: T? = null, + internal var right: T? = null, + internal var parent: T? = null) +{ + + class BinaryNode, V>( + key: K, + value: V, + left: BinaryNode? = null, + right: BinaryNode? = null, + parent: BinaryNode? = null + ) : Node>(key, value, left, right, parent) + + class RBNode, V>( + key: K, + value: V, + left: RBNode? = null, + right: RBNode? = null, + parent: RBNode? = null, + var color: Color = Color.RED + ) : Node>(key, value, left, right, parent) { + enum class Color { RED, BLACK } + } + class AVLNode, V>( + key: K, + value: V, + left: AVLNode? = null, + right: AVLNode? = null, + parent: AVLNode? = null, + var height: Int = 0 + ) : Node>(key, value, left, right, parent) + + override fun toString(): String { + return "$key $value" + } + +} + +open class Tree , V: Any, T> ( + var root: Node? = null +): Iterable> { + fun add(key: K, value: V) { + var node: Node = Node(key, value) + this.addNode(node) + } + fun addNode(node: Node) { + if (this.root == null) { + this.root = node + } else { + var isLeft: Boolean = this.root!!.key > node.key + addNode(node, + (if (isLeft) this.root!!.left else this.root!!.right) as Node?, + this.root!!, + isLeft) + } + } + + private tailrec fun addNode(node: Node, current: Node?, parent: Node, isLeft: Boolean) { + if (current == null) { + if (isLeft) parent.left = node as T + else parent.right = node as T + + node.parent = parent as T + } else { + var isLeft: Boolean = current.key > node.key + addNode(node, + (if (isLeft) current.left else current.right) as Node?, + current, + isLeft) + } + } + + operator fun set(key: K, value: V) { + if (!this.isKey(key)) { + this.add(key, value) + } + else { + this.getNode(key)!!.value = value + } + } + + operator fun get(key: K): V? { + var node: Node? = getNode(key) + return if (node == null) null else node.value + } + fun getNode(key: K): Node? { + return getNode(key, this.root) + } + + private tailrec fun getNode(key: K, current: Node?): Node? { + return if (current == null) null + else if (current.key > key) getNode(key, current.left as Node?) + else getNode(key, current.right as Node?) + } + + fun getOrDeault(key: K, default: V): V { + var node: Node? = getNode(key) + return if (node == null) default else node.value + } + fun getOrDeaultNode(key: K, default: Node): Node { + var node = getNode(key, this.root) + return if (node == null) default else node + } + + open fun delete(key: K) {} + + fun isKey(key: K): Boolean { + return isKey(key, this.root) + } + + private tailrec fun isKey(key: K, current: Node?): Boolean { + if (current == null) return false + else if (current.key == key) return true + else if (current.key > key) return isKey(key, current.left as Node?) + else return isKey(key, current.right as Node?) + } + + fun min(): Node? { + return if (root == null) null else min(root) + } + + private fun min(node: Node?): Node? { + if (node != null) { + return if (node.left == null) node else min(node.left as Node?) + } + else return null + TODO("SOME EXCEPTION I THICK") + } + + fun max(): Node? { + return if (root == null) null else max(root) + } + + private fun max(node: Node?): Node? { + if (node != null) { + println(node.value) + return if (node.right == null) node else min(node.right as Node?) + } + else return null + } + + fun getNext(node: Node): Node? { + if (node.right != null) { + // If the right subtree is not null, the successor is the leftmost node in the right subtree + return min(node.right as Node) + } + + // If the right subtree is null, the successor is the first ancestor whose left child is also an ancestor + var current = node + var parent = node.parent as? Node + while (parent != null && current == parent.right) { + + current = parent + parent = parent.parent as? Node + } + return parent + } + + fun getPrev(node: Node): Node? { + if (node.left != null) { + // If the left subtree is not null, the predecessor is the rightmost node in the left subtree + return max(node.left as? Node) + } + + // If the left subtree is null, the predecessor is the first ancestor whose right child is also an ancestor + var current = node + var parent = node.parent as? Node + while (parent != null && current == parent.left) { + current = parent + parent = parent.parent as? Node + } + return parent + } + + inner class ByKeyIterator, V: Any>(private val tree: Tree) : + Iterator> { + private var current: Node? = tree.min() + + init { + println(current) + } + + override fun hasNext(): Boolean { + return current != null + } + + override fun next(): Node { + val result = current ?: throw NoSuchElementException("No more elements") + current = tree.getNext(result) + return result + } + } + + inner class BFSIterator(tree: Tree): Iterator> { + + var queue: Queue> = LinkedList>() + + init { + var BSIqueue: Queue> = LinkedList>() + if (tree.root != null) { + BSIqueue.add(tree.root) + queue.add(tree.root) + while (!BSIqueue.isEmpty()) { + var current: Node = BSIqueue.poll() + if (current.left != null) { + BSIqueue.add(current.left as Node) + queue.add(current.left as Node) + } + if (current.right != null) { + BSIqueue.add(current.right as Node) + queue.add(current.right as Node) + } + } + } + } + + override fun hasNext(): Boolean { + return !queue.isEmpty() + } + + override fun next(): Node { + return queue.poll() + } + + } + + inner class DFSIterator(tree: Tree): Iterator> { + + var stack: Stack> = Stack>() + + init { + var DFSstack: Stack> = Stack>() + if (tree.root != null) { + DFSstack.add(tree.root) + stack.add(tree.root) + while (!DFSstack.isEmpty()) { + var current: Node = DFSstack.pop() + if (current.left != null) { + DFSstack.add(current.left as Node) + stack.add(current.left as Node) + } + if (current.right != null) { + DFSstack.add(current.right as Node) + stack.add(current.right as Node) + } + } + } + } + + override fun hasNext(): Boolean { + return !stack.isEmpty() + } + + override fun next(): Node { + return stack.pop() + } + + } + + override fun iterator(): Iterator> { + return ByKeyIterator(this) + } + + fun iterateBFS(): BFSIterator { + return BFSIterator(this) + } + + fun iterateDFS(): DFSIterator { + return DFSIterator(this) + } + + fun merge(tree: Tree) { + if (this.root == null) this.root = tree.root + else this.max()!!.right = tree.root as T? + } + + fun split(x: K): Pair, Tree> { + var lowerTree: Tree = Tree() + var biggerTree: Tree = Tree() + + for (i in this.iterateBFS()) { + if (i.key < x) lowerTree.addNode(i) + else biggerTree.addNode(i) + } + return Pair(lowerTree, biggerTree) + } + + fun clone(): Tree { + var clonedTree: Tree = Tree() + + for (i in this.iterateBFS()) clonedTree.add(i.key, i.value) + return clonedTree + } + + fun toStringBeautifulWidth(): String { + return if (this.root == null) "" + else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() + } + + private fun toStringBeautifulWidth( + prefix: StringBuilder, + isTail: Boolean, + buffer: StringBuilder, + current: Node): StringBuilder + { + if (current.right != null) { + var newPrefix = StringBuilder() + newPrefix.append(prefix) + newPrefix.append(if (isTail) "| " else " ") + this.toStringBeautifulWidth(newPrefix, false, buffer, current.right as Node) + } + buffer.append(prefix) + buffer.append(if (isTail) "└── " else "┌── ") + buffer.append(current.toString()) + buffer.append("\n") + if (current.left != null) { + var newPrefix = StringBuilder() + newPrefix.append(prefix) + newPrefix.append(if (!isTail) "| " else " ") + this.toStringBeautifulWidth(newPrefix, true, buffer, current.left as Node) + } + + return buffer + } + + fun toStringBeautifulHeight(): String { + if (this.root == null) return "" + else { + var buffer: StringBuilder = StringBuilder() + + var lines: MutableList> = mutableListOf>() + + var level: MutableList?> = mutableListOf?>() + var next: MutableList?> = mutableListOf?>() + + level.add(this.root) + + var nodeNumber: Int = 1 + var widtest: Int = 0 + + while (nodeNumber != 0) { + var line: MutableList = mutableListOf() + + nodeNumber = 0 + + for (node in level) { + if (node == null) { + line.add(null) + + next.add(null) + next.add(null) + } else { + var strNode: String = node.toString() + line.addLast(strNode) + + if (strNode.length > widtest) widtest = strNode.length + + next.add(node.left as Node?) + next.add(node.right as Node?) + + if (node.left != null) nodeNumber++ + if (node.right != null) nodeNumber++ + } + } + + widtest += widtest % 2 + + lines.add(line) + var swap = level + level = next + next = swap + next.clear() + } + + var perpiece: Int = lines[lines.size - 1].size * (widtest + 4) + + for (i in 1..perpiece / 2) buffer.append("─") + buffer.append("┐\n") + + for (i in 0..(lines.size - 1)) { + var line: MutableList = lines[i] + + var hpw: Int = floor(perpiece / 2f - 1).toInt() + + if (i > 0) { + for (j in 0..(line.size - 1)) { + var c: Char = ' ' + + if (j % 2 == 1) { + if (line[j - 1] != null) { + c = if (line[j] != null) '┴' else '┘' + } else { + if (j < line.size && line[j] != null) c = '└' + } + } + buffer.append(c) + + if (line[j] == null) { + for (k in 0..(perpiece - 2)) { + buffer.append(" ") + } + } else { + for (k in 0..(hpw - 1)) { + buffer.append(if (j % 2 == 0) " " else "─") + } + buffer.append(if (j % 2 === 0) "┌" else "┐") + for (k in 0..(hpw - 1)) { + buffer.append(if (j % 2 != 0) " " else "─") + } + } + } + buffer.append("\n") + } + for (j in 0..(line.size - 1)) { + var f: String? = line[j] + if (f == null) f = "" + var gap1: Int = ceil(perpiece / 2f - f.length / 2f).toInt() + var gap2: Int = floor(perpiece / 2f - f.length / 2f).toInt() + + for (k in 1..gap1) { + buffer.append(" ") + } + buffer.append(f) + for (k in 1..gap2) { + buffer.append(" ") + } + } + buffer.append("\n") + perpiece /= 2 + } + return buffer.toString() + } + } +} + +class BinaryTree, V: Any>(root: Node.BinaryNode? = null): Tree>(root) {} +class AVLTree, V: Any>(root: Node.AVLNode? = null): Tree>(root) {} +class RBTree, V: Any>(root: Node.RBNode? = null): Tree>(root) {} \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 732adf1..28950a2 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,5 +1,18 @@ -package org.example fun main() { - println("Hello World!") + var tree = BinaryTree() + tree.add(1, "B") + tree.add(0, "A") + tree.add(2, "C") + + var secondTree = BinaryTree() + secondTree.add(5, "E") + secondTree.add(4, "D") + secondTree.add(6, "F") + tree.merge(secondTree) + + tree.forEach( { println(it)} ) + + println(tree.toStringBeautifulWidth()) + println(tree.toStringBeautifulHeight()) } \ No newline at end of file From 6464e91d8c110564ff13ae011d1e312d3406360c Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Sat, 16 Mar 2024 17:26:59 +0700 Subject: [PATCH 06/52] Some changes in non-null operator usage and get function fix --- src/main/kotlin/BinaryTree.kt | 15 ++++++++++----- src/main/kotlin/Main.kt | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/BinaryTree.kt b/src/main/kotlin/BinaryTree.kt index f06ed47..1a6a886 100644 --- a/src/main/kotlin/BinaryTree.kt +++ b/src/main/kotlin/BinaryTree.kt @@ -55,11 +55,13 @@ open class Tree , V: Any, T> ( if (this.root == null) { this.root = node } else { - var isLeft: Boolean = this.root!!.key > node.key + var root: Node = this.root!! + // not-null assertion operator because null cheak + var isLeft: Boolean = root.key > node.key addNode(node, - (if (isLeft) this.root!!.left else this.root!!.right) as Node?, - this.root!!, - isLeft) + (if (isLeft) root.left else root.right) as Node?, + root, + isLeft) } } @@ -84,6 +86,7 @@ open class Tree , V: Any, T> ( } else { this.getNode(key)!!.value = value + // not-null assertion operator because key is exist in tree } } @@ -97,6 +100,7 @@ open class Tree , V: Any, T> ( private tailrec fun getNode(key: K, current: Node?): Node? { return if (current == null) null + else if (current.key == key) current else if (current.key > key) getNode(key, current.left as Node?) else getNode(key, current.right as Node?) } @@ -279,7 +283,7 @@ open class Tree , V: Any, T> ( fun merge(tree: Tree) { if (this.root == null) this.root = tree.root - else this.max()!!.right = tree.root as T? + else (this.max()?: throw NullPointerException("No null tree have null maximum")).right = tree.root as T? } fun split(x: K): Pair, Tree> { @@ -303,6 +307,7 @@ open class Tree , V: Any, T> ( fun toStringBeautifulWidth(): String { return if (this.root == null) "" else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() + // not-null assertion operator because null check } private fun toStringBeautifulWidth( diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 28950a2..ccf9a99 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -5,8 +5,10 @@ fun main() { tree.add(0, "A") tree.add(2, "C") + tree[0] = "A_BUT_OTHER" + var secondTree = BinaryTree() - secondTree.add(5, "E") + secondTree[5] = "E" secondTree.add(4, "D") secondTree.add(6, "F") tree.merge(secondTree) From 368088653a8d66bcde8d6d2eafe8818718e380a9 Mon Sep 17 00:00:00 2001 From: 7gambit7 <87602446+7gambit7@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:17:47 +0300 Subject: [PATCH 07/52] Add Github Actions --- .github/workflows/github-actions.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/github-actions.yml diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..15a61d6 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,18 @@ +name: GitHub Actions Demo +run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 +on: [push] +jobs: + Explore-GitHub-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v4 + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ github.workspace }} + - run: echo "🍏 This job's status is ${{ job.status }}." From c9c916ba5d68b4beaa81b8abf4f21baa1d70dd1e Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Mon, 18 Mar 2024 00:26:31 +0700 Subject: [PATCH 08/52] Create last tree method - delete. Add to node basic methods --- src/main/kotlin/BinaryTree.kt | 84 ++++++++++++++++++++++++++++++++--- src/main/kotlin/Main.kt | 4 ++ 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/BinaryTree.kt b/src/main/kotlin/BinaryTree.kt index 1a6a886..eff6d35 100644 --- a/src/main/kotlin/BinaryTree.kt +++ b/src/main/kotlin/BinaryTree.kt @@ -4,7 +4,7 @@ import kotlin.math.floor open class Node, V, T> internal constructor( - val key: K, + var key: K, var value: V, internal var left: T? = null, internal var right: T? = null, @@ -42,6 +42,14 @@ open class Node, V, T> internal constructor( return "$key $value" } + override fun equals(other: Any?): Boolean { + return if (other !is Node<*, *, *>) false else (this.key == other.key && this.value == other.value) + } + + override fun hashCode(): Int { + return super.hashCode() + } + } open class Tree , V: Any, T> ( @@ -114,7 +122,75 @@ open class Tree , V: Any, T> ( return if (node == null) default else node } - open fun delete(key: K) {} + open fun delete(key: K) { + if (this.root != null) this.delete(this.root!!) + } + + private fun delete(current: Node) { + var parent = current.parent + if (current.left == null && current.right == null) { + if (parent == null) { + this.root = null + } else if ((parent as Node).left === current) { + (parent as Node).left = null + } else { + (parent as Node).right = null + } + } else if (current.left == null || current.right == null) { + if (current.left == null) { + if (parent == null) { + this.root = current.right as Node + } else { + if ((parent as Node).left === current) { + (parent as Node).left = current.right + } else { + (parent as Node).right = current.right + } + (current.right as Node).parent = parent + } + } else { + if (parent == null) { + this.root = current.left as Node + } else { + if ((parent as Node).left === current) { + (parent as Node).left = current.left + } else { + (parent as Node).right = current.left + } + (current.left as Node).parent = parent + } + } + } else { + var next = getNext(current)!! + current.key = next.key + if ((next.parent as Node).left === next) { + (next.parent as Node).left = next.right + if (next.right != null) { + (next.right as Node).parent = next.parent + } + } else { + (next.parent as Node).right = next.right + if (next.right != null) { + (next.right as Node).parent = next.parent + } + } + + next.left = current.left + if (next.left != null) (next.left as Node).parent = current.parent + next.right = current.right + if (next.left != null) (next.right as Node).parent = current.parent + next.parent = current.parent + if (next.parent != null) { + if ((next.parent as Node).left === current) { + (next.parent as Node).left = next as T + } else { + (next.parent as Node).right = next as T + } + } else { + this.root = next + } + } + } fun isKey(key: K): Boolean { return isKey(key, this.root) @@ -446,6 +522,4 @@ open class Tree , V: Any, T> ( } } -class BinaryTree, V: Any>(root: Node.BinaryNode? = null): Tree>(root) {} -class AVLTree, V: Any>(root: Node.AVLNode? = null): Tree>(root) {} -class RBTree, V: Any>(root: Node.RBNode? = null): Tree>(root) {} \ No newline at end of file +class BinaryTree, V: Any>(root: Node.BinaryNode? = null): Tree>(root) \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index ccf9a99..eeedbd4 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -4,6 +4,7 @@ fun main() { tree.add(1, "B") tree.add(0, "A") tree.add(2, "C") + tree.add(-1, "OK") tree[0] = "A_BUT_OTHER" @@ -13,6 +14,9 @@ fun main() { secondTree.add(6, "F") tree.merge(secondTree) + tree.delete(1) + tree.delete(13) + tree.forEach( { println(it)} ) println(tree.toStringBeautifulWidth()) From 6af08964c03925209248d68fc44e1df0327ec0b3 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Tue, 19 Mar 2024 03:27:41 +0700 Subject: [PATCH 09/52] No more equal keys :( But more files to structure :) --- src/main/kotlin/BinaryTree.kt | 526 +--------------------------------- src/main/kotlin/Main.kt | 1 + src/main/kotlin/Tree.kt | 481 +++++++++++++++++++++++++++++++ 3 files changed, 484 insertions(+), 524 deletions(-) create mode 100644 src/main/kotlin/Tree.kt diff --git a/src/main/kotlin/BinaryTree.kt b/src/main/kotlin/BinaryTree.kt index eff6d35..bec2658 100644 --- a/src/main/kotlin/BinaryTree.kt +++ b/src/main/kotlin/BinaryTree.kt @@ -1,525 +1,3 @@ -import java.util.* -import kotlin.math.ceil -import kotlin.math.floor +class BinaryTree, V: Any>(root: Node.BinaryNode? = null): Tree>(root) - -open class Node, V, T> internal constructor( - var key: K, - var value: V, - internal var left: T? = null, - internal var right: T? = null, - internal var parent: T? = null) -{ - - class BinaryNode, V>( - key: K, - value: V, - left: BinaryNode? = null, - right: BinaryNode? = null, - parent: BinaryNode? = null - ) : Node>(key, value, left, right, parent) - - class RBNode, V>( - key: K, - value: V, - left: RBNode? = null, - right: RBNode? = null, - parent: RBNode? = null, - var color: Color = Color.RED - ) : Node>(key, value, left, right, parent) { - enum class Color { RED, BLACK } - } - class AVLNode, V>( - key: K, - value: V, - left: AVLNode? = null, - right: AVLNode? = null, - parent: AVLNode? = null, - var height: Int = 0 - ) : Node>(key, value, left, right, parent) - - override fun toString(): String { - return "$key $value" - } - - override fun equals(other: Any?): Boolean { - return if (other !is Node<*, *, *>) false else (this.key == other.key && this.value == other.value) - } - - override fun hashCode(): Int { - return super.hashCode() - } - -} - -open class Tree , V: Any, T> ( - var root: Node? = null -): Iterable> { - fun add(key: K, value: V) { - var node: Node = Node(key, value) - this.addNode(node) - } - fun addNode(node: Node) { - if (this.root == null) { - this.root = node - } else { - var root: Node = this.root!! - // not-null assertion operator because null cheak - var isLeft: Boolean = root.key > node.key - addNode(node, - (if (isLeft) root.left else root.right) as Node?, - root, - isLeft) - } - } - - private tailrec fun addNode(node: Node, current: Node?, parent: Node, isLeft: Boolean) { - if (current == null) { - if (isLeft) parent.left = node as T - else parent.right = node as T - - node.parent = parent as T - } else { - var isLeft: Boolean = current.key > node.key - addNode(node, - (if (isLeft) current.left else current.right) as Node?, - current, - isLeft) - } - } - - operator fun set(key: K, value: V) { - if (!this.isKey(key)) { - this.add(key, value) - } - else { - this.getNode(key)!!.value = value - // not-null assertion operator because key is exist in tree - } - } - - operator fun get(key: K): V? { - var node: Node? = getNode(key) - return if (node == null) null else node.value - } - fun getNode(key: K): Node? { - return getNode(key, this.root) - } - - private tailrec fun getNode(key: K, current: Node?): Node? { - return if (current == null) null - else if (current.key == key) current - else if (current.key > key) getNode(key, current.left as Node?) - else getNode(key, current.right as Node?) - } - - fun getOrDeault(key: K, default: V): V { - var node: Node? = getNode(key) - return if (node == null) default else node.value - } - fun getOrDeaultNode(key: K, default: Node): Node { - var node = getNode(key, this.root) - return if (node == null) default else node - } - - open fun delete(key: K) { - if (this.root != null) this.delete(this.root!!) - } - - private fun delete(current: Node) { - var parent = current.parent - if (current.left == null && current.right == null) { - if (parent == null) { - this.root = null - } else if ((parent as Node).left === current) { - (parent as Node).left = null - } else { - (parent as Node).right = null - } - } else if (current.left == null || current.right == null) { - if (current.left == null) { - if (parent == null) { - this.root = current.right as Node - } else { - if ((parent as Node).left === current) { - (parent as Node).left = current.right - } else { - (parent as Node).right = current.right - } - (current.right as Node).parent = parent - } - } else { - if (parent == null) { - this.root = current.left as Node - } else { - if ((parent as Node).left === current) { - (parent as Node).left = current.left - } else { - (parent as Node).right = current.left - } - (current.left as Node).parent = parent - } - } - } else { - var next = getNext(current)!! - current.key = next.key - if ((next.parent as Node).left === next) { - (next.parent as Node).left = next.right - if (next.right != null) { - (next.right as Node).parent = next.parent - } - } else { - (next.parent as Node).right = next.right - if (next.right != null) { - (next.right as Node).parent = next.parent - } - } - - next.left = current.left - if (next.left != null) (next.left as Node).parent = current.parent - next.right = current.right - if (next.left != null) (next.right as Node).parent = current.parent - next.parent = current.parent - if (next.parent != null) { - if ((next.parent as Node).left === current) { - (next.parent as Node).left = next as T - } else { - (next.parent as Node).right = next as T - } - } else { - this.root = next - } - } - } - - fun isKey(key: K): Boolean { - return isKey(key, this.root) - } - - private tailrec fun isKey(key: K, current: Node?): Boolean { - if (current == null) return false - else if (current.key == key) return true - else if (current.key > key) return isKey(key, current.left as Node?) - else return isKey(key, current.right as Node?) - } - - fun min(): Node? { - return if (root == null) null else min(root) - } - - private fun min(node: Node?): Node? { - if (node != null) { - return if (node.left == null) node else min(node.left as Node?) - } - else return null - TODO("SOME EXCEPTION I THICK") - } - - fun max(): Node? { - return if (root == null) null else max(root) - } - - private fun max(node: Node?): Node? { - if (node != null) { - println(node.value) - return if (node.right == null) node else min(node.right as Node?) - } - else return null - } - - fun getNext(node: Node): Node? { - if (node.right != null) { - // If the right subtree is not null, the successor is the leftmost node in the right subtree - return min(node.right as Node) - } - - // If the right subtree is null, the successor is the first ancestor whose left child is also an ancestor - var current = node - var parent = node.parent as? Node - while (parent != null && current == parent.right) { - - current = parent - parent = parent.parent as? Node - } - return parent - } - - fun getPrev(node: Node): Node? { - if (node.left != null) { - // If the left subtree is not null, the predecessor is the rightmost node in the left subtree - return max(node.left as? Node) - } - - // If the left subtree is null, the predecessor is the first ancestor whose right child is also an ancestor - var current = node - var parent = node.parent as? Node - while (parent != null && current == parent.left) { - current = parent - parent = parent.parent as? Node - } - return parent - } - - inner class ByKeyIterator, V: Any>(private val tree: Tree) : - Iterator> { - private var current: Node? = tree.min() - - init { - println(current) - } - - override fun hasNext(): Boolean { - return current != null - } - - override fun next(): Node { - val result = current ?: throw NoSuchElementException("No more elements") - current = tree.getNext(result) - return result - } - } - - inner class BFSIterator(tree: Tree): Iterator> { - - var queue: Queue> = LinkedList>() - - init { - var BSIqueue: Queue> = LinkedList>() - if (tree.root != null) { - BSIqueue.add(tree.root) - queue.add(tree.root) - while (!BSIqueue.isEmpty()) { - var current: Node = BSIqueue.poll() - if (current.left != null) { - BSIqueue.add(current.left as Node) - queue.add(current.left as Node) - } - if (current.right != null) { - BSIqueue.add(current.right as Node) - queue.add(current.right as Node) - } - } - } - } - - override fun hasNext(): Boolean { - return !queue.isEmpty() - } - - override fun next(): Node { - return queue.poll() - } - - } - - inner class DFSIterator(tree: Tree): Iterator> { - - var stack: Stack> = Stack>() - - init { - var DFSstack: Stack> = Stack>() - if (tree.root != null) { - DFSstack.add(tree.root) - stack.add(tree.root) - while (!DFSstack.isEmpty()) { - var current: Node = DFSstack.pop() - if (current.left != null) { - DFSstack.add(current.left as Node) - stack.add(current.left as Node) - } - if (current.right != null) { - DFSstack.add(current.right as Node) - stack.add(current.right as Node) - } - } - } - } - - override fun hasNext(): Boolean { - return !stack.isEmpty() - } - - override fun next(): Node { - return stack.pop() - } - - } - - override fun iterator(): Iterator> { - return ByKeyIterator(this) - } - - fun iterateBFS(): BFSIterator { - return BFSIterator(this) - } - - fun iterateDFS(): DFSIterator { - return DFSIterator(this) - } - - fun merge(tree: Tree) { - if (this.root == null) this.root = tree.root - else (this.max()?: throw NullPointerException("No null tree have null maximum")).right = tree.root as T? - } - - fun split(x: K): Pair, Tree> { - var lowerTree: Tree = Tree() - var biggerTree: Tree = Tree() - - for (i in this.iterateBFS()) { - if (i.key < x) lowerTree.addNode(i) - else biggerTree.addNode(i) - } - return Pair(lowerTree, biggerTree) - } - - fun clone(): Tree { - var clonedTree: Tree = Tree() - - for (i in this.iterateBFS()) clonedTree.add(i.key, i.value) - return clonedTree - } - - fun toStringBeautifulWidth(): String { - return if (this.root == null) "" - else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() - // not-null assertion operator because null check - } - - private fun toStringBeautifulWidth( - prefix: StringBuilder, - isTail: Boolean, - buffer: StringBuilder, - current: Node): StringBuilder - { - if (current.right != null) { - var newPrefix = StringBuilder() - newPrefix.append(prefix) - newPrefix.append(if (isTail) "| " else " ") - this.toStringBeautifulWidth(newPrefix, false, buffer, current.right as Node) - } - buffer.append(prefix) - buffer.append(if (isTail) "└── " else "┌── ") - buffer.append(current.toString()) - buffer.append("\n") - if (current.left != null) { - var newPrefix = StringBuilder() - newPrefix.append(prefix) - newPrefix.append(if (!isTail) "| " else " ") - this.toStringBeautifulWidth(newPrefix, true, buffer, current.left as Node) - } - - return buffer - } - - fun toStringBeautifulHeight(): String { - if (this.root == null) return "" - else { - var buffer: StringBuilder = StringBuilder() - - var lines: MutableList> = mutableListOf>() - - var level: MutableList?> = mutableListOf?>() - var next: MutableList?> = mutableListOf?>() - - level.add(this.root) - - var nodeNumber: Int = 1 - var widtest: Int = 0 - - while (nodeNumber != 0) { - var line: MutableList = mutableListOf() - - nodeNumber = 0 - - for (node in level) { - if (node == null) { - line.add(null) - - next.add(null) - next.add(null) - } else { - var strNode: String = node.toString() - line.addLast(strNode) - - if (strNode.length > widtest) widtest = strNode.length - - next.add(node.left as Node?) - next.add(node.right as Node?) - - if (node.left != null) nodeNumber++ - if (node.right != null) nodeNumber++ - } - } - - widtest += widtest % 2 - - lines.add(line) - var swap = level - level = next - next = swap - next.clear() - } - - var perpiece: Int = lines[lines.size - 1].size * (widtest + 4) - - for (i in 1..perpiece / 2) buffer.append("─") - buffer.append("┐\n") - - for (i in 0..(lines.size - 1)) { - var line: MutableList = lines[i] - - var hpw: Int = floor(perpiece / 2f - 1).toInt() - - if (i > 0) { - for (j in 0..(line.size - 1)) { - var c: Char = ' ' - - if (j % 2 == 1) { - if (line[j - 1] != null) { - c = if (line[j] != null) '┴' else '┘' - } else { - if (j < line.size && line[j] != null) c = '└' - } - } - buffer.append(c) - - if (line[j] == null) { - for (k in 0..(perpiece - 2)) { - buffer.append(" ") - } - } else { - for (k in 0..(hpw - 1)) { - buffer.append(if (j % 2 == 0) " " else "─") - } - buffer.append(if (j % 2 === 0) "┌" else "┐") - for (k in 0..(hpw - 1)) { - buffer.append(if (j % 2 != 0) " " else "─") - } - } - } - buffer.append("\n") - } - for (j in 0..(line.size - 1)) { - var f: String? = line[j] - if (f == null) f = "" - var gap1: Int = ceil(perpiece / 2f - f.length / 2f).toInt() - var gap2: Int = floor(perpiece / 2f - f.length / 2f).toInt() - - for (k in 1..gap1) { - buffer.append(" ") - } - buffer.append(f) - for (k in 1..gap2) { - buffer.append(" ") - } - } - buffer.append("\n") - perpiece /= 2 - } - return buffer.toString() - } - } -} - -class BinaryTree, V: Any>(root: Node.BinaryNode? = null): Tree>(root) \ No newline at end of file +// Not sure, that this file is needed, because BinaryTree is just Tree. \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index eeedbd4..87489d0 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -21,4 +21,5 @@ fun main() { println(tree.toStringBeautifulWidth()) println(tree.toStringBeautifulHeight()) + } \ No newline at end of file diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt new file mode 100644 index 0000000..d6a2d54 --- /dev/null +++ b/src/main/kotlin/Tree.kt @@ -0,0 +1,481 @@ +import java.util.* +import kotlin.math.ceil +import kotlin.math.floor + + +open class Tree , V: Any, T> ( + var root: Node? = null +): Iterable> { + fun add(key: K, value: V) { + var node: Node = Node(key, value) + this.addNode(node) + } + fun addNode(node: Node) { + if (this.root == null) { + this.root = node + } else { + var root: Node = this.root!! + // not-null assertion operator because null cheak + var isLeft: Boolean = root.key > node.key + addNode(node, + (if (isLeft) root.left else root.right) as Node?, + root, + isLeft) + } + } + + private tailrec fun addNode(node: Node, current: Node?, parent: Node, isLeft: Boolean) { + if (current == null) { + if (isLeft) parent.left = node as T + else parent.right = node as T + + node.parent = parent as T + } else { + require(current.key != node.key) { + throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function") + } + var isLeft: Boolean = current.key > node.key + addNode(node, + (if (isLeft) current.left else current.right) as Node?, + current, + isLeft) + } + } + + operator fun set(key: K, value: V) { + if (!this.isKey(key)) { + this.add(key, value) + } + else { + this.getNode(key)!!.value = value + // not-null assertion operator because key is exist in tree + } + } + + operator fun get(key: K): V? { + var node: Node? = getNode(key) + return if (node == null) null else node.value + } + fun getNode(key: K): Node? { + return getNode(key, this.root) + } + + private tailrec fun getNode(key: K, current: Node?): Node? { + return if (current == null) null + else if (current.key == key) current + else if (current.key > key) getNode(key, current.left as Node?) + else getNode(key, current.right as Node?) + } + + fun getOrDeault(key: K, default: V): V { + var node: Node? = getNode(key) + return if (node == null) default else node.value + } + fun getOrDeaultNode(key: K, default: Node): Node { + var node = getNode(key, this.root) + return if (node == null) default else node + } + + open fun delete(key: K) { + if (this.root != null) this.delete(this.root!!) + } + + private fun delete(current: Node) { + var parent = current.parent + if (current.left == null && current.right == null) { + if (parent == null) { + this.root = null + } else if ((parent as Node).left === current) { + (parent as Node).left = null + } else { + (parent as Node).right = null + } + } else if (current.left == null || current.right == null) { + if (current.left == null) { + if (parent == null) { + this.root = current.right as Node + } else { + if ((parent as Node).left === current) { + (parent as Node).left = current.right + } else { + (parent as Node).right = current.right + } + (current.right as Node).parent = parent + } + } else { + if (parent == null) { + this.root = current.left as Node + } else { + if ((parent as Node).left === current) { + (parent as Node).left = current.left + } else { + (parent as Node).right = current.left + } + (current.left as Node).parent = parent + } + } + } else { + var next = getNext(current)!! + current.key = next.key + if ((next.parent as Node).left === next) { + (next.parent as Node).left = next.right + if (next.right != null) { + (next.right as Node).parent = next.parent + } + } else { + (next.parent as Node).right = next.right + if (next.right != null) { + (next.right as Node).parent = next.parent + } + } + + next.left = current.left + if (next.left != null) (next.left as Node).parent = current.parent + next.right = current.right + if (next.left != null) (next.right as Node).parent = current.parent + next.parent = current.parent + if (next.parent != null) { + if ((next.parent as Node).left === current) { + (next.parent as Node).left = next as T + } else { + (next.parent as Node).right = next as T + } + } else { + this.root = next + } + } + } + + fun isKey(key: K): Boolean { + return isKey(key, this.root) + } + + private tailrec fun isKey(key: K, current: Node?): Boolean { + if (current == null) return false + else if (current.key == key) return true + else if (current.key > key) return isKey(key, current.left as Node?) + else return isKey(key, current.right as Node?) + } + + fun min(): Node? { + return if (root == null) null else min(root) + } + + private fun min(node: Node?): Node? { + if (node != null) { + return if (node.left == null) node else min(node.left as Node?) + } + else return null + } + + fun max(): Node? { + return if (root == null) null else max(root) + } + + private fun max(node: Node?): Node? { + if (node != null) { + println(node.value) + return if (node.right == null) node else min(node.right as Node?) + } + else return null + } + + fun getNext(node: Node): Node? { + if (node.right != null) { + // If the right subtree is not null, the successor is the leftmost node in the right subtree + return min(node.right as Node) + } + + // If the right subtree is null, the successor is the first ancestor whose left child is also an ancestor + var current = node + var parent = node.parent as? Node + while (parent != null && current == parent.right) { + + current = parent + parent = parent.parent as? Node + } + return parent + } + + fun getPrev(node: Node): Node? { + if (node.left != null) { + // If the left subtree is not null, the predecessor is the rightmost node in the left subtree + return max(node.left as? Node) + } + + // If the left subtree is null, the predecessor is the first ancestor whose right child is also an ancestor + var current = node + var parent = node.parent as? Node + while (parent != null && current == parent.left) { + current = parent + parent = parent.parent as? Node + } + return parent + } + + inner class ByKeyIterator, V: Any>(private val tree: Tree) : + Iterator> { + private var current: Node? = tree.min() + + init { + println(current) + } + + override fun hasNext(): Boolean { + return current != null + } + + override fun next(): Node { + val result = current ?: throw NoSuchElementException("No more elements") + current = tree.getNext(result) + return result + } + } + + inner class BFSIterator(tree: Tree): Iterator> { + + var queue: Queue> = LinkedList>() + + init { + var BSIqueue: Queue> = LinkedList>() + if (tree.root != null) { + BSIqueue.add(tree.root) + queue.add(tree.root) + while (!BSIqueue.isEmpty()) { + var current: Node = BSIqueue.poll() + if (current.left != null) { + BSIqueue.add(current.left as Node) + queue.add(current.left as Node) + } + if (current.right != null) { + BSIqueue.add(current.right as Node) + queue.add(current.right as Node) + } + } + } + } + + override fun hasNext(): Boolean { + return !queue.isEmpty() + } + + override fun next(): Node { + return queue.poll() + } + + } + + inner class DFSIterator(tree: Tree): Iterator> { + + var stack: Stack> = Stack>() + + init { + var DFSstack: Stack> = Stack>() + if (tree.root != null) { + DFSstack.add(tree.root) + stack.add(tree.root) + while (!DFSstack.isEmpty()) { + var current: Node = DFSstack.pop() + if (current.left != null) { + DFSstack.add(current.left as Node) + stack.add(current.left as Node) + } + if (current.right != null) { + DFSstack.add(current.right as Node) + stack.add(current.right as Node) + } + } + } + } + + override fun hasNext(): Boolean { + return !stack.isEmpty() + } + + override fun next(): Node { + return stack.pop() + } + + } + + override fun iterator(): Iterator> { + return ByKeyIterator(this) + } + + fun iterateBFS(): BFSIterator { + return BFSIterator(this) + } + + fun iterateDFS(): DFSIterator { + return DFSIterator(this) + } + + fun merge(tree: Tree) { + if (this.root != null && tree.root != null) { + require(this.max()!!.key < tree.min()!!.key) { + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" + } + } + if (this.root == null) this.root = tree.root + else this.max()!!.right = tree.root as T? + } + + fun split(x: K): Pair, Tree> { + var lowerTree: Tree = Tree() + var biggerTree: Tree = Tree() + + for (i in this.iterateBFS()) { + if (i.key < x) lowerTree.addNode(i) + else biggerTree.addNode(i) + } + return Pair(lowerTree, biggerTree) + } + + fun clone(): Tree { + var clonedTree: Tree = Tree() + + for (i in this.iterateBFS()) clonedTree.add(i.key, i.value) + return clonedTree + } + + fun toStringBeautifulWidth(): String { + return if (this.root == null) "" + else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() + // not-null assertion operator because null check + } + + private fun toStringBeautifulWidth( + prefix: StringBuilder, + isTail: Boolean, + buffer: StringBuilder, + current: Node): StringBuilder + { + if (current.right != null) { + var newPrefix = StringBuilder() + newPrefix.append(prefix) + newPrefix.append(if (isTail) "| " else " ") + this.toStringBeautifulWidth(newPrefix, false, buffer, current.right as Node) + } + buffer.append(prefix) + buffer.append(if (isTail) "└── " else "┌── ") + buffer.append(current.toString()) + buffer.append("\n") + if (current.left != null) { + var newPrefix = StringBuilder() + newPrefix.append(prefix) + newPrefix.append(if (!isTail) "| " else " ") + this.toStringBeautifulWidth(newPrefix, true, buffer, current.left as Node) + } + + return buffer + } + + fun toStringBeautifulHeight(): String { + if (this.root == null) return "" + else { + var buffer: StringBuilder = StringBuilder() + + var lines: MutableList> = mutableListOf>() + + var level: MutableList?> = mutableListOf?>() + var next: MutableList?> = mutableListOf?>() + + level.add(this.root) + + var nodeNumber: Int = 1 + var widtest: Int = 0 + + while (nodeNumber != 0) { + var line: MutableList = mutableListOf() + + nodeNumber = 0 + + for (node in level) { + if (node == null) { + line.add(null) + + next.add(null) + next.add(null) + } else { + var strNode: String = node.toString() + line.addLast(strNode) + + if (strNode.length > widtest) widtest = strNode.length + + next.add(node.left as Node?) + next.add(node.right as Node?) + + if (node.left != null) nodeNumber++ + if (node.right != null) nodeNumber++ + } + } + + widtest += widtest % 2 + + lines.add(line) + var swap = level + level = next + next = swap + next.clear() + } + + var perpiece: Int = lines[lines.size - 1].size * (widtest + 4) + + for (i in 1..perpiece / 2) buffer.append("─") + buffer.append("┐\n") + + for (i in 0..(lines.size - 1)) { + var line: MutableList = lines[i] + + var hpw: Int = floor(perpiece / 2f - 1).toInt() + + if (i > 0) { + for (j in 0..(line.size - 1)) { + var c: Char = ' ' + + if (j % 2 == 1) { + if (line[j - 1] != null) { + c = if (line[j] != null) '┴' else '┘' + } else { + if (j < line.size && line[j] != null) c = '└' + } + } + buffer.append(c) + + if (line[j] == null) { + for (k in 0..(perpiece - 2)) { + buffer.append(" ") + } + } else { + for (k in 0..(hpw - 1)) { + buffer.append(if (j % 2 == 0) " " else "─") + } + buffer.append(if (j % 2 === 0) "┌" else "┐") + for (k in 0..(hpw - 1)) { + buffer.append(if (j % 2 != 0) " " else "─") + } + } + } + buffer.append("\n") + } + for (j in 0..(line.size - 1)) { + var f: String? = line[j] + if (f == null) f = "" + var gap1: Int = ceil(perpiece / 2f - f.length / 2f).toInt() + var gap2: Int = floor(perpiece / 2f - f.length / 2f).toInt() + + for (k in 1..gap1) { + buffer.append(" ") + } + buffer.append(f) + for (k in 1..gap2) { + buffer.append(" ") + } + } + buffer.append("\n") + perpiece /= 2 + } + return buffer.toString() + } + } +} \ No newline at end of file From 28f0ea3656da1394e9cb0de3ce4ae770d38e04a8 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Tue, 19 Mar 2024 03:28:47 +0700 Subject: [PATCH 10/52] Add Nodes file to git --- src/main/kotlin/Nodes.kt | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/kotlin/Nodes.kt diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt new file mode 100644 index 0000000..b88d7d8 --- /dev/null +++ b/src/main/kotlin/Nodes.kt @@ -0,0 +1,48 @@ +open class Node, V, T> internal constructor( + var key: K, + var value: V, + internal var left: T? = null, + internal var right: T? = null, + internal var parent: T? = null) +{ + + class BinaryNode, V>( + key: K, + value: V, + left: BinaryNode? = null, + right: BinaryNode? = null, + parent: BinaryNode? = null + ) : Node>(key, value, left, right, parent) + + class RBNode, V>( + key: K, + value: V, + left: RBNode? = null, + right: RBNode? = null, + parent: RBNode? = null, + var color: Color = Color.RED + ) : Node>(key, value, left, right, parent) { + enum class Color { RED, BLACK } + } + class AVLNode, V>( + key: K, + value: V, + left: AVLNode? = null, + right: AVLNode? = null, + parent: AVLNode? = null, + var height: Int = 0 + ) : Node>(key, value, left, right, parent) + + override fun toString(): String { + return "$key $value" + } + + override fun equals(other: Any?): Boolean { + return if (other !is Node<*, *, *>) false else (this.key == other.key && this.value == other.value) + } + + override fun hashCode(): Int { + return super.hashCode() + } + +} \ No newline at end of file From 90749cd7393d422e39a280abd6993a0a0fe2db2f Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Tue, 19 Mar 2024 20:26:30 +0700 Subject: [PATCH 11/52] Delete fix and some BinaryTree tests --- src/main/kotlin/Main.kt | 28 +++++------- src/main/kotlin/Tree.kt | 31 ++++++-------- src/test/kotlin/BinaryTreeTest.kt | 71 +++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 src/test/kotlin/BinaryTreeTest.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 87489d0..ebbdf31 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,25 +1,19 @@ fun main() { var tree = BinaryTree() - tree.add(1, "B") - tree.add(0, "A") - tree.add(2, "C") - tree.add(-1, "OK") - tree[0] = "A_BUT_OTHER" - - var secondTree = BinaryTree() - secondTree[5] = "E" - secondTree.add(4, "D") - secondTree.add(6, "F") - tree.merge(secondTree) - - tree.delete(1) - tree.delete(13) - - tree.forEach( { println(it)} ) + tree.add(102, "") + tree.add(101, "") + tree.add(104, "") + tree.add(103, "") + tree.add(105, "") println(tree.toStringBeautifulWidth()) - println(tree.toStringBeautifulHeight()) + for (i in tree) { + println(i) + tree.delete(i.key) + println(tree.toStringBeautifulWidth()) + + } } \ No newline at end of file diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index d6a2d54..04fd912 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -34,11 +34,11 @@ open class Tree , V: Any, T> ( require(current.key != node.key) { throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function") } - var isLeft: Boolean = current.key > node.key + val isAddedLeft: Boolean = current.key > node.key addNode(node, - (if (isLeft) current.left else current.right) as Node?, + (if (isAddedLeft) current.left else current.right) as Node?, current, - isLeft) + isAddedLeft) } } @@ -67,17 +67,16 @@ open class Tree , V: Any, T> ( else getNode(key, current.right as Node?) } - fun getOrDeault(key: K, default: V): V { + fun getOrDefault(key: K, default: V): V { var node: Node? = getNode(key) - return if (node == null) default else node.value + return node?.value ?: default } - fun getOrDeaultNode(key: K, default: Node): Node { - var node = getNode(key, this.root) - return if (node == null) default else node + fun getOrDefaultNode(key: K, default: Node): Node { + return getNode(key, root) ?: default } open fun delete(key: K) { - if (this.root != null) this.delete(this.root!!) + if (this.getNode(key) != null) this.delete(this.getNode(key)!!) } private fun delete(current: Node) { @@ -100,8 +99,8 @@ open class Tree , V: Any, T> ( } else { (parent as Node).right = current.right } - (current.right as Node).parent = parent } + (current.right as Node).parent = parent } else { if (parent == null) { this.root = current.left as Node @@ -130,9 +129,9 @@ open class Tree , V: Any, T> ( } next.left = current.left - if (next.left != null) (next.left as Node).parent = current.parent + if (next.left != null) (next.left as Node).parent = next as T next.right = current.right - if (next.left != null) (next.right as Node).parent = current.parent + if (next.left != null) (next.right as Node).parent = next as T next.parent = current.parent if (next.parent != null) { if ((next.parent as Node).left === current) { @@ -142,6 +141,7 @@ open class Tree , V: Any, T> ( } } else { this.root = next + next.parent = null } } } @@ -174,7 +174,6 @@ open class Tree , V: Any, T> ( private fun max(node: Node?): Node? { if (node != null) { - println(node.value) return if (node.right == null) node else min(node.right as Node?) } else return null @@ -217,10 +216,6 @@ open class Tree , V: Any, T> ( Iterator> { private var current: Node? = tree.min() - init { - println(current) - } - override fun hasNext(): Boolean { return current != null } @@ -450,7 +445,7 @@ open class Tree , V: Any, T> ( for (k in 0..(hpw - 1)) { buffer.append(if (j % 2 == 0) " " else "─") } - buffer.append(if (j % 2 === 0) "┌" else "┐") + buffer.append(if (j % 2 == 0) "┌" else "┐") for (k in 0..(hpw - 1)) { buffer.append(if (j % 2 != 0) " " else "─") } diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt new file mode 100644 index 0000000..ed68b0b --- /dev/null +++ b/src/test/kotlin/BinaryTreeTest.kt @@ -0,0 +1,71 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class BinaryTreeTest { + private lateinit var tree: BinaryTree + + @BeforeEach + fun setup () { + tree = BinaryTree() + } + + @Test + fun `value should be added to empty tree`() { + val expectedResult = "A" + val key = 1 + + tree.add(key, expectedResult) + + assert(tree[key] contentEquals expectedResult) + } + + @Test + fun `root shouldn't be null after adding element to empty tree`() { + val expectedResult = "A" + val key = 1 + + tree.add(key, expectedResult) + + assert(tree.root != null) + } + @Test + fun `value should be added to not empty tree`() { + val expectedResult = "A" + val key = 1 + + tree.add(0, "") + tree.add(key, expectedResult) + + assert(tree[key] contentEquals expectedResult) + } + + @Test + fun `Value should be added after set if it not in tree`() { + val expectedResult = "A" + val key = 1 + + tree[key] = expectedResult + + assert(tree[key] contentEquals expectedResult) + } + + @Test + fun `Value should be changed after set if it in tree`() { + val firstValue = "B" + val expectedResult = "A" + val key = 1 + tree[key] = firstValue + tree[key] = expectedResult + + assert(tree[key] contentEquals expectedResult) + } + + @Test + fun `Get should return value`() { + val key = 1 + val expectedResult = "A" + tree[key] = expectedResult + assert(tree[key] contentEquals expectedResult) + } +} \ No newline at end of file From afb0f4bf4a737c10eee2d09230832119935a6903 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Tue, 19 Mar 2024 18:09:13 +0300 Subject: [PATCH 12/52] add: RBTree Added RBTree class and all of it's methods. --- src/main/kotlin/Main.kt | 41 ++++++--- src/main/kotlin/Nodes.kt | 16 ++++ src/main/kotlin/RBTree.kt | 177 ++++++++++++++++++++++++++++++++++++++ src/main/kotlin/Tree.kt | 14 +-- 4 files changed, 229 insertions(+), 19 deletions(-) create mode 100644 src/main/kotlin/RBTree.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 87489d0..205abc7 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,25 +1,42 @@ - fun main() { var tree = BinaryTree() + tree.add(1, "B") tree.add(0, "A") tree.add(2, "C") tree.add(-1, "OK") + tree.add(3, "D") + tree.add(-2, "E") + tree.add(4, "F") + tree.add(-3, "G") - tree[0] = "A_BUT_OTHER" + println(tree.toStringBeautifulWidth()) - var secondTree = BinaryTree() - secondTree[5] = "E" - secondTree.add(4, "D") - secondTree.add(6, "F") - tree.merge(secondTree) + println("\n\n") + println("RB Tree:") - tree.delete(1) - tree.delete(13) + val rbTree = RBTree() - tree.forEach( { println(it)} ) + val num = 40 + val num2 = 5 + for (i in 0..num) { + val value = "$i" + rbTree[i] = value + } + println(rbTree.toStringBeautifulWidth()) - println(tree.toStringBeautifulWidth()) - println(tree.toStringBeautifulHeight()) + + val secondRBTree = RBTree() + for (i in 1..num2) { + val value = "${num+i}" + secondRBTree[num+i] = value + } + println(secondRBTree.toStringBeautifulWidth()) + + + println("\n\n") + println("Merged:") + rbTree.merge(secondRBTree) + println(rbTree.toStringBeautifulWidth()) } \ No newline at end of file diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index b88d7d8..640d6e9 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -23,6 +23,22 @@ open class Node, V, T> internal constructor( var color: Color = Color.RED ) : Node>(key, value, left, right, parent) { enum class Color { RED, BLACK } + + fun max(): RBNode { + var current = this + while (current.right != null) { + current = current.right as RBNode + } + return current + } + + fun min(): RBNode { + var current = this + while (current.left != null) { + current = current.left as RBNode + } + return current + } } class AVLNode, V>( key: K, diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt new file mode 100644 index 0000000..9d2fb6b --- /dev/null +++ b/src/main/kotlin/RBTree.kt @@ -0,0 +1,177 @@ +class RBTree, V : Any>(root: Node.RBNode? = null) : Tree>(root) { + + override fun add(key: K, value: V) { + val node: Node> = Node.RBNode(key, value) + this.addNode(node) + } + + override fun addNode(node: Node>) { + super.addNode(node) + balanceRBTree(node as Node.RBNode) + } + + override fun merge(tree: Tree>) { + if (this.root != null && tree.root != null) { + require(this.max()!!.key < tree.min()!!.key) { + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" + } + } + if (this.root == null) this.root = tree.root + else { + tree.forEach { + this.add(it.key, it.value) + } + } + } + + override fun delete(key: K) { + val node = this.getNode(key) ?: return + if (node.left != null && node.right != null) { + val successor = node.right!!.min() + node.key = successor.key + node.value = successor.value + this.deleteNode(successor) + } else { + this.deleteNode(node as Node.RBNode) + } + } + + private fun deleteNode(node: Node.RBNode) { + val child = if (node.right != null) node.right else node.left + if (node.color == Node.RBNode.Color.BLACK) { + if (child?.color == Node.RBNode.Color.RED) { + child.color = Node.RBNode.Color.BLACK + } else { + balanceRBTree(node) + } + } + if (node.parent == null) { + root = child + } else if (node == node.parent?.left) { + node.parent?.left = child + } else { + node.parent?.right = child + } + child?.parent = node.parent + } + + override fun max(): Node.RBNode? { + return if (root == null) null else (root as Node.RBNode).max() + } + override fun min(): Node.RBNode? { + return if (root == null) null else (root as Node.RBNode).min() + } + + override fun toStringBeautifulWidth(): String { + return if (this.root == null) "" + else this.newToStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!! as Node.RBNode) + .toString() + } + + private fun newToStringBeautifulWidth( + prefix: StringBuilder, isTail: Boolean, buffer: StringBuilder, current: Node.RBNode + ): StringBuilder { + if (current.right != null) { + var newPrefix = StringBuilder() + newPrefix.append(prefix) + newPrefix.append(if (isTail) "| " else " ") + this.newToStringBeautifulWidth(newPrefix, false, buffer, current.right as Node.RBNode) + } + buffer.append(prefix) + buffer.append(if (isTail) "└── " else "┌── ") + buffer.append("${current.key}(${current.color})") + buffer.append("\n") + if (current.left != null) { + var newPrefix = StringBuilder() + newPrefix.append(prefix) + newPrefix.append(if (!isTail) "| " else " ") + this.newToStringBeautifulWidth(newPrefix, true, buffer, current.left as Node.RBNode) + } + + return buffer + } + + private fun balanceRBTree(node: Node.RBNode) { + if (node.parent == null) { + node.color = Node.RBNode.Color.BLACK + return + } + if (node.parent!!.color == Node.RBNode.Color.BLACK) { + return + } + if (node.parent!!.color == Node.RBNode.Color.RED) { + if (node.parent!!.parent!!.left == node.parent) { + val uncle = node.parent!!.parent!!.right + if (uncle?.color == Node.RBNode.Color.RED) { + node.parent!!.color = Node.RBNode.Color.BLACK + uncle.color = Node.RBNode.Color.BLACK + node.parent!!.parent!!.color = Node.RBNode.Color.RED + balanceRBTree(node.parent!!.parent!!) + } else { + if (node.parent!!.right == node) { + node.parent!!.leftRotate() + balanceRBTree(node.left!!) + } else { + node.parent!!.parent!!.rightRotate() + node.parent!!.color = Node.RBNode.Color.BLACK + node.parent!!.right!!.color = Node.RBNode.Color.RED + } + } + } else { + val uncle = node.parent!!.parent!!.left + if (uncle?.color == Node.RBNode.Color.RED) { + node.parent!!.color = Node.RBNode.Color.BLACK + uncle.color = Node.RBNode.Color.BLACK + node.parent!!.parent!!.color = Node.RBNode.Color.RED + balanceRBTree(node.parent!!.parent!!) + } else { + if (node.parent!!.left == node) { + node.parent!!.rightRotate() + balanceRBTree(node.right!!) + } else { + node.parent!!.parent!!.leftRotate() + node.parent!!.color = Node.RBNode.Color.BLACK + node.parent!!.left!!.color = Node.RBNode.Color.RED + } + } + } + } + } + + private fun Node.RBNode.leftRotate() { + val newRoot = this.right + this.right = newRoot?.left + if (newRoot?.left != null) { + newRoot.left!!.parent = this + } + newRoot?.parent = this.parent + if (this.parent == null) { + root = newRoot + } else if (this == this.parent?.left) { + this.parent?.left = newRoot + } else { + this.parent?.right = newRoot + } + newRoot?.left = this + this.parent = newRoot + } + + private fun Node.RBNode.rightRotate() { + val newRoot = this.left + this.left = newRoot?.right + if (newRoot?.right != null) { + newRoot.right!!.parent = this + } + newRoot?.parent = this.parent + if (this.parent == null) { + root = newRoot + } else if (this == this.parent?.right) { + this.parent?.right = newRoot + } else { + this.parent?.left = newRoot + } + newRoot?.right = this + this.parent = newRoot + } + +} \ No newline at end of file diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index d6a2d54..da274e5 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -6,16 +6,16 @@ import kotlin.math.floor open class Tree , V: Any, T> ( var root: Node? = null ): Iterable> { - fun add(key: K, value: V) { + open fun add(key: K, value: V) { var node: Node = Node(key, value) this.addNode(node) } - fun addNode(node: Node) { + open fun addNode(node: Node) { if (this.root == null) { this.root = node } else { var root: Node = this.root!! - // not-null assertion operator because null cheak + // not-null assertion operator because null check var isLeft: Boolean = root.key > node.key addNode(node, (if (isLeft) root.left else root.right) as Node?, @@ -157,7 +157,7 @@ open class Tree , V: Any, T> ( else return isKey(key, current.right as Node?) } - fun min(): Node? { + open fun min(): Node? { return if (root == null) null else min(root) } @@ -168,7 +168,7 @@ open class Tree , V: Any, T> ( else return null } - fun max(): Node? { + open fun max(): Node? { return if (root == null) null else max(root) } @@ -310,7 +310,7 @@ open class Tree , V: Any, T> ( return DFSIterator(this) } - fun merge(tree: Tree) { + open fun merge(tree: Tree) { if (this.root != null && tree.root != null) { require(this.max()!!.key < tree.min()!!.key) { "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" @@ -338,7 +338,7 @@ open class Tree , V: Any, T> ( return clonedTree } - fun toStringBeautifulWidth(): String { + open fun toStringBeautifulWidth(): String { return if (this.root == null) "" else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() // not-null assertion operator because null check From 96c392396351505ae55ec4c01c3c3e738c59d113 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Wed, 20 Mar 2024 03:01:51 +0700 Subject: [PATCH 13/52] NO MORE UNSAFE CASTS. Generic type added --- src/main/kotlin/Main.kt | 11 +-- src/main/kotlin/Nodes.kt | 8 +- src/main/kotlin/Tree.kt | 201 +++++++++++++++++++-------------------- 3 files changed, 105 insertions(+), 115 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index ebbdf31..5e1f9b1 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -2,18 +2,11 @@ fun main() { var tree = BinaryTree() - tree.add(102, "") - tree.add(101, "") - tree.add(104, "") - tree.add(103, "") - tree.add(105, "") + for (i in 1..10) tree.add(i * i * (i - 10), "") println(tree.toStringBeautifulWidth()) - - for (i in tree) { - println(i) + for (i in tree){ tree.delete(i.key) println(tree.toStringBeautifulWidth()) - } } \ No newline at end of file diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index b88d7d8..d3ad0e8 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -1,9 +1,9 @@ -open class Node, V, T> internal constructor( +open class Node, V, T: Node> internal constructor( var key: K, var value: V, - internal var left: T? = null, - internal var right: T? = null, - internal var parent: T? = null) + internal var left: Node? = null, + internal var right: Node? = null, + internal var parent: Node? = null) { class BinaryNode, V>( diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 04fd912..f709134 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -3,40 +3,40 @@ import kotlin.math.ceil import kotlin.math.floor -open class Tree , V: Any, T> ( +open class Tree , V: Any, T: Node> ( var root: Node? = null ): Iterable> { - fun add(key: K, value: V) { - var node: Node = Node(key, value) + open fun add(key: K, value: V) { + val node: Node = Node(key, value) this.addNode(node) } - fun addNode(node: Node) { + open fun addNode(node: Node) { if (this.root == null) { this.root = node } else { - var root: Node = this.root!! - // not-null assertion operator because null cheak - var isLeft: Boolean = root.key > node.key + val root: Node = this.root!! + // not-null assertion operator because null check + val isLeft: Boolean = root.key > node.key addNode(node, - (if (isLeft) root.left else root.right) as Node?, - root, - isLeft) + (if (isLeft) root.left else root.right), + root, + isLeft) } } private tailrec fun addNode(node: Node, current: Node?, parent: Node, isLeft: Boolean) { if (current == null) { - if (isLeft) parent.left = node as T - else parent.right = node as T + if (isLeft) parent.left = node + else parent.right = node - node.parent = parent as T + node.parent = parent } else { require(current.key != node.key) { throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function") } val isAddedLeft: Boolean = current.key > node.key addNode(node, - (if (isAddedLeft) current.left else current.right) as Node?, + (if (isAddedLeft) current.left else current.right), current, isAddedLeft) } @@ -52,10 +52,8 @@ open class Tree , V: Any, T> ( } } - operator fun get(key: K): V? { - var node: Node? = getNode(key) - return if (node == null) null else node.value - } + operator fun get(key: K): V? = getNode(key)?.value + fun getNode(key: K): Node? { return getNode(key, this.root) } @@ -63,12 +61,12 @@ open class Tree , V: Any, T> ( private tailrec fun getNode(key: K, current: Node?): Node? { return if (current == null) null else if (current.key == key) current - else if (current.key > key) getNode(key, current.left as Node?) - else getNode(key, current.right as Node?) + else if (current.key > key) getNode(key, current.left) + else getNode(key, current.right) } fun getOrDefault(key: K, default: V): V { - var node: Node? = getNode(key) + val node: Node? = getNode(key) return node?.value ?: default } fun getOrDefaultNode(key: K, default: Node): Node { @@ -80,24 +78,24 @@ open class Tree , V: Any, T> ( } private fun delete(current: Node) { - var parent = current.parent + val parent = current.parent if (current.left == null && current.right == null) { if (parent == null) { this.root = null - } else if ((parent as Node).left === current) { - (parent as Node).left = null + } else if (parent.left === current) { + parent.left = null } else { - (parent as Node).right = null + parent.right = null } } else if (current.left == null || current.right == null) { if (current.left == null) { if (parent == null) { this.root = current.right as Node } else { - if ((parent as Node).left === current) { - (parent as Node).left = current.right + if (parent.left === current) { + parent.left = current.right } else { - (parent as Node).right = current.right + parent.right = current.right } } (current.right as Node).parent = parent @@ -105,39 +103,39 @@ open class Tree , V: Any, T> ( if (parent == null) { this.root = current.left as Node } else { - if ((parent as Node).left === current) { - (parent as Node).left = current.left + if (parent.left === current) { + parent.left = current.left } else { - (parent as Node).right = current.left + parent.right = current.left } - (current.left as Node).parent = parent + parent.parent = parent } } } else { - var next = getNext(current)!! + val next = getNext(current)!! current.key = next.key - if ((next.parent as Node).left === next) { - (next.parent as Node).left = next.right + if (next.parent!!.left === next) { + next.parent!!.left = next.right if (next.right != null) { - (next.right as Node).parent = next.parent + next.right!!.parent = next.parent } } else { - (next.parent as Node).right = next.right + next.parent!!.right = next.right if (next.right != null) { - (next.right as Node).parent = next.parent + next.right!!.parent = next.parent } } next.left = current.left - if (next.left != null) (next.left as Node).parent = next as T + if (next.left != null) next.left!!.parent = next next.right = current.right - if (next.left != null) (next.right as Node).parent = next as T + if (next.left != null) next.right!!.parent = next next.parent = current.parent if (next.parent != null) { - if ((next.parent as Node).left === current) { - (next.parent as Node).left = next as T + if (next.parent!!.left === current) { + next.parent!!.left = next } else { - (next.parent as Node).right = next as T + next.parent!!.right = next } } else { this.root = next @@ -153,45 +151,42 @@ open class Tree , V: Any, T> ( private tailrec fun isKey(key: K, current: Node?): Boolean { if (current == null) return false else if (current.key == key) return true - else if (current.key > key) return isKey(key, current.left as Node?) - else return isKey(key, current.right as Node?) + else if (current.key > key) return isKey(key, current.left) + else return isKey(key, current.right) } - fun min(): Node? { + open fun min(): Node? { return if (root == null) null else min(root) } private fun min(node: Node?): Node? { - if (node != null) { - return if (node.left == null) node else min(node.left as Node?) - } - else return null + return if (node != null) { + if (node.left == null) node else min(node.left) + } else null } - fun max(): Node? { + open fun max(): Node? { return if (root == null) null else max(root) } private fun max(node: Node?): Node? { - if (node != null) { - return if (node.right == null) node else min(node.right as Node?) - } - else return null + return if (node != null) { + if (node.right == null) node else min(node.right) + } else null } fun getNext(node: Node): Node? { if (node.right != null) { // If the right subtree is not null, the successor is the leftmost node in the right subtree - return min(node.right as Node) + return min(node.right) } // If the right subtree is null, the successor is the first ancestor whose left child is also an ancestor var current = node - var parent = node.parent as? Node + var parent = node.parent while (parent != null && current == parent.right) { - current = parent - parent = parent.parent as? Node + parent = parent.parent } return parent } @@ -199,21 +194,22 @@ open class Tree , V: Any, T> ( fun getPrev(node: Node): Node? { if (node.left != null) { // If the left subtree is not null, the predecessor is the rightmost node in the left subtree - return max(node.left as? Node) + return max(node.left) } // If the left subtree is null, the predecessor is the first ancestor whose right child is also an ancestor var current = node - var parent = node.parent as? Node + var parent = node.parent while (parent != null && current == parent.left) { current = parent - parent = parent.parent as? Node + parent = parent.parent } return parent } - inner class ByKeyIterator, V: Any>(private val tree: Tree) : - Iterator> { + inner class ByKeyIterator( + private val tree: Tree + ) : Iterator> { private var current: Node? = tree.min() override fun hasNext(): Boolean { @@ -227,24 +223,25 @@ open class Tree , V: Any, T> ( } } + inner class BFSIterator(tree: Tree): Iterator> { - var queue: Queue> = LinkedList>() + private var queue: Queue> = LinkedList() init { - var BSIqueue: Queue> = LinkedList>() + val BSIqueue: Queue> = LinkedList() if (tree.root != null) { BSIqueue.add(tree.root) queue.add(tree.root) while (!BSIqueue.isEmpty()) { - var current: Node = BSIqueue.poll() + val current: Node = BSIqueue.poll() if (current.left != null) { - BSIqueue.add(current.left as Node) - queue.add(current.left as Node) + BSIqueue.add(current.left) + queue.add(current.left) } if (current.right != null) { - BSIqueue.add(current.right as Node) - queue.add(current.right as Node) + BSIqueue.add(current.right) + queue.add(current.right) } } } @@ -262,15 +259,15 @@ open class Tree , V: Any, T> ( inner class DFSIterator(tree: Tree): Iterator> { - var stack: Stack> = Stack>() + private var stack: Stack> = Stack>() init { - var DFSstack: Stack> = Stack>() + val DFSstack: Stack> = Stack>() if (tree.root != null) { DFSstack.add(tree.root) stack.add(tree.root) while (!DFSstack.isEmpty()) { - var current: Node = DFSstack.pop() + val current: Node = DFSstack.pop() if (current.left != null) { DFSstack.add(current.left as Node) stack.add(current.left as Node) @@ -305,19 +302,19 @@ open class Tree , V: Any, T> ( return DFSIterator(this) } - fun merge(tree: Tree) { + open fun merge(tree: Tree) { if (this.root != null && tree.root != null) { require(this.max()!!.key < tree.min()!!.key) { "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" } } if (this.root == null) this.root = tree.root - else this.max()!!.right = tree.root as T? + else this.max()!!.right = tree.root } fun split(x: K): Pair, Tree> { - var lowerTree: Tree = Tree() - var biggerTree: Tree = Tree() + val lowerTree: Tree = Tree() + val biggerTree: Tree = Tree() for (i in this.iterateBFS()) { if (i.key < x) lowerTree.addNode(i) @@ -327,13 +324,13 @@ open class Tree , V: Any, T> ( } fun clone(): Tree { - var clonedTree: Tree = Tree() + val clonedTree: Tree = Tree() for (i in this.iterateBFS()) clonedTree.add(i.key, i.value) return clonedTree } - fun toStringBeautifulWidth(): String { + open fun toStringBeautifulWidth(): String { return if (this.root == null) "" else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() // not-null assertion operator because null check @@ -346,7 +343,7 @@ open class Tree , V: Any, T> ( current: Node): StringBuilder { if (current.right != null) { - var newPrefix = StringBuilder() + val newPrefix = StringBuilder() newPrefix.append(prefix) newPrefix.append(if (isTail) "| " else " ") this.toStringBeautifulWidth(newPrefix, false, buffer, current.right as Node) @@ -356,7 +353,7 @@ open class Tree , V: Any, T> ( buffer.append(current.toString()) buffer.append("\n") if (current.left != null) { - var newPrefix = StringBuilder() + val newPrefix = StringBuilder() newPrefix.append(prefix) newPrefix.append(if (!isTail) "| " else " ") this.toStringBeautifulWidth(newPrefix, true, buffer, current.left as Node) @@ -368,20 +365,20 @@ open class Tree , V: Any, T> ( fun toStringBeautifulHeight(): String { if (this.root == null) return "" else { - var buffer: StringBuilder = StringBuilder() + val buffer: StringBuilder = StringBuilder() - var lines: MutableList> = mutableListOf>() + val lines: MutableList> = mutableListOf() - var level: MutableList?> = mutableListOf?>() - var next: MutableList?> = mutableListOf?>() + var level: MutableList?> = mutableListOf() + var next: MutableList?> = mutableListOf() level.add(this.root) - var nodeNumber: Int = 1 - var widtest: Int = 0 + var nodeNumber = 1 + var widtest = 0 while (nodeNumber != 0) { - var line: MutableList = mutableListOf() + val line: MutableList = mutableListOf() nodeNumber = 0 @@ -392,13 +389,13 @@ open class Tree , V: Any, T> ( next.add(null) next.add(null) } else { - var strNode: String = node.toString() + val strNode: String = node.toString() line.addLast(strNode) if (strNode.length > widtest) widtest = strNode.length - next.add(node.left as Node?) - next.add(node.right as Node?) + next.add(node.left) + next.add(node.right) if (node.left != null) nodeNumber++ if (node.right != null) nodeNumber++ @@ -408,7 +405,7 @@ open class Tree , V: Any, T> ( widtest += widtest % 2 lines.add(line) - var swap = level + val swap = level level = next next = swap next.clear() @@ -419,13 +416,13 @@ open class Tree , V: Any, T> ( for (i in 1..perpiece / 2) buffer.append("─") buffer.append("┐\n") - for (i in 0..(lines.size - 1)) { - var line: MutableList = lines[i] + for (i in 0.. = lines[i] - var hpw: Int = floor(perpiece / 2f - 1).toInt() + val hpw: Int = floor(perpiece / 2f - 1).toInt() if (i > 0) { - for (j in 0..(line.size - 1)) { + for (j in 0.., V: Any, T> ( buffer.append(" ") } } else { - for (k in 0..(hpw - 1)) { + for (k in 0.. Date: Tue, 19 Mar 2024 23:50:46 +0300 Subject: [PATCH 14/52] fix: RBTree methods casts Fixed RBTree cast errors due to change of the Node parameter types. --- src/main/kotlin/RBTree.kt | 84 ++++++++++++++++++++------------------- src/main/kotlin/Tree.kt | 9 ++--- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 9d2fb6b..8aa5a9d 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -12,7 +12,7 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree override fun merge(tree: Tree>) { if (this.root != null && tree.root != null) { - require(this.max()!!.key < tree.min()!!.key) { + require(this.max()!!.key < tree.min()?.key!!) { "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" } } @@ -27,7 +27,7 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree override fun delete(key: K) { val node = this.getNode(key) ?: return if (node.left != null && node.right != null) { - val successor = node.right!!.min() + val successor = (node.right as Node.RBNode).min() node.key = successor.key node.value = successor.value this.deleteNode(successor) @@ -39,8 +39,8 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree private fun deleteNode(node: Node.RBNode) { val child = if (node.right != null) node.right else node.left if (node.color == Node.RBNode.Color.BLACK) { - if (child?.color == Node.RBNode.Color.RED) { - child.color = Node.RBNode.Color.BLACK + if ((child as Node.RBNode?)?.color == Node.RBNode.Color.RED) { + (child as Node.RBNode).color = Node.RBNode.Color.BLACK } else { balanceRBTree(node) } @@ -58,15 +58,16 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree override fun max(): Node.RBNode? { return if (root == null) null else (root as Node.RBNode).max() } + override fun min(): Node.RBNode? { return if (root == null) null else (root as Node.RBNode).min() } - override fun toStringBeautifulWidth(): String { - return if (this.root == null) "" - else this.newToStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!! as Node.RBNode) - .toString() - } +// override fun toStringBeautifulWidth(): String { +// return if (this.root == null) "" +// else this.newToStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!! as Node.RBNode) +// .toString() +// } private fun newToStringBeautifulWidth( prefix: StringBuilder, isTail: Boolean, buffer: StringBuilder, current: Node.RBNode @@ -96,43 +97,46 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree node.color = Node.RBNode.Color.BLACK return } - if (node.parent!!.color == Node.RBNode.Color.BLACK) { + + val parentNode = node.parent as? Node.RBNode ?: return + + if (parentNode.color == Node.RBNode.Color.BLACK) { return } - if (node.parent!!.color == Node.RBNode.Color.RED) { - if (node.parent!!.parent!!.left == node.parent) { - val uncle = node.parent!!.parent!!.right - if (uncle?.color == Node.RBNode.Color.RED) { - node.parent!!.color = Node.RBNode.Color.BLACK - uncle.color = Node.RBNode.Color.BLACK - node.parent!!.parent!!.color = Node.RBNode.Color.RED - balanceRBTree(node.parent!!.parent!!) + val grandparentNode = parentNode.parent as? Node.RBNode ?: return + + if (parentNode == grandparentNode.left) { + val uncle = grandparentNode.right as? Node.RBNode + if (uncle?.color == Node.RBNode.Color.RED) { + parentNode.color = Node.RBNode.Color.BLACK + uncle.color = Node.RBNode.Color.BLACK + grandparentNode.color = Node.RBNode.Color.RED + balanceRBTree(grandparentNode) + } else { + if (node == parentNode.right) { + parentNode.leftRotate() + balanceRBTree(node.left as Node.RBNode) } else { - if (node.parent!!.right == node) { - node.parent!!.leftRotate() - balanceRBTree(node.left!!) - } else { - node.parent!!.parent!!.rightRotate() - node.parent!!.color = Node.RBNode.Color.BLACK - node.parent!!.right!!.color = Node.RBNode.Color.RED - } + grandparentNode.rightRotate() + parentNode.color = Node.RBNode.Color.BLACK + (parentNode.right as Node.RBNode?)?.color = Node.RBNode.Color.RED } + } + } else { + val uncle = grandparentNode.left as? Node.RBNode + if (uncle?.color == Node.RBNode.Color.RED) { + parentNode.color = Node.RBNode.Color.BLACK + uncle.color = Node.RBNode.Color.BLACK + grandparentNode.color = Node.RBNode.Color.RED + balanceRBTree(grandparentNode) } else { - val uncle = node.parent!!.parent!!.left - if (uncle?.color == Node.RBNode.Color.RED) { - node.parent!!.color = Node.RBNode.Color.BLACK - uncle.color = Node.RBNode.Color.BLACK - node.parent!!.parent!!.color = Node.RBNode.Color.RED - balanceRBTree(node.parent!!.parent!!) + if (node == parentNode.left) { + parentNode.rightRotate() + balanceRBTree((node.right as Node.RBNode)) } else { - if (node.parent!!.left == node) { - node.parent!!.rightRotate() - balanceRBTree(node.right!!) - } else { - node.parent!!.parent!!.leftRotate() - node.parent!!.color = Node.RBNode.Color.BLACK - node.parent!!.left!!.color = Node.RBNode.Color.RED - } + grandparentNode.leftRotate() + parentNode.color = Node.RBNode.Color.BLACK + (parentNode.left as Node.RBNode?)?.color = Node.RBNode.Color.RED } } } diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index f709134..4c4e358 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -2,7 +2,6 @@ import java.util.* import kotlin.math.ceil import kotlin.math.floor - open class Tree , V: Any, T: Node> ( var root: Node? = null ): Iterable> { @@ -149,10 +148,10 @@ open class Tree , V: Any, T: Node> ( } private tailrec fun isKey(key: K, current: Node?): Boolean { - if (current == null) return false - else if (current.key == key) return true - else if (current.key > key) return isKey(key, current.left) - else return isKey(key, current.right) + return if (current == null) false + else if (current.key == key) true + else if (current.key > key) isKey(key, current.left) + else isKey(key, current.right) } open fun min(): Node? { From e3f5984956bf2670ade2b9ffd3cd9171c08bcec7 Mon Sep 17 00:00:00 2001 From: 7gambit7 <87602446+7gambit7@users.noreply.github.com> Date: Wed, 20 Mar 2024 07:26:29 +0300 Subject: [PATCH 15/52] Configuring CI tests --- .github/workflows/github-actions.yml | 38 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 15a61d6..c0a41ac 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,18 +1,26 @@ -name: GitHub Actions Demo -run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 -on: [push] +name: Kotlin CI with Gradle + +on: + push: + branches: + [main] + pull_request: + branches: + [main] jobs: - Explore-GitHub-Actions: + build: runs-on: ubuntu-latest steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - name: Check out repository code - uses: actions/checkout@v4 - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "🖥️ The workflow is now ready to test your code on the runner." - - name: List files in the repository - run: | - ls ${{ github.workspace }} - - run: echo "🍏 This job's status is ${{ job.status }}." + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: "oracle" + java-version: 21 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Testing + run: ./gradlew test From a03009019090f3c9949df08aa6136efd9c911df8 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Thu, 21 Mar 2024 01:05:38 +0700 Subject: [PATCH 16/52] Gradle now log tests --- build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index af1182c..36a295a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,11 @@ dependencies { tasks.test { useJUnitPlatform() + + testLogging { + events("passed", "skipped", "failed") + } } kotlin { jvmToolchain(21) -} \ No newline at end of file +} From bdea463088eaa3afcbfea6a39349331b4f75ee4f Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Tue, 26 Mar 2024 06:06:43 +0700 Subject: [PATCH 17/52] Tests. Just Tests. Instruction covering 60% --- build.gradle.kts | 15 ++ src/main/kotlin/Tree.kt | 30 +-- src/test/kotlin/BinaryTreeTest.kt | 339 +++++++++++++++++++++++++++++- 3 files changed, 364 insertions(+), 20 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 36a295a..19dbe8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,10 @@ plugins { kotlin("jvm") version "1.9.22" + `java-library` + jacoco } + group = "org.example" version = "1.0-SNAPSHOT" @@ -19,7 +22,19 @@ tasks.test { testLogging { events("passed", "skipped", "failed") } + + finalizedBy(tasks.jacocoTestReport) +} + +tasks.named("jacocoTestReport") { + dependsOn(tasks.test) + reports { + csv.required = false + xml.required = false + html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + } } + kotlin { jvmToolchain(21) } diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 4c4e358..9918a5f 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -155,29 +155,30 @@ open class Tree , V: Any, T: Node> ( } open fun min(): Node? { - return if (root == null) null else min(root) + val rootNow = this.root + return if (rootNow == null) null else min(rootNow) } - private fun min(node: Node?): Node? { - return if (node != null) { - if (node.left == null) node else min(node.left) - } else null + internal fun min(node: Node): Node? { + val left = node.left + return if (left == null) node else min(left) } open fun max(): Node? { - return if (root == null) null else max(root) + val rootNow = this.root + return if (rootNow == null) null else max(rootNow) } - private fun max(node: Node?): Node? { - return if (node != null) { - if (node.right == null) node else min(node.right) - } else null + internal fun max(node: Node): Node? { + val right = node.right + return if (right == null) node else max(right) } fun getNext(node: Node): Node? { - if (node.right != null) { + val right = node.right + if (right != null) { // If the right subtree is not null, the successor is the leftmost node in the right subtree - return min(node.right) + return min(right) } // If the right subtree is null, the successor is the first ancestor whose left child is also an ancestor @@ -191,9 +192,10 @@ open class Tree , V: Any, T: Node> ( } fun getPrev(node: Node): Node? { - if (node.left != null) { + val left = node.left + if (left != null) { // If the left subtree is not null, the predecessor is the rightmost node in the left subtree - return max(node.left) + return max(left) } // If the left subtree is null, the predecessor is the first ancestor whose right child is also an ancestor diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt index ed68b0b..763eccd 100644 --- a/src/test/kotlin/BinaryTreeTest.kt +++ b/src/test/kotlin/BinaryTreeTest.kt @@ -30,14 +30,67 @@ class BinaryTreeTest { assert(tree.root != null) } @Test - fun `value should be added to not empty tree`() { - val expectedResult = "A" - val key = 1 + fun `value should added correctly left-left`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 - tree.add(0, "") - tree.add(key, expectedResult) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(firstKey, firstKey.toString()) - assert(tree[key] contentEquals expectedResult) + assert( tree.root?.key == thirdKey && + tree.root?.left?.key == secondKey && + tree.root?.left?.left?.key == firstKey + ) + } + + @Test + fun `value should added correctly left-right`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(firstKey, firstKey.toString()) + tree.add(secondKey, secondKey.toString()) + + assert( tree.root?.key == thirdKey && + tree.root?.left?.key == firstKey && + tree.root?.left?.right?.key == secondKey + ) + } + + @Test + fun `value should added correctly right-left`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + + assert( tree.root?.key == firstKey && + tree.root?.right?.key == thirdKey && + tree.root?.right?.left?.key == secondKey + ) + } + + @Test + fun `value should added correctly right-right`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + + assert( tree.root?.key == firstKey && + tree.root?.right?.key == secondKey && + tree.root?.right?.right?.key == thirdKey + ) } @Test @@ -68,4 +121,278 @@ class BinaryTreeTest { tree[key] = expectedResult assert(tree[key] contentEquals expectedResult) } + + @Test + fun `Get should return value left-left`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(firstKey, firstKey.toString()) + + assert(tree[firstKey] == firstKey.toString()) + } + + @Test + fun `Get should return value left-right`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(firstKey, firstKey.toString()) + tree.add(secondKey, secondKey.toString()) + + assert( tree[secondKey] == secondKey.toString() ) + } + + @Test + fun `Get should return value right-left`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + + assert(tree[secondKey] == secondKey.toString()) + } + + @Test + fun `Get should return value right-right`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + + assert(tree[thirdKey] == thirdKey.toString()) + } + @Test + fun `GetOrDefault should return value if it exist`() { + val key = 1 + val expectedResult = "A" + + tree.add(key, expectedResult) + assert(tree.getOrDefault(key, "B") == "A") + } + + @Test + fun `GetOrDefault should return default if it's not exist`() { + val key = 1 + val fakeKey = 2 + val expectedResult = "A" + + tree.add(key, expectedResult) + assert(tree.getOrDefault(fakeKey, "B") == "B") + } + + @Test + fun `IsKey should return true if key is exist (zig-zag)`() { + val keys = arrayOf(5, 1, 4, 2, 3) + keys.forEach { tree.add(it, it.toString()) } + + assert(tree.isKey(3)) + } + @Test + fun `IsKey should return false if key isn't exist (zig-zag)`() { + val keys = arrayOf(6, 1, 5, 2, 4) + keys.forEach { tree.add(it, it.toString()) } + + assert(!tree.isKey(3)) + } + + @Test + fun `Min should return null from empty tree`() { + assert(tree.min() == null) + } + + @Test + fun `Min should return min from nonempty tree`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(firstKey, firstKey.toString()) + + assert(tree.min()?.key == firstKey) + } + + @Test + fun `Max should return null from empty tree`() { + assert(tree.max() == null) + } + + @Test + fun `Max should return min from nonempty tree`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(firstKey, firstKey.toString()) + + assert(tree.max()?.key == thirdKey) + } + + @Test + fun `getNext should return min of right child if it exist`(){ + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + + val root = tree.root + val right = root?.right + if (right != null) { + assert(tree.getNext(root) === tree.min(right)) + } else { + assert(false) + } + } + + @Test + fun `getNext should return parent if right child isn't exist`(){ + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(firstKey, firstKey.toString()) + tree.add(secondKey, secondKey.toString()) + + val secondNode = tree.getNode(secondKey) + val thirdNode = tree.getNode(thirdKey) + + if (secondNode != null) { + assert(tree.getNext(secondNode) === thirdNode) + } else { + assert(false) + } + } + + @Test + fun `getPrev should return max of left child if it exist`(){ + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + tree.add(firstKey, firstKey.toString()) + + val root = tree.root + val left = root?.left + if (left != null) { + assert(tree.getPrev(root) === tree.max(left)) + } else { + assert(false) + } + } + + @Test + fun `getPrev should return parent if left child isn't exist`(){ + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + + val secondNode = tree.getNode(secondKey) + val firstNode = tree.getNode(firstKey) + + if (secondNode != null) { + assert(tree.getPrev(secondNode) === firstNode) + } else { + assert(false) + } + } + + @Test + fun `Merge should make other root to this root if this root is null`() { + val secondTree = BinaryTree() + + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + + secondTree.merge(tree) + + assert(secondTree.root === tree.root) + } + + @Test + fun `Merge should make other root to this max if this root isn't null`() { + val secondTree = BinaryTree() + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + + secondTree.add(firstKey - 3, firstKey.toString()) + secondTree.add(secondKey - 3, secondKey.toString()) + secondTree.add(thirdKey - 3, thirdKey.toString()) + + val max = secondTree.max() + secondTree.merge(tree) + + assert(max?.right === tree.root) + } + + @Test + fun `Split should work correctly`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + + val (small, big) = tree.split(secondKey) + + assert(small.root === tree.getNode(firstKey) && big.root === tree.getNode(thirdKey)) + } + + @Test + fun `Clone should clone only content not nodes`() { + val firstKey = -1 + val secondKey = 0 + val thirdKey = 1 + + tree.add(firstKey, firstKey.toString()) + tree.add(thirdKey, thirdKey.toString()) + tree.add(secondKey, secondKey.toString()) + + val clonedTree = tree.clone() + var isCorrect = true + + assert( + tree.getNode(firstKey) == clonedTree.getNode(firstKey) + && tree.getNode(firstKey) !== clonedTree.getNode(firstKey) + && tree.getNode(secondKey) == clonedTree.getNode(secondKey) + && tree.getNode(secondKey) !== clonedTree.getNode(secondKey) + && tree.getNode(thirdKey) == clonedTree.getNode(thirdKey) + && tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey) + ) + } } \ No newline at end of file From 68829f1875719138c878c66c0c7373e65830e8c3 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Wed, 27 Mar 2024 04:49:33 +0700 Subject: [PATCH 18/52] MORE TESTS. All delete covering. --- src/main/kotlin/Tree.kt | 32 ++-- src/test/kotlin/BFSIteratorTest.kt | 31 ++++ src/test/kotlin/BinaryTreeTest.kt | 245 ++++++++++++++++++++++++++- src/test/kotlin/ByKeyIteratorTest.kt | 31 ++++ src/test/kotlin/DFSIteratorTest.kt | 32 ++++ src/test/kotlin/NodeTest.kt | 64 +++++++ 6 files changed, 415 insertions(+), 20 deletions(-) create mode 100644 src/test/kotlin/BFSIteratorTest.kt create mode 100644 src/test/kotlin/ByKeyIteratorTest.kt create mode 100644 src/test/kotlin/DFSIteratorTest.kt create mode 100644 src/test/kotlin/NodeTest.kt diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 9918a5f..86d33d2 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -107,12 +107,13 @@ open class Tree , V: Any, T: Node> ( } else { parent.right = current.left } - parent.parent = parent + (current.left as Node).parent = parent } } } else { val next = getNext(current)!! current.key = next.key + current.value = next.value if (next.parent!!.left === next) { next.parent!!.left = next.right if (next.right != null) { @@ -128,7 +129,7 @@ open class Tree , V: Any, T: Node> ( next.left = current.left if (next.left != null) next.left!!.parent = next next.right = current.right - if (next.left != null) next.right!!.parent = next + if (next.right != null) next.right!!.parent = next next.parent = current.parent if (next.parent != null) { if (next.parent!!.left === current) { @@ -260,24 +261,17 @@ open class Tree , V: Any, T: Node> ( inner class DFSIterator(tree: Tree): Iterator> { - private var stack: Stack> = Stack>() + private var stack: Queue> = LinkedList() init { - val DFSstack: Stack> = Stack>() - if (tree.root != null) { - DFSstack.add(tree.root) - stack.add(tree.root) - while (!DFSstack.isEmpty()) { - val current: Node = DFSstack.pop() - if (current.left != null) { - DFSstack.add(current.left as Node) - stack.add(current.left as Node) - } - if (current.right != null) { - DFSstack.add(current.right as Node) - stack.add(current.right as Node) - } - } + addToStack(tree.root) + } + + private fun addToStack(current: Node?) { + if (current != null) { + stack.add(current) + this.addToStack(current.left) + this.addToStack(current.right) } } @@ -286,7 +280,7 @@ open class Tree , V: Any, T: Node> ( } override fun next(): Node { - return stack.pop() + return stack.poll() } } diff --git a/src/test/kotlin/BFSIteratorTest.kt b/src/test/kotlin/BFSIteratorTest.kt new file mode 100644 index 0000000..5092195 --- /dev/null +++ b/src/test/kotlin/BFSIteratorTest.kt @@ -0,0 +1,31 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class BFSIteratorTest{ + private lateinit var tree: BinaryTree + + @BeforeEach + fun setup() { + tree = BinaryTree() + } + + @Test + fun `BFS should work correctly empty tree`() { + var counter = 0 + for (i in tree.iterateBFS()) counter++ + assert(counter == 0) + } + + @Test + fun `DFS should work correctly simple tree sample`() { + val keys = arrayOf(2, 1, 3, 0, 4) + val result = mutableListOf() + val expectedResult = arrayOf(2, 1, 3, 0, 4) + keys.forEach { tree.add(it, it.toString()) } + for (i in tree.iterateBFS()) result.add(i.key) + + for (i in keys.indices) assert(result[i] == expectedResult[i]) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt index 763eccd..0f51b75 100644 --- a/src/test/kotlin/BinaryTreeTest.kt +++ b/src/test/kotlin/BinaryTreeTest.kt @@ -384,7 +384,6 @@ class BinaryTreeTest { tree.add(secondKey, secondKey.toString()) val clonedTree = tree.clone() - var isCorrect = true assert( tree.getNode(firstKey) == clonedTree.getNode(firstKey) @@ -395,4 +394,248 @@ class BinaryTreeTest { && tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey) ) } + + @Test + fun `Delete from empty tree if key isn't in tree should work correctly(do nothing)`(){ + tree.delete(15) + assert(true) + } + + @Test + fun `Delete should work correctly no children root`() { + val key = 1 + + tree.add(key, key.toString()) + tree.delete(key) + + assert(tree.root == null) + } + + @Test + fun `Delete should work correctly no children not-root left`() { + val keyRoot = 0 + val key = -1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.delete(key) + + val root = tree.root + assert(tree.getNode(keyRoot) === root && root != null && root.left == null) + } + + @Test + fun `Delete should work correctly no children not-root right`() { + val keyRoot = 0 + val key = 1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.delete(key) + + val root = tree.root + assert(tree.getNode(keyRoot) === root && root != null && root.right == null) + } + + @Test + fun `Delete should work correctly left child root`() { + val keyRoot = 0 + val key = -1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + + tree.delete(keyRoot) + + assert(tree.getNode(key) === tree.root) + } + + @Test + fun `Delete should work correctly right child root`() { + val keyRoot = 0 + val key = 1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.delete(keyRoot) + + val root = tree.root + assert(tree.getNode(key) === root) + } + + @Test + fun `Delete should work correctly left child not-root left`() { + val keyRoot = 0 + val key = -1 + val childKey = -2 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.add(childKey, childKey.toString()) + + val root = tree.root + tree.delete(key) + + assert(tree.getNode(keyRoot) === root + && root != null + && root.left === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root + ) + } + + @Test + fun `Delete should work correctly right child not-root left`() { + val keyRoot = 0 + val key = -2 + val childKey = -1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.add(childKey, childKey.toString()) + + val root = tree.root + tree.delete(key) + + assert(tree.getNode(keyRoot) === root + && root != null + && root.left === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root + ) + } + + @Test + fun `Delete should work correctly left child not-root right`() { + val keyRoot = 0 + val key = 2 + val childKey = 1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.add(childKey, childKey.toString()) + + val root = tree.root + tree.delete(key) + + assert(tree.getNode(keyRoot) === root + && root != null + && root.right === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root + ) + } + + @Test + fun `Delete should work correctly right child not-root right`() { + val keyRoot = 0 + val key = 1 + val childKey = 2 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.add(childKey, childKey.toString()) + + val root = tree.root + tree.delete(key) + + assert(tree.getNode(keyRoot) === root + && root != null + && root.right === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root + ) + } + + // Madness is following... + + @Test + fun `Delete should work correctly 2 child root where next haven't children`() { + val keyRoot = 0 + val keyRight = 1 + val keyLeft = -1 + tree.add(keyRoot, keyRoot.toString()) + tree.add(keyRight, keyRight.toString()) + tree.add(keyLeft, keyLeft.toString()) + + tree.delete(keyRoot) + + val root = tree.root + assert(tree.getNode(keyRight) === root + && root != null + && root.left === tree.getNode(keyLeft) + && tree.getNode(keyLeft)?.parent === root + ) + } + + @Test + fun `Delete should work correctly 2 child root where next has not left children`() { + + // scenario explanation on picture + // + // ┌── 3 ┌── 3 + // | | ┌── 2 | └── 2 + // | └── 1 ──> 1 + // 0 (0 delete) └── -1 + // └── -1 + + val keys = arrayOf(0, -1, 3, 1, 2) + + keys.forEach { tree.add(it, it.toString()) } + + val next = tree.getNext(tree.root!!) + + tree.delete(keys[0]) + + val root = tree.root + assert(root == next + && root != null + && root.left === tree.getNode(keys[1]) + && tree.getNode(keys[1])?.parent === root + && root.right === tree.getNode(keys[2]) + && tree.getNode(keys[2])?.parent === root + && root.right != null + && root.right?.left === tree.getNode(keys[4]) + && tree.getNode(keys[4])?.parent === root.right + ) + } + + @Test + fun `delete should work correctly 2 child not-root`() { + // scenario explanation on picture + // ┌── 3 ┌── 3 + // ┌── 2 | └── 1 + // | └── 1 ──> 0 + // 0 (2 delete) └── -1 + // └── -1 + + val keys = arrayOf(0, -1, 2, 1, 3) + keys.forEach { tree.add(it, it.toString()) } + + val node = tree.getNode(keys[4]) + tree.delete(keys[2]) + + val root = tree.root + assert(root != null + && root.left === tree.getNode(keys[1]) + && tree.getNode(keys[1])?.parent === root + && node == root.right + && root.right?.left === tree.getNode(keys[3]) + ) + } + + @Test + fun `delete should work correctly 2 child not-root but next is left child of its parent`() { + // scenario explanation on picture + // 0 0 + // | ┌── -1 | ┌── -1 + // | ┌── -2 ──> └── -2 + // └── -3 (-2 delete) └── -4 + // └── -4 + + val keys = arrayOf(0, -3, -2, -4, -1) + keys.forEach { tree.add(it, it.toString()) } + + tree.delete(keys[1]) + + val root = tree.root + val node = tree.getNode(keys[2]) + assert(root != null + && root.left === node + && node?.parent === root + && node.left === tree.getNode(keys[3]) + && tree.getNode(keys[3])?.parent === node + && node.right === tree.getNode(keys[4]) + && tree.getNode(keys[4])?.parent === node + ) + } + } \ No newline at end of file diff --git a/src/test/kotlin/ByKeyIteratorTest.kt b/src/test/kotlin/ByKeyIteratorTest.kt new file mode 100644 index 0000000..386f88e --- /dev/null +++ b/src/test/kotlin/ByKeyIteratorTest.kt @@ -0,0 +1,31 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class ByKeyIteratorTest{ + private lateinit var tree: BinaryTree + + @BeforeEach + fun setup() { + tree = BinaryTree() + } + + @Test + fun `BFS should work correctly empty tree`() { + var counter = 0 + for (i in tree) counter++ + assert(counter == 0) + } + + @Test + fun `DFS should work correctly simple tree sample`() { + val keys = arrayOf(2, 1, 3, 0, 4) + val result = mutableListOf() + val expectedResult = arrayOf(0, 1, 2, 3, 4) + keys.forEach { tree.add(it, it.toString()) } + for (i in tree) result.add(i.key) + + for (i in keys.indices) assert(result[i] == expectedResult[i]) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/DFSIteratorTest.kt b/src/test/kotlin/DFSIteratorTest.kt new file mode 100644 index 0000000..a3ad9ef --- /dev/null +++ b/src/test/kotlin/DFSIteratorTest.kt @@ -0,0 +1,32 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.math.acos + +class DFSIteratorTest { + private lateinit var tree: BinaryTree + + @BeforeEach + fun setup() { + tree = BinaryTree() + } + + @Test + fun `DFS should work correctly empty tree`() { + var counter = 0 + for (i in tree.iterateDFS()) counter++ + assert(counter == 0) + } + + @Test + fun `DFS should work correctly simple tree sample`() { + val keys = arrayOf(2, 1, 3, 0, 4) + val result = mutableListOf() + val expectedResult = arrayOf(2, 1, 0, 3, 4) + keys.forEach { tree.add(it, it.toString()) } + for (i in tree.iterateDFS()) result.add(i.key) + + for (i in keys.indices) assert(result[i] == expectedResult[i]) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/NodeTest.kt b/src/test/kotlin/NodeTest.kt new file mode 100644 index 0000000..80bfa1c --- /dev/null +++ b/src/test/kotlin/NodeTest.kt @@ -0,0 +1,64 @@ +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class NodeTest{ + @Test + fun `Node should be equal if values and keys are equal`() { + val key = 15 + val value = "Mitochondria" + + val nodeOne = Node>(key, value) + val nodeSecond = Node>(key, value) + + assert(nodeOne == nodeSecond) + } + + @Test + fun `Node should be equal if values aren't equal`() { + val key = 15 + val valueOne = "Mitochondria" + val valueTwo = "Mit0ch0ndr1a" + + val nodeOne = Node>(key, valueOne) + val nodeSecond = Node>(key, valueTwo) + + assert(nodeOne != nodeSecond) + } + @Test + fun `Node should be equal if keys aren't equal`() { + val keyOne = 15 + val keyTwo = 150 + val value = "Mitochondria" + + val nodeOne = Node>(keyOne, value) + val nodeSecond = Node>(keyTwo, value) + + assert(nodeOne != nodeSecond) + } + + @Test + fun `Node should be equal if keys aren't equal and values aren't equal`() { + val keyOne = 15 + val keyTwo = 150 + val valueOne = "Mitochondria" + val valueTwo = "Mit0ch0ndr1a" + + val nodeOne = Node>(keyOne, valueOne) + val nodeSecond = Node>(keyTwo, valueTwo) + + assert(nodeOne != nodeSecond) + } + + @Test + fun `Node shouldn't be equal to notNode`() { + val keyOne = 15 + val valueOne = "Mitochondria" + + val nodeOne = Node>(keyOne, valueOne) + + val notNode = arrayOf(keyOne, valueOne) + + assert(nodeOne != notNode) + } +} \ No newline at end of file From e69f9c5eefd389dbfad366f138fdc462f5c7b593 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Fri, 29 Mar 2024 23:22:54 +0300 Subject: [PATCH 19/52] fix: delete method --- src/main/kotlin/RBTree.kt | 288 ++++++++++++++++++++++++++++++---- src/test/kotlin/RBNodeTest.kt | 39 +++++ 2 files changed, 300 insertions(+), 27 deletions(-) create mode 100644 src/test/kotlin/RBNodeTest.kt diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 8aa5a9d..0e206ac 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -25,36 +25,270 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree } override fun delete(key: K) { - val node = this.getNode(key) ?: return - if (node.left != null && node.right != null) { - val successor = (node.right as Node.RBNode).min() - node.key = successor.key - node.value = successor.value - this.deleteNode(successor) + val node = this.getNode(key) as Node.RBNode? ?: return + + if (node.color == Node.RBNode.Color.RED) { + if (node.left == null && node.right == null) { + deleteRedNodeWithNoChildren(node) + } else if (node.left != null && node.right != null) { + deleteNodeWithTwoChildren(node) + } } else { - this.deleteNode(node as Node.RBNode) + if (node.left == null && node.right == null) { + deleteBlackNodeWithNoChildren(node) + } else if (node.left != null && node.right != null) { + deleteNodeWithTwoChildren(node) + } else { + deleteBlackNodeWithOneChild(node) + } } } - private fun deleteNode(node: Node.RBNode) { - val child = if (node.right != null) node.right else node.left - if (node.color == Node.RBNode.Color.BLACK) { - if ((child as Node.RBNode?)?.color == Node.RBNode.Color.RED) { - (child as Node.RBNode).color = Node.RBNode.Color.BLACK + private fun deleteRedNodeWithNoChildren(node: Node.RBNode) { + if (node == node.parent?.left) { + node.parent?.left = null + } else { + node.parent?.right = null + } + } + + private fun deleteNodeWithTwoChildren(node: Node.RBNode) { + val successor = (node.right as Node.RBNode).min() + val tmpValue = node.value + val tmpKey = node.key + node.key = successor.key + node.value = successor.value + successor.key = tmpKey + successor.value = tmpValue + if (successor.color == Node.RBNode.Color.RED) { + if (successor.left != null && successor.right != null) { + deleteNodeWithTwoChildren(successor) } else { - balanceRBTree(node) + if (successor.left != null || successor.right != null) { + throw IllegalStateException("Successor should have two children or None") + } + deleteRedNodeWithNoChildren(successor) + } + } else { + if (successor.left == null && successor.right == null) { + deleteBlackNodeWithNoChildren(successor) + } else if (successor.left != null && successor.right != null) { + deleteNodeWithTwoChildren(successor) + } else { + deleteBlackNodeWithOneChild(successor) } } + +// if ((successor.parent as Node.RBNode).color == Node.RBNode.Color.RED) { +// (successor.parent as Node.RBNode).leftRotate() +// } + } + + private fun deleteBlackNodeWithOneChild(node: Node.RBNode) { if (node.parent == null) { - root = child - } else if (node == node.parent?.left) { - node.parent?.left = child + root = node.left ?: node.right + (root as Node.RBNode).color = Node.RBNode.Color.BLACK + return + } + val child = node.left as Node.RBNode? ?: node.right as Node.RBNode + val tmpValue = node.value + val tmpKey = node.key + node.key = child.key + node.value = child.value + child.key = tmpKey + child.value = tmpValue + + if (child.color == Node.RBNode.Color.BLACK) { + deleteBlackNodeWithNoChildren(child) + } else { + deleteRedNodeWithNoChildren(child) + } + } + + private fun deleteBlackNodeWithNoChildren(node: Node.RBNode) { + if (node.parent == null) { + // If the node is the root, set the root to null + root = null } else { - node.parent?.right = child + var fixNode: Node.RBNode? = null + var sibling = + if (node == node.parent!!.left) node.parent!!.right as Node.RBNode? else node.parent!!.left as Node.RBNode? + if (sibling?.color == Node.RBNode.Color.RED) { + // If the sibling is red, recolor and perform rotations + sibling.color = Node.RBNode.Color.BLACK + (node.parent as Node.RBNode).color = Node.RBNode.Color.RED + if (node == node.parent!!.left) { + (node.parent as Node.RBNode).leftRotate() + } else { + (node.parent as Node.RBNode).rightRotate() + } + // Update sibling after rotation + fixNode = + if (node == node.parent!!.left) node.parent!!.right as Node.RBNode? else node.parent!!.left as Node.RBNode? + } else { + // If the sibling is black + if (((sibling?.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.left == null) && ((sibling?.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.right == null)) { + // If both children of sibling are black, recolor sibling and move fixNode to parent + sibling?.color = Node.RBNode.Color.RED + fixNode = node.parent as Node.RBNode? + } else { + if (node.parent != null) { + val parentNode = node.parent!! as Node.RBNode + val isLeftChild = node == parentNode.left + + if ((isLeftChild && (sibling.right == null || (sibling.right as Node.RBNode?)?.color== Node.RBNode.Color.BLACK)) || + (!isLeftChild && (sibling.left == null || (sibling.left as Node.RBNode?)?.color== + Node.RBNode.Color.BLACK))) { + // If node is left child and sibling's right child is black or null, + // or if node is right child and sibling's left child is black or null, + // perform rotation and recoloring + val siblingChild = if (isLeftChild) sibling.left else sibling.right + (siblingChild as Node.RBNode?)?.color = Node.RBNode.Color.BLACK + sibling.color = Node.RBNode.Color.BLACK + if (isLeftChild) sibling.rightRotate() else sibling.leftRotate() + // Update sibling after rotation + fixNode = if (isLeftChild) parentNode.right as Node.RBNode else parentNode.left as Node.RBNode + } else if ((isLeftChild && ((sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED)) || + (!isLeftChild && ((sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED))) { + // If node is left child and sibling's right child is red, + // or if node is right child and sibling's left child is red, + // perform rotation and additional checks for recoloring + if (isLeftChild) parentNode.leftRotate() else parentNode.rightRotate() + if ((isLeftChild && (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED && + (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK && + (sibling.right as Node.RBNode?)?.right == null && + sibling.right?.left == null) || + (!isLeftChild && (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED && + (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK && sibling + .left?.left == null && + sibling.left?.right == null)) { + val siblingChild = if (isLeftChild) sibling.right else sibling.left + (siblingChild as Node.RBNode?)?.color = Node.RBNode.Color.BLACK + } + } + } + // sometimes it helps to balance the tree, else it does nothing + balanceRBTree(node) + // Perform additional fix-up operations if needed + if (fixNode != null) { + if (fixNode == fixNode.parent!!.left) { + (fixNode.parent as Node.RBNode).leftRotate() + } else { + (fixNode.parent as Node.RBNode).rightRotate() + } + fixNode.color = (fixNode.parent as Node.RBNode).color + (fixNode.parent as Node.RBNode).color = Node.RBNode.Color.BLACK + } + } + } + // Transplant node with null and fix deletion + transplant(node, null) + if (fixNode != null) { + fixDeletion(fixNode) + } } - child?.parent = node.parent } + private fun transplant(u: Node.RBNode?, v: Node.RBNode?) { + if (u?.parent == null) { + // If u is the root, set v as the new root + root = v + } else if (u == u.parent?.left) { + // If u is the left child of its parent, set v as the new left child + u.parent?.left = v + } else { + // Otherwise, u is the right child of its parent, set v as the new right child + u.parent?.right = v + } + // Update v's parent to be u's parent + v?.parent = u?.parent + } + + private fun fixDeletion(node: Node.RBNode?) { + var fixNode = node + + while (fixNode != null && fixNode != root && fixNode.color == Node.RBNode.Color.BLACK) { + val parentNode = fixNode.parent as Node.RBNode? ?: break + val isLeftChild = fixNode == parentNode.left + var sibling = + if (isLeftChild) parentNode.right as Node.RBNode? else parentNode.left as Node.RBNode? + + if (sibling == null) { + // No sibling, continue fixup from parent + fixNode.color = Node.RBNode.Color.RED + + } + if (fixNode == parentNode.left) { + + if (sibling?.color == Node.RBNode.Color.RED) { + // Case 1: Sibling is red + sibling.color = Node.RBNode.Color.BLACK + parentNode.color = Node.RBNode.Color.RED + parentNode.leftRotate() + sibling = parentNode.right as Node.RBNode? + } + + val siblingLeft = sibling?.left as Node.RBNode? + val siblingRight = sibling?.right as Node.RBNode? + if ((siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null) && (siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null)) { + // Case 2: Both children of sibling are black + sibling?.color = Node.RBNode.Color.RED + fixNode = parentNode + } else { + if ((siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null)) { + // Case 3: Left child of sibling is red + siblingLeft?.color = Node.RBNode.Color.BLACK + sibling?.color = Node.RBNode.Color.RED + sibling?.rightRotate() + sibling = parentNode.right as Node.RBNode? + } + + // Case 4: Right child of sibling is red + sibling?.color = parentNode.color + parentNode.color = Node.RBNode.Color.BLACK + siblingRight?.color = Node.RBNode.Color.BLACK + parentNode.leftRotate() + fixNode = root as Node.RBNode? + } + } else { + + if (sibling?.color == Node.RBNode.Color.RED) { + // Case 1: Sibling is red + sibling.color = Node.RBNode.Color.BLACK + parentNode.color = Node.RBNode.Color.RED + parentNode.rightRotate() + sibling = parentNode.left as Node.RBNode? + } + + val siblingLeft = sibling?.left as Node.RBNode? + val siblingRight = sibling?.right as Node.RBNode? + if ((siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null) && (siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null)) { + // Case 2: Both children of sibling are black + sibling?.color = Node.RBNode.Color.RED + fixNode = parentNode + } else { + if ((siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null)) { + // Case 3: Right child of sibling is red + siblingRight?.color = Node.RBNode.Color.BLACK + sibling?.color = Node.RBNode.Color.RED + sibling?.leftRotate() + sibling = parentNode.left as Node.RBNode? + } + + // Case 4: Left child of sibling is red + sibling?.color = parentNode.color + parentNode.color = Node.RBNode.Color.BLACK + siblingLeft?.color = Node.RBNode.Color.BLACK + parentNode.rightRotate() + fixNode = root as Node.RBNode? + } + } + } + + fixNode?.color = Node.RBNode.Color.BLACK + } + + override fun max(): Node.RBNode? { return if (root == null) null else (root as Node.RBNode).max() } @@ -63,30 +297,30 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree return if (root == null) null else (root as Node.RBNode).min() } -// override fun toStringBeautifulWidth(): String { -// return if (this.root == null) "" -// else this.newToStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!! as Node.RBNode) -// .toString() -// } - private fun newToStringBeautifulWidth( + fun toStringColoredWidth(): String { + return if (this.root == null) "" + else this.toStringColoredWidth(StringBuilder(), true, StringBuilder(), this.root!! as Node.RBNode) + .toString() + } + private fun toStringColoredWidth( prefix: StringBuilder, isTail: Boolean, buffer: StringBuilder, current: Node.RBNode ): StringBuilder { if (current.right != null) { var newPrefix = StringBuilder() newPrefix.append(prefix) newPrefix.append(if (isTail) "| " else " ") - this.newToStringBeautifulWidth(newPrefix, false, buffer, current.right as Node.RBNode) + this.toStringColoredWidth(newPrefix, false, buffer, current.right as Node.RBNode) } buffer.append(prefix) buffer.append(if (isTail) "└── " else "┌── ") - buffer.append("${current.key}(${current.color})") + buffer.append("${current.key} (${current.color})") buffer.append("\n") if (current.left != null) { var newPrefix = StringBuilder() newPrefix.append(prefix) newPrefix.append(if (!isTail) "| " else " ") - this.newToStringBeautifulWidth(newPrefix, true, buffer, current.left as Node.RBNode) + this.toStringColoredWidth(newPrefix, true, buffer, current.left as Node.RBNode) } return buffer diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt new file mode 100644 index 0000000..3fbbd3c --- /dev/null +++ b/src/test/kotlin/RBNodeTest.kt @@ -0,0 +1,39 @@ +import org.junit.jupiter.api.Test + +class RBNodeTest { + + @Test + fun `max should return max value`() { + val tree = RBTree() + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + tree.add(4, "D") + tree.add(5, "E") + tree.add(6, "F") + tree.add(7, "G") + tree.add(8, "H") + tree.add(9, "I") + tree.add(10, "J") + + assert(tree.max()?.value == "J") + } + + @Test + fun `min should return min value`() { + val tree = RBTree() + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + tree.add(4, "D") + tree.add(5, "E") + tree.add(6, "F") + tree.add(7, "G") + tree.add(8, "H") + tree.add(9, "I") + tree.add(10, "J") + + assert(tree.min()?.value == "A") + } + +} \ No newline at end of file From eef0d06031700fc7b2f305eec26b422d194e61ce Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Sat, 30 Mar 2024 06:13:21 +0700 Subject: [PATCH 20/52] DFS modes were added. ToString function was added with mode argument. For these two functions were added enum classes. Also added tests for: 1) DFS modes 2) toString Normal mode 3) Negative scenarios(Adding existing key, merging "not-bigger" tree) --- src/main/kotlin/Main.kt | 16 +- src/main/kotlin/Nodes.kt | 2 +- src/main/kotlin/Tree.kt | 78 +++++++-- src/test/kotlin/BinaryTreeTest.kt | 268 ++++++++++++++++++++++------- src/test/kotlin/DFSIteratorTest.kt | 24 ++- 5 files changed, 293 insertions(+), 95 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index ebbdf31..13fc41b 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -2,18 +2,6 @@ fun main() { var tree = BinaryTree() - tree.add(102, "") - tree.add(101, "") - tree.add(104, "") - tree.add(103, "") - tree.add(105, "") - - println(tree.toStringBeautifulWidth()) - - for (i in tree) { - println(i) - tree.delete(i.key) - println(tree.toStringBeautifulWidth()) - - } + tree.add(1, "1") + tree.add(1, "1") } \ No newline at end of file diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index edf4f17..3f61399 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -50,7 +50,7 @@ open class Node, V, T: Node> internal constructor( ) : Node>(key, value, left, right, parent) override fun toString(): String { - return "$key $value" + return "($key: $value)" } override fun equals(other: Any?): Boolean { diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 86d33d2..6654dae 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -10,6 +10,9 @@ open class Tree , V: Any, T: Node> ( this.addNode(node) } open fun addNode(node: Node) { + require(this.root?.key != node.key) { + throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function") + } if (this.root == null) { this.root = node } else { @@ -259,19 +262,42 @@ open class Tree , V: Any, T: Node> ( } - inner class DFSIterator(tree: Tree): Iterator> { + enum class ModeDFS {SIMPLE, REVERSE, SYMMETRIC} + + inner class DFSIterator(tree: Tree, mode: ModeDFS = ModeDFS.SIMPLE): Iterator> { + private var stack: Queue> = LinkedList() init { - addToStack(tree.root) + when (mode) { + ModeDFS.SIMPLE -> addToStackSimple(tree.root) + ModeDFS.SYMMETRIC -> addToStackSymmetric(tree.root) + ModeDFS.REVERSE -> addToStackSimpleReverse(tree.root) + } + } + + private fun addToStackSimple(current: Node?) { + if (current != null) { + stack.add(current) + this.addToStackSimple(current.left) + this.addToStackSimple(current.right) + } } - private fun addToStack(current: Node?) { + private fun addToStackSimpleReverse(current: Node?) { if (current != null) { + this.addToStackSimpleReverse(current.left) + this.addToStackSimpleReverse(current.right) stack.add(current) - this.addToStack(current.left) - this.addToStack(current.right) + } + } + + private fun addToStackSymmetric(current: Node?) { + if (current != null) { + this.addToStackSymmetric(current.left) + stack.add(current) + this.addToStackSymmetric(current.right) } } @@ -293,8 +319,8 @@ open class Tree , V: Any, T: Node> ( return BFSIterator(this) } - fun iterateDFS(): DFSIterator { - return DFSIterator(this) + fun iterateDFS(mode: ModeDFS = ModeDFS.SIMPLE): DFSIterator { + return DFSIterator(this, mode=mode) } open fun merge(tree: Tree) { @@ -325,6 +351,30 @@ open class Tree , V: Any, T: Node> ( return clonedTree } + enum class TreeStringMode {NORMAL, WIDTH, HEIGHT} + + fun toString(mode: TreeStringMode = TreeStringMode.NORMAL): String { + return when (mode) { + TreeStringMode.NORMAL -> this.toString() + TreeStringMode.WIDTH -> this.toStringBeautifulWidth() + TreeStringMode.HEIGHT -> this.toStringBeautifulHeight() + } + } + + override fun toString(): String { + val buffer = StringBuilder() + + buffer.append("[") + this.forEach {buffer.append("${it.toString()}, ")} + val preResult = buffer.removeSuffix(", ").toString() + + val result = StringBuilder() + result.append(preResult) + result.append("]") + + return result.toString() + } + open fun toStringBeautifulWidth(): String { return if (this.root == null) "" else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() @@ -357,7 +407,7 @@ open class Tree , V: Any, T: Node> ( return buffer } - fun toStringBeautifulHeight(): String { + fun toStringBeautifulHeight(ofSide: Int = 4): String { if (this.root == null) return "" else { val buffer: StringBuilder = StringBuilder() @@ -406,7 +456,7 @@ open class Tree , V: Any, T: Node> ( next.clear() } - var perpiece: Int = lines[lines.size - 1].size * (widtest + 4) + var perpiece: Int = lines[lines.size - 1].size * (widtest + ofSide) for (i in 1..perpiece / 2) buffer.append("─") buffer.append("┐\n") @@ -430,15 +480,15 @@ open class Tree , V: Any, T: Node> ( buffer.append(c) if (line[j] == null) { - for (k in 0..(perpiece - 2)) { + repeat(perpiece - 1) { buffer.append(" ") } } else { - for (k in 0.., V: Any, T: Node> ( val gap1: Int = ceil(perpiece / 2f - f.length / 2f).toInt() val gap2: Int = floor(perpiece / 2f - f.length / 2f).toInt() - for (k in 1..gap1) { + repeat(gap1) { buffer.append(" ") } buffer.append(f) - for (k in 1..gap2) { + repeat(gap2) { buffer.append(" ") } } diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt index 0f51b75..d098cbb 100644 --- a/src/test/kotlin/BinaryTreeTest.kt +++ b/src/test/kotlin/BinaryTreeTest.kt @@ -1,15 +1,18 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import kotlin.test.assertFailsWith class BinaryTreeTest { private lateinit var tree: BinaryTree @BeforeEach - fun setup () { + fun setup() { tree = BinaryTree() } + // Positive + @Test fun `value should be added to empty tree`() { val expectedResult = "A" @@ -29,6 +32,7 @@ class BinaryTreeTest { assert(tree.root != null) } + @Test fun `value should added correctly left-left`() { val firstKey = -1 @@ -39,9 +43,10 @@ class BinaryTreeTest { tree.add(secondKey, secondKey.toString()) tree.add(firstKey, firstKey.toString()) - assert( tree.root?.key == thirdKey && - tree.root?.left?.key == secondKey && - tree.root?.left?.left?.key == firstKey + assert( + tree.root?.key == thirdKey && + tree.root?.left?.key == secondKey && + tree.root?.left?.left?.key == firstKey ) } @@ -55,9 +60,10 @@ class BinaryTreeTest { tree.add(firstKey, firstKey.toString()) tree.add(secondKey, secondKey.toString()) - assert( tree.root?.key == thirdKey && - tree.root?.left?.key == firstKey && - tree.root?.left?.right?.key == secondKey + assert( + tree.root?.key == thirdKey && + tree.root?.left?.key == firstKey && + tree.root?.left?.right?.key == secondKey ) } @@ -71,9 +77,10 @@ class BinaryTreeTest { tree.add(thirdKey, thirdKey.toString()) tree.add(secondKey, secondKey.toString()) - assert( tree.root?.key == firstKey && - tree.root?.right?.key == thirdKey && - tree.root?.right?.left?.key == secondKey + assert( + tree.root?.key == firstKey && + tree.root?.right?.key == thirdKey && + tree.root?.right?.left?.key == secondKey ) } @@ -87,9 +94,10 @@ class BinaryTreeTest { tree.add(secondKey, secondKey.toString()) tree.add(thirdKey, thirdKey.toString()) - assert( tree.root?.key == firstKey && - tree.root?.right?.key == secondKey && - tree.root?.right?.right?.key == thirdKey + assert( + tree.root?.key == firstKey && + tree.root?.right?.key == secondKey && + tree.root?.right?.right?.key == thirdKey ) } @@ -145,7 +153,7 @@ class BinaryTreeTest { tree.add(firstKey, firstKey.toString()) tree.add(secondKey, secondKey.toString()) - assert( tree[secondKey] == secondKey.toString() ) + assert(tree[secondKey] == secondKey.toString()) } @Test @@ -173,6 +181,7 @@ class BinaryTreeTest { assert(tree[thirdKey] == thirdKey.toString()) } + @Test fun `GetOrDefault should return value if it exist`() { val key = 1 @@ -199,6 +208,7 @@ class BinaryTreeTest { assert(tree.isKey(3)) } + @Test fun `IsKey should return false if key isn't exist (zig-zag)`() { val keys = arrayOf(6, 1, 5, 2, 4) @@ -244,7 +254,7 @@ class BinaryTreeTest { } @Test - fun `getNext should return min of right child if it exist`(){ + fun `getNext should return min of right child if it exist`() { val firstKey = -1 val secondKey = 0 val thirdKey = 1 @@ -263,7 +273,7 @@ class BinaryTreeTest { } @Test - fun `getNext should return parent if right child isn't exist`(){ + fun `getNext should return parent if right child isn't exist`() { val firstKey = -1 val secondKey = 0 val thirdKey = 1 @@ -283,7 +293,7 @@ class BinaryTreeTest { } @Test - fun `getPrev should return max of left child if it exist`(){ + fun `getPrev should return max of left child if it exist`() { val firstKey = -1 val secondKey = 0 val thirdKey = 1 @@ -302,7 +312,7 @@ class BinaryTreeTest { } @Test - fun `getPrev should return parent if left child isn't exist`(){ + fun `getPrev should return parent if left child isn't exist`() { val firstKey = -1 val secondKey = 0 val thirdKey = 1 @@ -359,6 +369,14 @@ class BinaryTreeTest { assert(max?.right === tree.root) } + @Test + fun `Merge should work correctly on 2 empty trees`() { + val secondTree = BinaryTree() + + tree.merge(secondTree) + + assert(tree.root == null) + } @Test fun `Split should work correctly`() { val firstKey = -1 @@ -387,16 +405,16 @@ class BinaryTreeTest { assert( tree.getNode(firstKey) == clonedTree.getNode(firstKey) - && tree.getNode(firstKey) !== clonedTree.getNode(firstKey) - && tree.getNode(secondKey) == clonedTree.getNode(secondKey) - && tree.getNode(secondKey) !== clonedTree.getNode(secondKey) - && tree.getNode(thirdKey) == clonedTree.getNode(thirdKey) - && tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey) + && tree.getNode(firstKey) !== clonedTree.getNode(firstKey) + && tree.getNode(secondKey) == clonedTree.getNode(secondKey) + && tree.getNode(secondKey) !== clonedTree.getNode(secondKey) + && tree.getNode(thirdKey) == clonedTree.getNode(thirdKey) + && tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey) ) } @Test - fun `Delete from empty tree if key isn't in tree should work correctly(do nothing)`(){ + fun `Delete from empty tree if key isn't in tree should work correctly(do nothing)`() { tree.delete(15) assert(true) } @@ -471,10 +489,11 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert(tree.getNode(keyRoot) === root - && root != null - && root.left === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root + assert( + tree.getNode(keyRoot) === root + && root != null + && root.left === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root ) } @@ -490,10 +509,11 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert(tree.getNode(keyRoot) === root - && root != null - && root.left === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root + assert( + tree.getNode(keyRoot) === root + && root != null + && root.left === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root ) } @@ -509,10 +529,11 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert(tree.getNode(keyRoot) === root - && root != null - && root.right === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root + assert( + tree.getNode(keyRoot) === root + && root != null + && root.right === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root ) } @@ -528,10 +549,11 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert(tree.getNode(keyRoot) === root - && root != null - && root.right === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root + assert( + tree.getNode(keyRoot) === root + && root != null + && root.right === tree.getNode(childKey) + && tree.getNode(childKey)?.parent === root ) } @@ -549,10 +571,11 @@ class BinaryTreeTest { tree.delete(keyRoot) val root = tree.root - assert(tree.getNode(keyRight) === root - && root != null - && root.left === tree.getNode(keyLeft) - && tree.getNode(keyLeft)?.parent === root + assert( + tree.getNode(keyRight) === root + && root != null + && root.left === tree.getNode(keyLeft) + && tree.getNode(keyLeft)?.parent === root ) } @@ -576,15 +599,16 @@ class BinaryTreeTest { tree.delete(keys[0]) val root = tree.root - assert(root == next - && root != null - && root.left === tree.getNode(keys[1]) - && tree.getNode(keys[1])?.parent === root - && root.right === tree.getNode(keys[2]) - && tree.getNode(keys[2])?.parent === root - && root.right != null - && root.right?.left === tree.getNode(keys[4]) - && tree.getNode(keys[4])?.parent === root.right + assert( + root == next + && root != null + && root.left === tree.getNode(keys[1]) + && tree.getNode(keys[1])?.parent === root + && root.right === tree.getNode(keys[2]) + && tree.getNode(keys[2])?.parent === root + && root.right != null + && root.right?.left === tree.getNode(keys[4]) + && tree.getNode(keys[4])?.parent === root.right ) } @@ -604,11 +628,12 @@ class BinaryTreeTest { tree.delete(keys[2]) val root = tree.root - assert(root != null - && root.left === tree.getNode(keys[1]) - && tree.getNode(keys[1])?.parent === root - && node == root.right - && root.right?.left === tree.getNode(keys[3]) + assert( + root != null + && root.left === tree.getNode(keys[1]) + && tree.getNode(keys[1])?.parent === root + && node == root.right + && root.right?.left === tree.getNode(keys[3]) ) } @@ -628,14 +653,127 @@ class BinaryTreeTest { val root = tree.root val node = tree.getNode(keys[2]) - assert(root != null - && root.left === node - && node?.parent === root - && node.left === tree.getNode(keys[3]) - && tree.getNode(keys[3])?.parent === node - && node.right === tree.getNode(keys[4]) - && tree.getNode(keys[4])?.parent === node + assert( + root != null + && root.left === node + && node?.parent === root + && node.left === tree.getNode(keys[3]) + && tree.getNode(keys[3])?.parent === node + && node.right === tree.getNode(keys[4]) + && tree.getNode(keys[4])?.parent === node ) } + // Yes, writing test on this function is very easy + + @Test + fun `toStringBeautifulWidth should work correctly on empty tree`() { + assert(tree.toStringBeautifulWidth() == "") + } + + @Test + fun `toStringBeautifulWidth should work correctly on sample`() { + // no-child, left, right and 2 child node example + // because it works similar on all this scenarios + val keys = arrayOf(0, 1, -1, 2, -2) + keys.forEach { tree.add(it, it.toString()) } + + val strTree = tree.toStringBeautifulWidth() + val expectedResult = """| ┌── (2: 2) +| ┌── (1: 1) +└── (0: 0) + └── (-1: -1) + └── (-2: -2) +""" + assert(strTree == expectedResult) + } + + @Test + fun `toStringBeautifulHeight should work correctly on empty tree`() { + assert(tree.toStringBeautifulHeight() == "") + } + + @Test + fun `toStringBeautifulHeight should work correctly on sample`() { + // no-child, left, right and 2 child node example + // because it works similar on all this scenarios + val keys = arrayOf(0, 1, -1, 2, -2) + keys.forEach { tree.add(it, it.toString()) } + + val strTree = tree.toStringBeautifulHeight() + val expectedResult = """────────────────────────┐ + (0: 0) + ┌───────────┴───────────┐ + (-1: -1) (1: 1) + ┌─────┘ └─────┐ + (-2: -2) (2: 2) +""" + assert(strTree == expectedResult) + } + + @Test + fun `toString should work correctly on empty tree`() { + assert(tree.toString() == "[]") + } + + @Test + fun `toString should work correctly on sample`() { + // no-child, left, right and 2 child node example + // because it works similar on all this scenarios + val keys = arrayOf(0, 1, -1, 2, -2) + keys.forEach { tree.add(it, it.toString()) } + + val strTree = tree.toString() + val expectedResult = "[(-2: -2), (-1: -1), (0: 0), (1: 1), (2: 2)]" + assert(strTree == expectedResult) + } + + // Negative + + @Test + fun `Add should throw IllegalArgumentException on attempt to add key to root that already exist `() { + val key = 1 + val message = + "Multiple uses of key: ${key}. To modify value use set function" + + tree.add(key, key.toString()) + + val exception = assertFailsWith { + tree.add(key, key.toString()) + } + assert(exception.message == message) + } + + @Test + fun `Add should throw IllegalArgumentException on attempt to add key to not-root that already exist`() { + val keyRoot = 0 + val key = 1 + val message = + "Multiple uses of key: ${key}. To modify value use set function" + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + + val exception = assertFailsWith { + tree.add(key, key.toString()) + } + assert(exception.message == message) + } + + @Test + fun `Merge should throw IllegalArgumentException on attempt to merge tree that isn't bigger than this`() { + val secondTree = BinaryTree() + val keys = arrayOf(0, 1, -1) + val secondKeys = arrayOf(3, 4, 0) + + keys.forEach { tree.add(it, it.toString()) } + secondKeys.forEach { secondTree.add(it, it.toString()) } + + val message = + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" + + val exception = assertFailsWith { + tree.merge(secondTree) + } + assert(exception.message == message) + } } \ No newline at end of file diff --git a/src/test/kotlin/DFSIteratorTest.kt b/src/test/kotlin/DFSIteratorTest.kt index a3ad9ef..db061cd 100644 --- a/src/test/kotlin/DFSIteratorTest.kt +++ b/src/test/kotlin/DFSIteratorTest.kt @@ -19,7 +19,7 @@ class DFSIteratorTest { } @Test - fun `DFS should work correctly simple tree sample`() { + fun `DFS should work correctly simple tree sample simple mode`() { val keys = arrayOf(2, 1, 3, 0, 4) val result = mutableListOf() val expectedResult = arrayOf(2, 1, 0, 3, 4) @@ -29,4 +29,26 @@ class DFSIteratorTest { for (i in keys.indices) assert(result[i] == expectedResult[i]) } + @Test + fun `DFS should work correctly simple tree sample symmetric mode`() { + val keys = arrayOf(2, 1, 3, 0, 4) + val result = mutableListOf() + val expectedResult = arrayOf(0, 1, 2, 3, 4) + keys.forEach { tree.add(it, it.toString()) } + for (i in tree.iterateDFS(mode = Tree.ModeDFS.SYMMETRIC)) result.add(i.key) + + for (i in keys.indices) assert(result[i] == expectedResult[i]) + } + + @Test + fun `DFS should work correctly simple tree sample reverse mode`() { + val keys = arrayOf(2, 1, 3, 0, 4) + val result = mutableListOf() + val expectedResult = arrayOf(0, 1, 4, 3, 2) + keys.forEach { tree.add(it, it.toString()) } + for (i in tree.iterateDFS(mode = Tree.ModeDFS.REVERSE)) result.add(i.key) + + for (i in keys.indices) assert(result[i] == expectedResult[i]) + } + } \ No newline at end of file From 70a109c5e77616b270037368efa5df87fef24adb Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Sat, 30 Mar 2024 06:42:25 +0700 Subject: [PATCH 21/52] Gradle optimised. Remove full information about each test. Added information about number of tests for each event --- build.gradle.kts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 19dbe8e..11ca1be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,20 @@ tasks.test { useJUnitPlatform() testLogging { - events("passed", "skipped", "failed") + events("skipped", "failed") + afterSuite( + // spell + KotlinClosure2({ desc: TestDescriptor, result: TestResult -> + // Only execute on the outermost suite + if (desc.parent == null) { + println(" **** Result: ${result.resultType} ****") + println(" > Tests: ${result.testCount}") + println(" > Passed: ${result.successfulTestCount}") + println(" > Failed: ${result.failedTestCount}") + println(" > Skipped: ${result.skippedTestCount}") + } + }) + ) } finalizedBy(tasks.jacocoTestReport) From aef9f6ad26250906899ca958c37a82ec062c877a Mon Sep 17 00:00:00 2001 From: VersusXX Date: Sat, 30 Mar 2024 04:51:19 +0300 Subject: [PATCH 22/52] edit: toString method now displays colored nodes Removed toStringBeautiful method, and instead of it we inherit toString method with modes. Added an option to print a tree in colored mode. Colored mode can be regulated by setColored() method. --- src/main/kotlin/Nodes.kt | 23 ++++++++++--- src/main/kotlin/RBTree.kt | 70 ++++++++++++++------------------------- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index 3f61399..8662895 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -1,10 +1,10 @@ -open class Node, V, T: Node> internal constructor( +open class Node, V, T : Node> internal constructor( var key: K, var value: V, internal var left: Node? = null, internal var right: Node? = null, - internal var parent: Node? = null) -{ + internal var parent: Node? = null +) { class BinaryNode, V>( key: K, @@ -20,10 +20,24 @@ open class Node, V, T: Node> internal constructor( left: RBNode? = null, right: RBNode? = null, parent: RBNode? = null, - var color: Color = Color.RED + var color: Color = Color.RED, ) : Node>(key, value, left, right, parent) { enum class Color { RED, BLACK } + companion object { + private const val RED_COLOR: String = "\u001B[31m" + private const val RESET_COLOR: String = "\u001B[0m" + var colored: Boolean = false + } + + override fun toString(): String { + return if ( colored && color == Color.RED) { + "${RED_COLOR}($key: $value)${RESET_COLOR}" + } else { + "($key: $value)" + } + } + fun max(): RBNode { var current = this while (current.right != null) { @@ -40,6 +54,7 @@ open class Node, V, T: Node> internal constructor( return current } } + class AVLNode, V>( key: K, value: V, diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 0e206ac..b2e9c1d 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -124,10 +124,13 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree } // Update sibling after rotation fixNode = - if (node == node.parent!!.left) node.parent!!.right as Node.RBNode? else node.parent!!.left as Node.RBNode? + if (node == node.parent!!.left) node.parent!!.right as Node.RBNode? + else node.parent!!.left as Node.RBNode? } else { // If the sibling is black - if (((sibling?.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.left == null) && ((sibling?.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.right == null)) { + if (((sibling?.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.left == null) + && ((sibling?.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.right == null) + ) { // If both children of sibling are black, recolor sibling and move fixNode to parent sibling?.color = Node.RBNode.Color.RED fixNode = node.parent as Node.RBNode? @@ -136,9 +139,10 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree val parentNode = node.parent!! as Node.RBNode val isLeftChild = node == parentNode.left - if ((isLeftChild && (sibling.right == null || (sibling.right as Node.RBNode?)?.color== Node.RBNode.Color.BLACK)) || - (!isLeftChild && (sibling.left == null || (sibling.left as Node.RBNode?)?.color== - Node.RBNode.Color.BLACK))) { + if ((isLeftChild && (sibling.right == null || (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK)) + || (!isLeftChild && (sibling.left == null || (sibling.left as Node.RBNode?)?.color == + Node.RBNode.Color.BLACK)) + ) { // If node is left child and sibling's right child is black or null, // or if node is right child and sibling's left child is black or null, // perform rotation and recoloring @@ -147,21 +151,20 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree sibling.color = Node.RBNode.Color.BLACK if (isLeftChild) sibling.rightRotate() else sibling.leftRotate() // Update sibling after rotation - fixNode = if (isLeftChild) parentNode.right as Node.RBNode else parentNode.left as Node.RBNode - } else if ((isLeftChild && ((sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED)) || - (!isLeftChild && ((sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED))) { + fixNode = + if (isLeftChild) parentNode.right as Node.RBNode else parentNode.left as Node.RBNode + } else if ((isLeftChild && ((sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED)) + || (!isLeftChild && ((sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED))) { // If node is left child and sibling's right child is red, // or if node is right child and sibling's left child is red, // perform rotation and additional checks for recoloring if (isLeftChild) parentNode.leftRotate() else parentNode.rightRotate() - if ((isLeftChild && (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED && - (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK && - (sibling.right as Node.RBNode?)?.right == null && - sibling.right?.left == null) || - (!isLeftChild && (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED && - (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK && sibling - .left?.left == null && - sibling.left?.right == null)) { + if ((isLeftChild && (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED + && (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK + && (sibling.right as Node.RBNode?)?.right == null && sibling.right?.left == null) + || (!isLeftChild && (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED + && (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK + && sibling.left?.left == null && sibling.left?.right == null)) { val siblingChild = if (isLeftChild) sibling.right else sibling.left (siblingChild as Node.RBNode?)?.color = Node.RBNode.Color.BLACK } @@ -230,7 +233,8 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree val siblingLeft = sibling?.left as Node.RBNode? val siblingRight = sibling?.right as Node.RBNode? - if ((siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null) && (siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null)) { + if ((siblingLeft?.color == Node.RBNode.Color.BLACK || + siblingLeft == null) && (siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null)) { // Case 2: Both children of sibling are black sibling?.color = Node.RBNode.Color.RED fixNode = parentNode @@ -262,7 +266,8 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree val siblingLeft = sibling?.left as Node.RBNode? val siblingRight = sibling?.right as Node.RBNode? - if ((siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null) && (siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null)) { + if ((siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null) + && (siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null)) { // Case 2: Both children of sibling are black sibling?.color = Node.RBNode.Color.RED fixNode = parentNode @@ -297,33 +302,8 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree return if (root == null) null else (root as Node.RBNode).min() } - - fun toStringColoredWidth(): String { - return if (this.root == null) "" - else this.toStringColoredWidth(StringBuilder(), true, StringBuilder(), this.root!! as Node.RBNode) - .toString() - } - private fun toStringColoredWidth( - prefix: StringBuilder, isTail: Boolean, buffer: StringBuilder, current: Node.RBNode - ): StringBuilder { - if (current.right != null) { - var newPrefix = StringBuilder() - newPrefix.append(prefix) - newPrefix.append(if (isTail) "| " else " ") - this.toStringColoredWidth(newPrefix, false, buffer, current.right as Node.RBNode) - } - buffer.append(prefix) - buffer.append(if (isTail) "└── " else "┌── ") - buffer.append("${current.key} (${current.color})") - buffer.append("\n") - if (current.left != null) { - var newPrefix = StringBuilder() - newPrefix.append(prefix) - newPrefix.append(if (!isTail) "| " else " ") - this.toStringColoredWidth(newPrefix, true, buffer, current.left as Node.RBNode) - } - - return buffer + fun setColored(value: Boolean) { + Node.RBNode.colored = value } private fun balanceRBTree(node: Node.RBNode) { From b74b9c3c39ba0cebeb0cf5341e499679c053c80d Mon Sep 17 00:00:00 2001 From: VersusXX Date: Sat, 30 Mar 2024 04:58:05 +0300 Subject: [PATCH 23/52] add: full cover RBNodeTest and RBTreeTest 30% covered Covered with tests toString method of RBNode. --- src/test/kotlin/RBNodeTest.kt | 39 +++++++++ src/test/kotlin/RedBlackTreeTest.kt | 123 ++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/test/kotlin/RedBlackTreeTest.kt diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt index 3fbbd3c..f841d3b 100644 --- a/src/test/kotlin/RBNodeTest.kt +++ b/src/test/kotlin/RBNodeTest.kt @@ -2,6 +2,18 @@ import org.junit.jupiter.api.Test class RBNodeTest { + @Test + fun `max should return if empty`() { + val tree = RBTree() + assert(tree.max()?.value == null) + } + + @Test + fun `min should return if empty`() { + val tree = RBTree() + assert(tree.min()?.value == null) + } + @Test fun `max should return max value`() { val tree = RBTree() @@ -36,4 +48,31 @@ class RBNodeTest { assert(tree.min()?.value == "A") } + @Test + fun `should return null if tree is empty`() { + val tree = RBTree() + assert(tree.root?.toString() == null) + } + + @Test + fun `should return node with black color`() { + val tree = RBTree() + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + + assert(tree.getNode(1)?.toString() == "(1: A)") + } + + @Test + fun `should return node with red color`() { + val tree = RBTree() + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + tree.setColored(true) + + assert(tree.getNode(1)?.toString() == "\u001B[31m(1: A)\u001B[0m") + } + } \ No newline at end of file diff --git a/src/test/kotlin/RedBlackTreeTest.kt b/src/test/kotlin/RedBlackTreeTest.kt new file mode 100644 index 0000000..d408360 --- /dev/null +++ b/src/test/kotlin/RedBlackTreeTest.kt @@ -0,0 +1,123 @@ +import org.junit.jupiter.api.BeforeEach +import kotlin.test.Test + +class RedBlackTreeTest { + private lateinit var tree: RBTree + + @BeforeEach + fun setup () { + tree = RBTree() + } + + // tests for add + @Test + fun `root should be black if has single node`() { + tree.add(1, "") + assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) + } + + @Test + fun `root should be black if has multiple nodes`() { + for (i in 1..10) { + tree.add(i, "") + } + assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) + } + + @Test + fun `root should be black if has multiple nodes and root is deleted`() { + for (i in 1..10) { + tree.add(i, "") + } + tree.root?.let { tree.delete(it.key) } + assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) + } + + + // tests for min, max + @Test + fun `max should return max node`() { + for (i in 1..10) { + tree.add(i, "") + } + assert(tree.max()?.key == 10) + } + + @Test + fun `min should return min node`() { + for (i in 1..10) { + tree.add(i, "") + } + assert(tree.min()?.key == 1) + } + + // tests for balanceRBTree + @Test + fun `balanceRBTree should return balanced tree`() { + for (i in 1..10) { + tree.add(i, "") + } + + val expectedBalanced = arrayOf(4, 2, 6, 1, 3, 5, 8, 7, 9, 10) + var index = 0 + tree.iterateBFS().forEach { + assert(it.key == expectedBalanced[index]) + index++ + } + } + + + // tests for merge + @Test + fun `tree should be empty`() { + val secondTree = RBTree() + + tree.merge(secondTree) + + assert(tree.root == null) + } + + @Test + fun `should merge second tree with empty tree`() { + val secondTree = RBTree() + secondTree.add(1, "value1") + secondTree.add(2, "value2") + + tree.merge(secondTree) + + assert(secondTree.root == tree.root) + } + + @Test + fun `should merge empty second tree with initial tree`() { + val secondTree = RBTree() + tree.add(1, "value1") + tree.add(2, "value2") + + tree.merge(secondTree) + + assert(tree.root == tree.root) + } + + @Test + fun `should merge non empty trees`() { + val secondTree = RBTree() + tree.add(1, "value1") + tree.add(2, "value2") + secondTree.add(3, "value3") + secondTree.add(4, "value4") + + tree.merge(secondTree) + + // Check if the resulting tree contains all elements from both trees + var treeNodeCount = 0 + tree.iterator().forEach { _ -> treeNodeCount++ } + assert(4 == treeNodeCount) + assert("value1" == tree.getNode(1)?.value) + assert("value2" == tree.getNode(2)?.value) + assert("value3" == tree.getNode(3)?.value) + assert("value4" == tree.getNode(4)?.value) + } + + +} \ No newline at end of file From dafb61c7486a2e7fe1a6392d1d15134ef23a4d60 Mon Sep 17 00:00:00 2001 From: Alexandr Kudrya <70281496+AlexandrKudrya@users.noreply.github.com> Date: Sat, 30 Mar 2024 19:28:39 +0700 Subject: [PATCH 24/52] Update github-actions.yml Adding jacoco report generation --- .github/workflows/github-actions.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index c0a41ac..9c5b377 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -3,7 +3,7 @@ name: Kotlin CI with Gradle on: push: branches: - [main] + [main, CI] pull_request: branches: [main] @@ -24,3 +24,6 @@ jobs: - name: Testing run: ./gradlew test + + - name: Test covering + run: ./gradlew jacocoTestReport From 2720f6a79a80a2887eef46d2ed8a3288a2af48cb Mon Sep 17 00:00:00 2001 From: Alexandr Kudrya <70281496+AlexandrKudrya@users.noreply.github.com> Date: Sat, 30 Mar 2024 19:33:09 +0700 Subject: [PATCH 25/52] Update github-actions.yml --- .github/workflows/github-actions.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index c0a41ac..2e8bd03 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -24,3 +24,12 @@ jobs: - name: Testing run: ./gradlew test + + - name: Run Test Coverage + run: ./gradlew jacocoTestReport + + - name: Generate JaCoCo Badge + uses: cicirello/jacoco-badge-generator@v2 + with: + generate-branches-badge: true + jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv From c5d2dece23dd75b1b1c5dc239d777b744344fafe Mon Sep 17 00:00:00 2001 From: VersusXX Date: Sun, 31 Mar 2024 08:13:23 +0300 Subject: [PATCH 26/52] add: tests for merge and balanceRBTree 1) Added tests for merge method, now it is fully covered. 2) Added tests for balanceRBTree, now it is also fully covered. 3) Gathered tests by their category in @Nested tests class to clear up code. --- src/test/kotlin/RedBlackTreeTest.kt | 312 +++++++++++++++++++++------- 1 file changed, 232 insertions(+), 80 deletions(-) diff --git a/src/test/kotlin/RedBlackTreeTest.kt b/src/test/kotlin/RedBlackTreeTest.kt index d408360..b20e3b8 100644 --- a/src/test/kotlin/RedBlackTreeTest.kt +++ b/src/test/kotlin/RedBlackTreeTest.kt @@ -1,5 +1,7 @@ import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested import kotlin.test.Test +import kotlin.test.assertFailsWith class RedBlackTreeTest { private lateinit var tree: RBTree @@ -8,116 +10,266 @@ class RedBlackTreeTest { fun setup () { tree = RBTree() } + @Nested + inner class AddTests { + @Test + fun `root should be black if has single node`() { + tree.add(1, "") + assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) + } - // tests for add - @Test - fun `root should be black if has single node`() { - tree.add(1, "") - assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) - } + @Test + fun `root should be black if has multiple nodes`() { + for (i in 1..10) { + tree.add(i, "") + } + assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) + } - @Test - fun `root should be black if has multiple nodes`() { - for (i in 1..10) { - tree.add(i, "") + @Test + fun `root should be black if has multiple nodes and root is deleted`() { + for (i in 1..10) { + tree.add(i, "") + } + tree.root?.let { tree.delete(it.key) } + assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) } - assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) } - @Test - fun `root should be black if has multiple nodes and root is deleted`() { - for (i in 1..10) { - tree.add(i, "") + @Nested + inner class MinimumMaximumTests { + @Test + fun `max should return max node`() { + for (i in 1..10) { + tree.add(i, "") + } + assert(tree.max()?.key == 10) + } + + @Test + fun `min should return min node`() { + for (i in 1..10) { + tree.add(i, "") + } + assert(tree.min()?.key == 1) } - tree.root?.let { tree.delete(it.key) } - assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) } + @Nested + inner class MergeTests { + @Test + fun `Merge should work correctly on 2 empty trees`() { + val secondTree = RBTree() - // tests for min, max - @Test - fun `max should return max node`() { - for (i in 1..10) { - tree.add(i, "") + tree.merge(secondTree) + + assert(tree.root == null) } - assert(tree.max()?.key == 10) - } + @Test + fun `should merge second tree with empty tree`() { + val secondTree = RBTree() + secondTree.add(1, "value1") + secondTree.add(2, "value2") - @Test - fun `min should return min node`() { - for (i in 1..10) { - tree.add(i, "") + tree.merge(secondTree) + + assert(secondTree.root == tree.root) } - assert(tree.min()?.key == 1) - } - // tests for balanceRBTree - @Test - fun `balanceRBTree should return balanced tree`() { - for (i in 1..10) { - tree.add(i, "") + @Test + fun `should merge empty second tree with initial tree`() { + val secondTree = RBTree() + tree.add(1, "value1") + tree.add(2, "value2") + + tree.merge(secondTree) + + assert(tree.root == tree.root) } - val expectedBalanced = arrayOf(4, 2, 6, 1, 3, 5, 8, 7, 9, 10) - var index = 0 - tree.iterateBFS().forEach { - assert(it.key == expectedBalanced[index]) - index++ + @Test + fun `should merge non empty trees`() { + val secondTree = RBTree() + tree.add(1, "value1") + tree.add(2, "value2") + secondTree.add(3, "value3") + secondTree.add(4, "value4") + + tree.merge(secondTree) + + // Check if the resulting tree contains all elements from both trees + var treeNodeCount = 0 + tree.iterator().forEach { _ -> treeNodeCount++ } + assert(4 == treeNodeCount) + assert("value1" == tree.getNode(1)?.value) + assert("value2" == tree.getNode(2)?.value) + assert("value3" == tree.getNode(3)?.value) + assert("value4" == tree.getNode(4)?.value) } - } + @Test + fun `Merge should throw IllegalArgumentException on attempt to merge tree that isn't bigger than this`() { + val secondTree = RBTree() + val keys = arrayOf(0, 1, -1) + val secondKeys = arrayOf(3, 4, 0) - // tests for merge - @Test - fun `tree should be empty`() { - val secondTree = RBTree() + keys.forEach { tree.add(it, it.toString()) } + secondKeys.forEach { secondTree.add(it, it.toString()) } - tree.merge(secondTree) + val message = + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" - assert(tree.root == null) + val exception = assertFailsWith { + tree.merge(secondTree) + } + assert(exception.message == message) + } } + // Tests for setColored @Test - fun `should merge second tree with empty tree`() { - val secondTree = RBTree() - secondTree.add(1, "value1") - secondTree.add(2, "value2") + fun `setColored should set color of node`() { + tree.add(1, "A") + tree.add(2, "A") + tree.add(3, "A") - tree.merge(secondTree) + tree.setColored( true ) + assert(tree.getNode(1)?.toString() == "\u001B[31m(1: A)\u001B[0m") - assert(secondTree.root == tree.root) + tree.setColored( false ) + assert(tree.getNode(1)?.toString() == "(1: A)") } - @Test - fun `should merge empty second tree with initial tree`() { - val secondTree = RBTree() - tree.add(1, "value1") - tree.add(2, "value2") + @Nested + inner class BalanceTreeTests{ + @Test + fun `balanceRBTree should return balanced tree`() { + for (i in 1..10) { + tree.add(i, "") + } - tree.merge(secondTree) + val expectedBalanced = arrayOf(4, 2, 6, 1, 3, 5, 8, 7, 9, 10) + var index = 0 + tree.iterateBFS().forEach { + assert(it.key == expectedBalanced[index]) + index++ + } + } - assert(tree.root == tree.root) - } + @Test + fun `should convert red node and it's sibling node to black if added new red node on right of grandparent`() { + // scenario explanation on picture + // | ┌── (93 Red) | ┌── (93 Black) + // └── (83 Black) -----------> └── (83 Black) + // | (add 71 Red) | ┌── (71 Red ) + // └── (47 Red) └── (47 Black) - @Test - fun `should merge non empty trees`() { - val secondTree = RBTree() - tree.add(1, "value1") - tree.add(2, "value2") - secondTree.add(3, "value3") - secondTree.add(4, "value4") - - tree.merge(secondTree) - - // Check if the resulting tree contains all elements from both trees - var treeNodeCount = 0 - tree.iterator().forEach { _ -> treeNodeCount++ } - assert(4 == treeNodeCount) - assert("value1" == tree.getNode(1)?.value) - assert("value2" == tree.getNode(2)?.value) - assert("value3" == tree.getNode(3)?.value) - assert("value4" == tree.getNode(4)?.value) - } + tree.add(47, "47") + tree.add(83, "83") + tree.add(93, "93") + tree.add(71, "71") + + val root = tree.root as Node.RBNode? + val addedNode = tree.getNode(71) as Node.RBNode? + val parent = addedNode?.parent as Node.RBNode? + val uncle = root?.right as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && parent?.color == Node.RBNode.Color.BLACK + && uncle?.color == Node.RBNode.Color.BLACK + && addedNode?.color == Node.RBNode.Color.RED + ) + } + + @Test + fun `should convert red node and it's sibling node to black if added new red node on left of grandparent`() { + // scenario explanation on picture + // | ┌── (93 Red) | ┌── (93 Black) + // | | -----------> | | ┌── (90 Red) + // └── (83 Black) (add 71 Red) └── (83 Black) + // └── (47 Red) └── (47 Black) + + tree.add(47, "47") + tree.add(83, "83") + tree.add(93, "93") + tree.add(90, "90") + + val root = tree.root as Node.RBNode? + val addedNode = tree.getNode(90) as Node.RBNode? + val parent = addedNode?.parent as Node.RBNode? + val uncle = root?.right as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && parent?.color == Node.RBNode.Color.BLACK + && uncle?.color == Node.RBNode.Color.BLACK + && addedNode?.color == Node.RBNode.Color.RED + ) + } + + @Test + fun `should right rotate if added red node after red node on left of parent`() { + // scenario explanation on picture + // | ┌── (95 Red) | ┌── (95 Red) + // | ┌── (93 Black ) | ┌── (94 Black ) + // └── (83 Black ) -----------> | | └── (93 Red) + // └── (47 Black ) (add 94 Red) └── (83 Black ) + // └── (47 Black ) + tree.add(47, "") + tree.add(83, "") + tree.add(93, "") + tree.add(95, "") + tree.add(94, "") + + val root = tree.root as Node.RBNode? + val addedNode = tree.getNode(94) as Node.RBNode? + val leftChild = addedNode?.left as Node.RBNode? + val rightChild = addedNode?.right as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && addedNode?.color == Node.RBNode.Color.BLACK + && leftChild?.color == Node.RBNode.Color.RED + && rightChild?.color == Node.RBNode.Color.RED + ) + } + + @Test + fun `should left rotate if added red node after red node on right of parent`() { + // scenario explanation on picture + // | | ┌── (93 Red) + // | ┌── (93 Black ) | ┌── (92 Black ) + // | | └── (90 Red) | | └── (90 Red) + // └── (83 Black ) -----------> └── (83 Black ) + // └── (47 Black ) (add 92 Red) └── (47 Black ) + // + + tree.add(47, "") + tree.add(83, "") + tree.add(93, "") + tree.add(90, "") + tree.add(92, "") + + val root = tree.root as Node.RBNode? + val addedNode = tree.getNode(92) as Node.RBNode? + val leftChild = addedNode?.left as Node.RBNode? + val rightChild = addedNode?.right as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && addedNode?.color == Node.RBNode.Color.BLACK + && leftChild?.color == Node.RBNode.Color.RED + && rightChild?.color == Node.RBNode.Color.RED + ) + } + + + } } \ No newline at end of file From ddf52e3fbe397642655f1c76c32d520fa563ce61 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Mon, 1 Apr 2024 07:12:42 +0300 Subject: [PATCH 27/52] fix: delete method in Red-Black tree Rewrote the delete method in Red-Black tree to fix unnoticed bugs in previous version. Added some helper methods to the Red-Black Node class. --- src/main/kotlin/Nodes.kt | 16 ++ src/main/kotlin/RBTree.kt | 363 ++++++++++++++------------------------ 2 files changed, 144 insertions(+), 235 deletions(-) diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index 8662895..c77996b 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -53,6 +53,22 @@ open class Node, V, T : Node> internal constructor( } return current } + val isOnLeft: Boolean + get() = this == parent?.left + + fun sibling(): RBNode? { + + if (parent == null) return null + + if (isOnLeft) return parent!!.right as RBNode? + + return parent!!.left as RBNode? + } + + fun hasRedChild(): Boolean { + return (left != null && (left as RBNode).color == Color.RED) || + (right != null && (right as RBNode).color == Color.RED) + } } class AVLNode, V>( diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index b2e9c1d..3ee8ff2 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,7 +1,7 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree>(root) { override fun add(key: K, value: V) { - val node: Node> = Node.RBNode(key, value) + val node: Node.RBNode = Node.RBNode(key, value) this.addNode(node) } @@ -27,273 +27,166 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree override fun delete(key: K) { val node = this.getNode(key) as Node.RBNode? ?: return - if (node.color == Node.RBNode.Color.RED) { - if (node.left == null && node.right == null) { - deleteRedNodeWithNoChildren(node) - } else if (node.left != null && node.right != null) { - deleteNodeWithTwoChildren(node) - } - } else { - if (node.left == null && node.right == null) { - deleteBlackNodeWithNoChildren(node) - } else if (node.left != null && node.right != null) { - deleteNodeWithTwoChildren(node) - } else { - deleteBlackNodeWithOneChild(node) - } - } + deleteNode(node) } - private fun deleteRedNodeWithNoChildren(node: Node.RBNode) { - if (node == node.parent?.left) { - node.parent?.left = null - } else { - node.parent?.right = null - } - } + private fun deleteNode(nodeToDelete: Node.RBNode) { + val replacementNode = BSTreplace(nodeToDelete) + + // Determine if both 'replacementNode' and 'nodeToDelete' are black + val areBothBlack = + (replacementNode == null || replacementNode.color == Node.RBNode.Color.BLACK) && (nodeToDelete.color == Node.RBNode.Color.BLACK) - private fun deleteNodeWithTwoChildren(node: Node.RBNode) { - val successor = (node.right as Node.RBNode).min() - val tmpValue = node.value - val tmpKey = node.key - node.key = successor.key - node.value = successor.value - successor.key = tmpKey - successor.value = tmpValue - if (successor.color == Node.RBNode.Color.RED) { - if (successor.left != null && successor.right != null) { - deleteNodeWithTwoChildren(successor) + val parentNode = nodeToDelete.parent + + // Case 1: 'nodeToDelete' has no replacement node + if (replacementNode == null) { + if (nodeToDelete == root) { + root = null } else { - if (successor.left != null || successor.right != null) { - throw IllegalStateException("Successor should have two children or None") + if (areBothBlack) { + // Both 'replacementNode' and 'nodeToDelete' are black, so fix double black at 'nodeToDelete' + fixDoubleBlack(nodeToDelete) + } else if (nodeToDelete.sibling() != null) { + nodeToDelete.sibling()!!.color = Node.RBNode.Color.RED } - deleteRedNodeWithNoChildren(successor) + // Delete 'nodeToDelete' from the tree + if (nodeToDelete.isOnLeft) parentNode!!.left = null + else parentNode!!.right = null } - } else { - if (successor.left == null && successor.right == null) { - deleteBlackNodeWithNoChildren(successor) - } else if (successor.left != null && successor.right != null) { - deleteNodeWithTwoChildren(successor) + return + } + + // Case 2: 'nodeToDelete' has only one child + if (nodeToDelete.left == null || nodeToDelete.right == null) { + if (nodeToDelete == root) { + // 'nodeToDelete' is the root, so assign the value of 'replacementNode' to 'nodeToDelete' and delete 'replacementNode' + nodeToDelete.key = replacementNode.key + nodeToDelete.right = null + nodeToDelete.left = nodeToDelete.right + // Delete 'replacementNode' } else { - deleteBlackNodeWithOneChild(successor) + // Detach 'nodeToDelete' from the tree and move 'replacementNode' up + if (nodeToDelete.isOnLeft) parentNode!!.left = replacementNode + else parentNode!!.right = replacementNode + + replacementNode.parent = parentNode + + if (areBothBlack) { + // Both 'replacementNode' and 'nodeToDelete' are black, so fix double black at 'replacementNode' + fixDoubleBlack(replacementNode) + } else { + // Either 'replacementNode' or 'nodeToDelete' is red, so color 'replacementNode' black + replacementNode.color = Node.RBNode.Color.BLACK + } } + return } -// if ((successor.parent as Node.RBNode).color == Node.RBNode.Color.RED) { -// (successor.parent as Node.RBNode).leftRotate() -// } + // Case 3: 'nodeToDelete' has two children, swap values with successor and recurse + swapValues(replacementNode, nodeToDelete) + deleteNode(replacementNode) } - private fun deleteBlackNodeWithOneChild(node: Node.RBNode) { - if (node.parent == null) { - root = node.left ?: node.right - (root as Node.RBNode).color = Node.RBNode.Color.BLACK - return - } - val child = node.left as Node.RBNode? ?: node.right as Node.RBNode - val tmpValue = node.value - val tmpKey = node.key - node.key = child.key - node.value = child.value - child.key = tmpKey - child.value = tmpValue - - if (child.color == Node.RBNode.Color.BLACK) { - deleteBlackNodeWithNoChildren(child) - } else { - deleteRedNodeWithNoChildren(child) - } + private fun swapValues(nodeA: Node.RBNode, nodeB: Node.RBNode) { + val tempKey = nodeA.key + val tempValue = nodeA.value + + nodeA.key = nodeB.key + nodeA.value = nodeB.value + + nodeB.key = tempKey + nodeB.value = tempValue } - private fun deleteBlackNodeWithNoChildren(node: Node.RBNode) { - if (node.parent == null) { - // If the node is the root, set the root to null - root = null - } else { - var fixNode: Node.RBNode? = null - var sibling = - if (node == node.parent!!.left) node.parent!!.right as Node.RBNode? else node.parent!!.left as Node.RBNode? - if (sibling?.color == Node.RBNode.Color.RED) { - // If the sibling is red, recolor and perform rotations + private fun fixDoubleBlack(doubleBlackNode: Node.RBNode?) { + if (doubleBlackNode == null || doubleBlackNode == root) return + + // Retrieve the sibling and parent of the double black node + val sibling = doubleBlackNode.sibling() + val parent = doubleBlackNode.parent as Node.RBNode? + + // If there is no sibling, the double black is pushed up to its parent + if (sibling == null) fixDoubleBlack(parent) + else { + if (parent == null) return + if (sibling.color == Node.RBNode.Color.RED) { + // If the sibling is red, perform rotation and color changes + parent.color = Node.RBNode.Color.RED sibling.color = Node.RBNode.Color.BLACK - (node.parent as Node.RBNode).color = Node.RBNode.Color.RED - if (node == node.parent!!.left) { - (node.parent as Node.RBNode).leftRotate() - } else { - (node.parent as Node.RBNode).rightRotate() - } - // Update sibling after rotation - fixNode = - if (node == node.parent!!.left) node.parent!!.right as Node.RBNode? - else node.parent!!.left as Node.RBNode? + + if (sibling.isOnLeft) parent.rightRotate() // Right case + else parent.leftRotate() // Left case + + fixDoubleBlack(doubleBlackNode) // Recursively fix double black } else { // If the sibling is black - if (((sibling?.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.left == null) - && ((sibling?.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK || sibling?.right == null) - ) { - // If both children of sibling are black, recolor sibling and move fixNode to parent - sibling?.color = Node.RBNode.Color.RED - fixNode = node.parent as Node.RBNode? - } else { - if (node.parent != null) { - val parentNode = node.parent!! as Node.RBNode - val isLeftChild = node == parentNode.left - - if ((isLeftChild && (sibling.right == null || (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK)) - || (!isLeftChild && (sibling.left == null || (sibling.left as Node.RBNode?)?.color == - Node.RBNode.Color.BLACK)) - ) { - // If node is left child and sibling's right child is black or null, - // or if node is right child and sibling's left child is black or null, - // perform rotation and recoloring - val siblingChild = if (isLeftChild) sibling.left else sibling.right - (siblingChild as Node.RBNode?)?.color = Node.RBNode.Color.BLACK - sibling.color = Node.RBNode.Color.BLACK - if (isLeftChild) sibling.rightRotate() else sibling.leftRotate() - // Update sibling after rotation - fixNode = - if (isLeftChild) parentNode.right as Node.RBNode else parentNode.left as Node.RBNode - } else if ((isLeftChild && ((sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED)) - || (!isLeftChild && ((sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED))) { - // If node is left child and sibling's right child is red, - // or if node is right child and sibling's left child is red, - // perform rotation and additional checks for recoloring - if (isLeftChild) parentNode.leftRotate() else parentNode.rightRotate() - if ((isLeftChild && (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.RED - && (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.BLACK - && (sibling.right as Node.RBNode?)?.right == null && sibling.right?.left == null) - || (!isLeftChild && (sibling.left as Node.RBNode?)?.color == Node.RBNode.Color.RED - && (sibling.right as Node.RBNode?)?.color == Node.RBNode.Color.BLACK - && sibling.left?.left == null && sibling.left?.right == null)) { - val siblingChild = if (isLeftChild) sibling.right else sibling.left - (siblingChild as Node.RBNode?)?.color = Node.RBNode.Color.BLACK - } + if (sibling.hasRedChild()) { + // If at least one child of the sibling is red + val siblingLeft = sibling.left as Node.RBNode? + val siblingRight = sibling.right as Node.RBNode? + if (sibling.isOnLeft) { + // Left subtree of the sibling + if (siblingLeft?.color == Node.RBNode.Color.RED) { + // Left-left case + siblingLeft.color = sibling.color + sibling.color = parent.color + parent.rightRotate() + } else { + // Left-right case + siblingRight?.color = parent.color + sibling.leftRotate() + parent.rightRotate() } - } - // sometimes it helps to balance the tree, else it does nothing - balanceRBTree(node) - // Perform additional fix-up operations if needed - if (fixNode != null) { - if (fixNode == fixNode.parent!!.left) { - (fixNode.parent as Node.RBNode).leftRotate() + } else { + // Right subtree of the sibling + if (siblingRight?.color == Node.RBNode.Color.RED) { + // Right-right case + siblingRight.color = sibling.color + sibling.color = parent.color + parent.leftRotate() } else { - (fixNode.parent as Node.RBNode).rightRotate() + // Right-left case + siblingLeft?.color = parent.color + sibling.rightRotate() + parent.leftRotate() } - fixNode.color = (fixNode.parent as Node.RBNode).color - (fixNode.parent as Node.RBNode).color = Node.RBNode.Color.BLACK } + parent.color = Node.RBNode.Color.BLACK + } else { + // If both children of the sibling are black + sibling.color = Node.RBNode.Color.RED + if (parent.color == Node.RBNode.Color.BLACK) fixDoubleBlack(parent) + else parent.color = Node.RBNode.Color.BLACK } } - // Transplant node with null and fix deletion - transplant(node, null) - if (fixNode != null) { - fixDeletion(fixNode) - } } } - private fun transplant(u: Node.RBNode?, v: Node.RBNode?) { - if (u?.parent == null) { - // If u is the root, set v as the new root - root = v - } else if (u == u.parent?.left) { - // If u is the left child of its parent, set v as the new left child - u.parent?.left = v - } else { - // Otherwise, u is the right child of its parent, set v as the new right child - u.parent?.right = v - } - // Update v's parent to be u's parent - v?.parent = u?.parent + private fun successor(x: Node.RBNode): Node.RBNode { + var temp = x + while (temp.left != null) temp = temp.left as Node.RBNode + return temp } - private fun fixDeletion(node: Node.RBNode?) { - var fixNode = node - - while (fixNode != null && fixNode != root && fixNode.color == Node.RBNode.Color.BLACK) { - val parentNode = fixNode.parent as Node.RBNode? ?: break - val isLeftChild = fixNode == parentNode.left - var sibling = - if (isLeftChild) parentNode.right as Node.RBNode? else parentNode.left as Node.RBNode? - - if (sibling == null) { - // No sibling, continue fixup from parent - fixNode.color = Node.RBNode.Color.RED - - } - if (fixNode == parentNode.left) { - - if (sibling?.color == Node.RBNode.Color.RED) { - // Case 1: Sibling is red - sibling.color = Node.RBNode.Color.BLACK - parentNode.color = Node.RBNode.Color.RED - parentNode.leftRotate() - sibling = parentNode.right as Node.RBNode? - } - - val siblingLeft = sibling?.left as Node.RBNode? - val siblingRight = sibling?.right as Node.RBNode? - if ((siblingLeft?.color == Node.RBNode.Color.BLACK || - siblingLeft == null) && (siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null)) { - // Case 2: Both children of sibling are black - sibling?.color = Node.RBNode.Color.RED - fixNode = parentNode - } else { - if ((siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null)) { - // Case 3: Left child of sibling is red - siblingLeft?.color = Node.RBNode.Color.BLACK - sibling?.color = Node.RBNode.Color.RED - sibling?.rightRotate() - sibling = parentNode.right as Node.RBNode? - } - - // Case 4: Right child of sibling is red - sibling?.color = parentNode.color - parentNode.color = Node.RBNode.Color.BLACK - siblingRight?.color = Node.RBNode.Color.BLACK - parentNode.leftRotate() - fixNode = root as Node.RBNode? - } - } else { - - if (sibling?.color == Node.RBNode.Color.RED) { - // Case 1: Sibling is red - sibling.color = Node.RBNode.Color.BLACK - parentNode.color = Node.RBNode.Color.RED - parentNode.rightRotate() - sibling = parentNode.left as Node.RBNode? - } - - val siblingLeft = sibling?.left as Node.RBNode? - val siblingRight = sibling?.right as Node.RBNode? - if ((siblingRight?.color == Node.RBNode.Color.BLACK || siblingRight == null) - && (siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null)) { - // Case 2: Both children of sibling are black - sibling?.color = Node.RBNode.Color.RED - fixNode = parentNode - } else { - if ((siblingLeft?.color == Node.RBNode.Color.BLACK || siblingLeft == null)) { - // Case 3: Right child of sibling is red - siblingRight?.color = Node.RBNode.Color.BLACK - sibling?.color = Node.RBNode.Color.RED - sibling?.leftRotate() - sibling = parentNode.left as Node.RBNode? - } + // find node that replaces a deleted node in BST + private fun BSTreplace(node: Node.RBNode): Node.RBNode? { + // If the node has two children + if (node.left != null && node.right != null) { + return successor(node.right as Node.RBNode) + } - // Case 4: Left child of sibling is red - sibling?.color = parentNode.color - parentNode.color = Node.RBNode.Color.BLACK - siblingLeft?.color = Node.RBNode.Color.BLACK - parentNode.rightRotate() - fixNode = root as Node.RBNode? - } - } + if (node.left == null && node.right == null) { + return null // There is no replacement } - fixNode?.color = Node.RBNode.Color.BLACK + return if (node.left != null) { + node.left as Node.RBNode? + } else { + node.right as Node.RBNode? + } } - override fun max(): Node.RBNode? { return if (root == null) null else (root as Node.RBNode).max() } From 66a046aeba3401ef38bd562788cf8fe5547def65 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Mon, 1 Apr 2024 07:15:09 +0300 Subject: [PATCH 28/52] add: tests to Red-Black Node Added some more tests to Red-Black Node Tests to cover newly added helper methods in Red-Black Node class. --- src/test/kotlin/RBNodeTest.kt | 108 +++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt index f841d3b..3ae6c4b 100644 --- a/src/test/kotlin/RBNodeTest.kt +++ b/src/test/kotlin/RBNodeTest.kt @@ -1,22 +1,27 @@ +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class RBNodeTest { + private lateinit var tree: RBTree + @BeforeEach + fun setup() { + tree = RBTree() + } + + // max and min tests @Test fun `max should return if empty`() { - val tree = RBTree() assert(tree.max()?.value == null) } @Test fun `min should return if empty`() { - val tree = RBTree() assert(tree.min()?.value == null) } @Test fun `max should return max value`() { - val tree = RBTree() tree.add(1, "A") tree.add(2, "B") tree.add(3, "C") @@ -33,7 +38,6 @@ class RBNodeTest { @Test fun `min should return min value`() { - val tree = RBTree() tree.add(1, "A") tree.add(2, "B") tree.add(3, "C") @@ -48,15 +52,15 @@ class RBNodeTest { assert(tree.min()?.value == "A") } + + // toString tests @Test fun `should return null if tree is empty`() { - val tree = RBTree() assert(tree.root?.toString() == null) } @Test fun `should return node with black color`() { - val tree = RBTree() tree.add(1, "A") tree.add(2, "B") tree.add(3, "C") @@ -66,7 +70,6 @@ class RBNodeTest { @Test fun `should return node with red color`() { - val tree = RBTree() tree.add(1, "A") tree.add(2, "B") tree.add(3, "C") @@ -75,4 +78,95 @@ class RBNodeTest { assert(tree.getNode(1)?.toString() == "\u001B[31m(1: A)\u001B[0m") } + // isOnLeft tests + @Test + fun `isOnLeft should return false to root node`() { + tree.add(1, "A") + assert(!(tree.root as Node.RBNode).isOnLeft) + } + + @Test + fun `isOnLeft should return true`() { + tree.add(1, "A") + tree.add(2, "D") + tree.add(3, "C") + + assert((tree.getNode(1) as Node.RBNode).isOnLeft) + } + + @Test + fun `isOnLeft should return false`() { + tree.add(1, "A") + tree.add(2, "D") + tree.add(3, "C") + + assert(!(tree.getNode(3) as Node.RBNode).isOnLeft) + } + + + // sibling tests + @Test + fun `sibling should return null if parent is null`() { + tree.add(1, "A") + assert((tree.root as Node.RBNode).sibling() == null) + } + + @Test + fun `sibling should return right node`() { + tree.add(1, "A") + tree.add(2, "D") + tree.add(3, "C") + + assert((tree.getNode(1) as Node.RBNode).sibling()?.value == "C") + } + + @Test + fun `sibling should return left node`() { + tree.add(1, "A") + tree.add(2, "D") + tree.add(3, "C") + + assert((tree.getNode(3) as Node.RBNode).sibling()?.value == "A") + } + + + // hasRedChild tests + @Test + fun `should return false if has no child`() { + tree.add(1, "A") + + assert(!(tree.getNode(1) as Node.RBNode).hasRedChild()) + } + + @Test + fun `should return false if both children are black`() { + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + + // because Red-Black tree is self-balancing, it balances the nodes automatically, that is wht for the sake of + // the test we need to set the color of the nodes to black manually + tree.getNode(1)?.let { (it as Node.RBNode).color = Node.RBNode.Color.BLACK } + tree.getNode(3)?.let { (it as Node.RBNode).color = Node.RBNode.Color.BLACK } + + assert(!(tree.getNode(2) as Node.RBNode).hasRedChild()) + } + + @Test + fun `should return true if left child is red`() { + tree.add(2, "B") + tree.add(1, "A") + tree.setColored(true) + + assert((tree.getNode(2) as Node.RBNode).hasRedChild()) + } + + @Test + fun `should return true if right child is red`() { + tree.add(1, "A") + tree.add(2, "B") + tree.setColored(true) + + assert((tree.getNode(1) as Node.RBNode).hasRedChild()) + } } \ No newline at end of file From 27a02f30fa8b56b11b21bab989bafe802c245315 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Mon, 1 Apr 2024 07:26:32 +0300 Subject: [PATCH 29/52] add: more tests for delete method Added some basic tests for delete method. 70% covered. --- src/test/kotlin/RedBlackTreeTest.kt | 127 ++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/test/kotlin/RedBlackTreeTest.kt b/src/test/kotlin/RedBlackTreeTest.kt index b20e3b8..1a70d64 100644 --- a/src/test/kotlin/RedBlackTreeTest.kt +++ b/src/test/kotlin/RedBlackTreeTest.kt @@ -272,4 +272,131 @@ class RedBlackTreeTest { } + @Nested + inner class DeleteNodeTests { + + @Test + fun `should not find node and return`() { + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + + tree.delete(4) + + assert(tree.getNode(1) != null) + assert(tree.getNode(2) != null) + assert(tree.getNode(3) != null) + } + + @Test + fun `should delete root with no children`() { + tree.add(1, "A") + + tree.delete(1) + + assert(tree.root == null) + } + + @Test + fun `should delete right red node with no children`() { + tree.add(1, "A") + tree.add(2, "B") + + tree.delete(2) + val root = tree.root as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 1 + && root.left == null + && root.right == null + && tree.getNode(2) == null + ) + } + + @Test + fun `should delete left red node with no children`() { + tree.add(2, "B") + tree.add(1, "A") + + tree.delete(1) + val root = tree.root as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 2 + && root.left == null + && root.right == null + && tree.getNode(1) == null + ) + } + + @Test + fun `should delete root with one child`() { + tree.add(1, "A") + tree.add(2, "B") + + tree.delete(1) + val root = tree.root as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 2 + && root.left == null + && root.right == null + && tree.getNode(1) == null + ) + } + + @Test + fun `should delete node with 2 children and red successor`() { + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + + tree.delete(2) + val root = tree.root as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 3 + && root.right == null + && root.left?.key == 1 + && tree.getNode(2) == null + ) + } + + @Test + fun `should delete black node with one red child` () { + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + tree.add(4, "D") + + tree.delete(3) + + val root = tree.root as Node.RBNode? + + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 2 + && root.left?.key == 1 + && root.right?.key == 4 + && tree.getNode(3) == null + ) + } + + @Test + fun `should delete black node with one black child` () { + + } + + } + + } \ No newline at end of file From 1f2e24dee828dad40096fe98de0e629c82a3852a Mon Sep 17 00:00:00 2001 From: VersusXX Date: Mon, 1 Apr 2024 09:21:10 +0300 Subject: [PATCH 30/52] add: delete and add methods' tests. Added tessts for delete method and add method. Total instructions covering - 93% Total branches covering - 82% --- src/test/kotlin/RBNodeTest.kt | 2 +- src/test/kotlin/RedBlackTreeTest.kt | 311 +++++++++++++++++++++++++++- 2 files changed, 304 insertions(+), 9 deletions(-) diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt index 3ae6c4b..103f122 100644 --- a/src/test/kotlin/RBNodeTest.kt +++ b/src/test/kotlin/RBNodeTest.kt @@ -144,7 +144,7 @@ class RBNodeTest { tree.add(2, "B") tree.add(3, "C") - // because Red-Black tree is self-balancing, it balances the nodes automatically, that is wht for the sake of + // because Red-Black tree is self-balancing, it balances the nodes automatically, that is why for the sake of // the test we need to set the color of the nodes to black manually tree.getNode(1)?.let { (it as Node.RBNode).color = Node.RBNode.Color.BLACK } tree.getNode(3)?.let { (it as Node.RBNode).color = Node.RBNode.Color.BLACK } diff --git a/src/test/kotlin/RedBlackTreeTest.kt b/src/test/kotlin/RedBlackTreeTest.kt index 1a70d64..ff1c33d 100644 --- a/src/test/kotlin/RedBlackTreeTest.kt +++ b/src/test/kotlin/RedBlackTreeTest.kt @@ -7,9 +7,10 @@ class RedBlackTreeTest { private lateinit var tree: RBTree @BeforeEach - fun setup () { + fun setup() { tree = RBTree() } + @Nested inner class AddTests { @Test @@ -34,6 +35,27 @@ class RedBlackTreeTest { tree.root?.let { tree.delete(it.key) } assert((tree.root as Node.RBNode?)?.color == Node.RBNode.Color.BLACK) } + + @Test + fun `should right rotate when add, then change root`() { + tree.add(85, "A") + tree.add(56, "B") + tree.add(27, "C") + + val root = tree.root as Node.RBNode? + val left = root?.left as Node.RBNode? + val right = root?.right as Node.RBNode? + + assert( + root != null + && root.key == 56 + && root.color == Node.RBNode.Color.BLACK + && left?.key == 27 + && left.color == Node.RBNode.Color.RED + && right?.key == 85 + && right.color == Node.RBNode.Color.RED + ) + } } @Nested @@ -65,6 +87,7 @@ class RedBlackTreeTest { assert(tree.root == null) } + @Test fun `should merge second tree with empty tree`() { val secondTree = RBTree() @@ -133,15 +156,15 @@ class RedBlackTreeTest { tree.add(2, "A") tree.add(3, "A") - tree.setColored( true ) + tree.setColored(true) assert(tree.getNode(1)?.toString() == "\u001B[31m(1: A)\u001B[0m") - tree.setColored( false ) + tree.setColored(false) assert(tree.getNode(1)?.toString() == "(1: A)") } @Nested - inner class BalanceTreeTests{ + inner class BalanceTreeTests { @Test fun `balanceRBTree should return balanced tree`() { for (i in 1..10) { @@ -272,6 +295,12 @@ class RedBlackTreeTest { } + /** + * WARNING: + * because Red-Black tree is self-balancing, it balances the nodes automatically, that is why for the sake of + * the test we need to set the color of the nodes manually. The cases are totally possible in the real world, I + * set colors manually to make easier to understand the test cases. + */ @Nested inner class DeleteNodeTests { @@ -371,7 +400,7 @@ class RedBlackTreeTest { } @Test - fun `should delete black node with one red child` () { + fun `should delete black node with one red child`() { tree.add(1, "A") tree.add(2, "B") tree.add(3, "C") @@ -392,11 +421,277 @@ class RedBlackTreeTest { } @Test - fun `should delete black node with one black child` () { + fun `should delete black node with one black child`() { + // scenario explanation on picture + // | ┌── (6 Black) | ┌── (6 Black) + // | ┌── (5 Black) | ┌── (5 Red) + // | | └── (4 Black) -----------> | | └── (4 Black) + // └── (3 Black) (delete 2) └── (3 Black) + // └── (2 Black) └── (1 Black) + // └── (1 Black) + tree.add(3, "C") + tree.add(2, "B") + tree.add(4, "D") + tree.add(1, "A") + tree.add(5, "E") + tree.add(6, "F") + + (tree.getNode(1) as Node.RBNode?)?.color = Node.RBNode.Color.BLACK + (tree.getNode(4) as Node.RBNode?)?.color = Node.RBNode.Color.BLACK + (tree.getNode(6) as Node.RBNode?)?.color = Node.RBNode.Color.BLACK + + tree.delete(2) + val root = tree.root as Node.RBNode? + val rootRight = root?.right as Node.RBNode? + val rootRightRight = rootRight?.right as Node.RBNode? + val rootRightLeft = rootRight?.left as Node.RBNode? + // check if the tree is balanced + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 3 + && root.left?.key == 1 + && root.right?.key == 5 + && rootRight?.color == Node.RBNode.Color.RED + && rootRightLeft?.key == 4 + && rootRightLeft.color == Node.RBNode.Color.BLACK + && rootRightRight?.key == 6 + && rootRightRight.color == Node.RBNode.Color.BLACK + && tree.getNode(2) == null + ) } - } + @Test + fun `should delete black node with 2 black children`() { + // scenario explanation on picture + // | ┌── (6 Red) | ┌── (6 Red) + // | ┌── (5 Black) | ┌── (5 Black) + // | | └── (4 Red) ------------> └── (4 Black) + // └── (3 Black) └── (2 Black) + // └── (2 Black) └── (1 Red) + // └── (1 Red) + + tree.add(3, "C") + tree.add(2, "B") + tree.add(4, "D") + tree.add(1, "A") + tree.add(5, "E") + tree.add(6, "F") + + tree.delete(3) + val root = tree.root as Node.RBNode? + val rootRight = root?.right as Node.RBNode? + val rootLeft = root?.left as Node.RBNode? + val rootLeftLeft = rootLeft?.left as Node.RBNode? + val rootRightRight = rootRight?.right as Node.RBNode? + + // check if the tree is balanced + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 4 + && rootLeft?.key == 2 + && rootRight?.key == 5 + && rootRight.color == Node.RBNode.Color.BLACK + && rootLeft.color == Node.RBNode.Color.BLACK + && rootLeftLeft?.key == 1 + && rootLeftLeft.color == Node.RBNode.Color.RED + && rootRightRight?.key == 6 + && rootRightRight.color == Node.RBNode.Color.RED + && tree.getNode(3) == null + ) + } + + @Test + fun `should delete red node with 2 children and fixDoubleBlack sibling on left`() { + // scenario explanation on picture + // | ┌── (93 Black) | ┌── (93 Black) + // | ┌── (90 Red) | ┌── (90 Red) + // | | | ┌── (81 Red) | | | ┌── (81 Red) + // | | └── (69 Black) | | └── (69 Black) + // | | └── (62 Red) | | └── (62 Red) + // └── (53 Black) -----------> └── (53 Black) + // | ┌── (43 Black) (delete 32) | ┌── (43 Black) + // └── (32 Red) └── (27 Red) + // | ┌── (27 Red) └── (17 Black) + // └── (17 Black) + val array = arrayOf( + 32, 81, 17, 90, 93, 43, 27, 53, 69, 62 + ) + + for (num in array) { + tree.add(num, "$num") + } + tree.delete(32) + + val root = tree.root as Node.RBNode? + val rootLeft = root?.left as Node.RBNode? + val rootLeftLeft = rootLeft?.left as Node.RBNode? + val rootLeftRight = rootLeft?.right as Node.RBNode? + + // check if the tree is balanced + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 53 + && rootLeft?.key == 27 + && rootLeft.color == Node.RBNode.Color.RED + && rootLeftLeft?.key == 17 + && rootLeftLeft.color == Node.RBNode.Color.BLACK + && rootLeftRight?.key == 43 + && rootLeftRight.color == Node.RBNode.Color.BLACK + && tree.getNode(32) == null + ) + } + + @Test + fun `should delete black node with no children, and fixDoubleBlack sibling on left and sibling's left child is red`() { + // scenario explanation on picture + // | ┌── (93 Black) | ┌── (90 Black) + // | ┌── (90 Red) | | └── (81 Red) + // | | | ┌── (81 Red) | ┌── (69 Red) + // | | └── (69 Black) | | └── (62 Black) + // | | └── (62 Red) -----------> └── (53 Black) + // └── (53 Black) (delete 93) | ┌── (43 Black) + // | ┌── (43 Black) └── (27 Red) + // └── (27 Red) └── (17 Black) + // └── (17 Black) + val array = arrayOf( + 32, 81, 17, 90, 93, 43, 27, 53, 69, 62 + ) + + for (num in array) { + tree.add(num, "$num") + } + tree.delete(32) + tree.delete(93) + + val root = tree.root as Node.RBNode? + val rootRight = root?.right as Node.RBNode? + val rootRightLeft = rootRight?.left as Node.RBNode? + val rootRightRight = rootRight?.right as Node.RBNode? + val rootRightRightLeft = rootRightRight?.left as Node.RBNode? + + // check if the tree is balanced + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 53 + && rootRight?.key == 69 + && rootRight.color == Node.RBNode.Color.RED + && rootRightLeft?.key == 62 + && rootRightLeft.color == Node.RBNode.Color.BLACK + && rootRightRight?.key == 90 + && rootRightRight.color == Node.RBNode.Color.BLACK + && rootRightRightLeft?.key == 81 + && rootRightRightLeft.color == Node.RBNode.Color.RED + && tree.getNode(93) == null + ) + } + + @Test + fun `should delete black node with no children, fixDoubleBlack sibling on right`() { + // scenario explanation on picture + // | ┌── (93 Black) | ┌── (93 Black) + // | ┌── (84 Red) | ┌── (84 Red) + // | | └── (71 Black) | | └── (71 Black) + // | | └── (68 Red) | | └── (68 Red) + // └── (60 Black) -----------> └── (60 Black) + // | ┌── (59 Black) (delete 23) | ┌── (59 Black) + // | | └── (49 Red) └── (49 Red) + // └── (42 Red) └── (42 Black) + // └── (23 Black) + val array = arrayOf( + 60, 84, 23, 71, 93, 59, 68, 42, 49 + ) + for (num in array) { + tree.add(num, "$num") + } + tree.delete(23) + + val root = tree.root as Node.RBNode? + val rootRight = root?.right as Node.RBNode? + val rootLeft = root?.left as Node.RBNode? + val rootRightLeft = rootRight?.left as Node.RBNode? + val rootRightRight = rootRight?.right as Node.RBNode? + val rootRightLeftLeft = rootRightLeft?.left as Node.RBNode? + val rootLeftRight = rootLeft?.right as Node.RBNode? + val rootLeftLeft = rootLeft?.left as Node.RBNode? + + // check if the tree is balanced + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 60 + && rootLeft?.key == 49 + && rootLeft.color == Node.RBNode.Color.RED + && rootLeftLeft?.key == 42 + && rootLeftLeft.color == Node.RBNode.Color.BLACK + && rootLeftRight?.key == 59 + && rootLeftRight.color == Node.RBNode.Color.BLACK + && rootRight?.key == 84 + && rootRight.color == Node.RBNode.Color.RED + && rootRightLeft?.key == 71 + && rootRightLeft.color == Node.RBNode.Color.BLACK + && rootRightRight?.key == 93 + && rootRightRight.color == Node.RBNode.Color.BLACK + && rootRightLeftLeft?.key == 68 + && rootRightLeftLeft.color == Node.RBNode.Color.RED + && tree.getNode(23) == null + ) + } + + @Test + fun `should delete black node with 2 children, child's sibling - red, fixDoubleBlack sibling on right`() { + // scenario explanation on picture + // | ┌── (88 Red) + // | ┌── (78 Black) | ┌── (88 Black) + // | ┌── (69 Red) | ┌── (78 Red) + // | | └── (45 Black) | | └── (69 Black) + // └── (28 Black) -----------> └── (45 Black) + // | ┌── (21 Red) (delete 28) | ┌── (21 Red) + // └── (17 Black) └── (17 Black) + // └── (1 Red) └── (1 Red) + val array = arrayOf( + 21, 69, 28, 17, 42, 1, 78, 88, 45, + ) + for (num in array) { + tree.add(num, "$num") + } + tree.delete(42) + tree.delete(28) + + val root = tree.root as Node.RBNode? + val rootRight = root?.right as Node.RBNode? + val rootLeft = root?.left as Node.RBNode? + val rootRightLeft = rootRight?.left as Node.RBNode? + val rootRightRight = rootRight?.right as Node.RBNode? + val rootLeftRight = rootLeft?.right as Node.RBNode? + val rootLeftLeft = rootLeft?.left as Node.RBNode? + + // check if the tree is balanced + assert( + root != null + && root.color == Node.RBNode.Color.BLACK + && root.key == 45 + && rootLeft?.key == 17 + && rootLeft.color == Node.RBNode.Color.BLACK + && rootLeftLeft?.key == 1 + && rootLeftLeft.color == Node.RBNode.Color.RED + && rootLeftRight?.key == 21 + && rootLeftRight.color == Node.RBNode.Color.RED + && rootRight?.key == 78 + && rootRight.color == Node.RBNode.Color.RED + && rootRightLeft?.key == 69 + && rootRightLeft.color == Node.RBNode.Color.BLACK + && rootRightRight?.key == 88 + && rootRightRight.color == Node.RBNode.Color.BLACK + && tree.getNode(28) == null + && tree.getNode(42) == null + ) + } + } -} \ No newline at end of file +} From fdafc02a11a62f5d412588e24bef893b5aa4e7b5 Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Tue, 2 Apr 2024 05:05:34 +0700 Subject: [PATCH 31/52] Some gradle change to generate jacoco badge --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 11ca1be..bf565ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,9 +42,9 @@ tasks.test { tasks.named("jacocoTestReport") { dependsOn(tasks.test) reports { - csv.required = false + csv.required = true xml.required = false - html.outputLocation = layout.buildDirectory.dir("jacocoHtml") + html.required = false } } From 60405105df098c5b8c77a0c53db09f4336f26de3 Mon Sep 17 00:00:00 2001 From: Alexandr Kudrya <70281496+AlexandrKudrya@users.noreply.github.com> Date: Tue, 2 Apr 2024 05:20:33 +0700 Subject: [PATCH 32/52] Update github-actions to create jacoco badges.yml --- .github/workflows/github-actions.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index f8d7e33..43cf411 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -33,3 +33,21 @@ jobs: with: generate-branches-badge: true jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv + + - name: Log coverage percentage + run: | + echo "coverage = ${{ steps.jacoco.outputs.coverage }}" + echo "branch coverage = ${{ steps.jacoco.outputs.branches }}" + + - name: Commit and push the badge (if it changed) + uses: EndBug/add-and-commit@v7 + with: + default_author: github_actions + message: 'commit badge' + add: '*.svg' + + - name: Upload JaCoCo coverage report + uses: actions/upload-artifact@v2 + with: + name: jacoco-report + path: target/site/jacoco/ From ed1b23514c716da2801f202410c449ed7d07f9e7 Mon Sep 17 00:00:00 2001 From: Alexandr Kudrya <70281496+AlexandrKudrya@users.noreply.github.com> Date: Tue, 2 Apr 2024 05:21:34 +0700 Subject: [PATCH 33/52] fix: path correct usage.yml --- .github/workflows/github-actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 43cf411..3a0e3a8 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -50,4 +50,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: jacoco-report - path: target/site/jacoco/ + path: target/site/jacoco/ From 0dbcc5ff7bd623f838023a642b1dca597d872f70 Mon Sep 17 00:00:00 2001 From: Alexandr Kudrya <70281496+AlexandrKudrya@users.noreply.github.com> Date: Tue, 2 Apr 2024 05:34:10 +0700 Subject: [PATCH 34/52] fix: access fix.yml --- .github/workflows/github-actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 3a0e3a8..61851b4 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -42,7 +42,7 @@ jobs: - name: Commit and push the badge (if it changed) uses: EndBug/add-and-commit@v7 with: - default_author: github_actions + default_author: AlexandrKudrya message: 'commit badge' add: '*.svg' From 2fa9e87c07b4b70ad24045ef39804c02eb9add36 Mon Sep 17 00:00:00 2001 From: Alexandr Kudrya <70281496+AlexandrKudrya@users.noreply.github.com> Date: Tue, 2 Apr 2024 05:46:27 +0700 Subject: [PATCH 35/52] Badge canceling.yml Cancel the idea of creating a badge, due to the inability to change the repository settings. --- .github/workflows/github-actions.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 61851b4..f8d7e33 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -33,21 +33,3 @@ jobs: with: generate-branches-badge: true jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv - - - name: Log coverage percentage - run: | - echo "coverage = ${{ steps.jacoco.outputs.coverage }}" - echo "branch coverage = ${{ steps.jacoco.outputs.branches }}" - - - name: Commit and push the badge (if it changed) - uses: EndBug/add-and-commit@v7 - with: - default_author: AlexandrKudrya - message: 'commit badge' - add: '*.svg' - - - name: Upload JaCoCo coverage report - uses: actions/upload-artifact@v2 - with: - name: jacoco-report - path: target/site/jacoco/ From 3f6887e6695f652d1ba48482ad8b4e412aaeb714 Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Tue, 2 Apr 2024 05:33:11 +0300 Subject: [PATCH 36/52] AVL v.0.1 --- src/main/kotlin/AVLTree.kt | 200 +++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/main/kotlin/AVLTree.kt diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt new file mode 100644 index 0000000..68038df --- /dev/null +++ b/src/main/kotlin/AVLTree.kt @@ -0,0 +1,200 @@ +class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tree>(root) { + + override fun add(key: K, value: V){ + val node : Node.AVLNode = Node.AVLNode(key, value) + this.addNode(node) + } + override fun addNode(node : Node>){ + super.addNode(node) + balanceAVLTree(node as Node.AVLNode) + } + + override fun merge(tree: Tree>) { + if (this.root != null && tree.root != null) { + require(this.max()!!.key < tree.min()?.key!!) { + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" + } + } + if (this.root == null) this.root = tree.root + else { + tree.forEach { + this.add(it.key, it.value) + } + } + } + + override fun max(): Node.AVLNode? { + return if (root == null) null else (root as Node.AVLNode).max() + } + + override fun min(): Node.AVLNode? { + return if (root == null) null else (root as Node.AVLNode).min() + } + + override fun delete(key : K){ + val node = this.getNode(key) as Node.AVLNode? ?: return + deleteNode(node, key) + } + + private fun deleteNode(node : Node.AVLNode?, key : K) : Node.AVLNode? { + if (node == null) return node; + if (key < node.key){ + node.left = deleteNode(node.left as Node.AVLNode, key) + } + else if (key > node.key){ + node.right = deleteNode(node.right as Node.AVLNode, key) + } + else{ + if (node.left == null || node.right == null){ + var tmp : Node.AVLNode? = null + tmp = node.left as Node.AVLNode ?: node.right as Node.AVLNode + if (tmp == null){ + tmp = node + node = null + } else { + node = tmp + } + } + else { + val tmp = findMin(node.right as Node.AVLNode) + node.key = tmp.key + node.right = deleteNode(node.right as Node.AVLNode, tmp.key) + } + } + if (node == null) return node + fixHeight(node) + val balance = balanceFactor(node) + if (balance > 1){ + if (balanceFactor(node.left as Node.AVLNode) >= 0){ + return rightRotate(node) + } else { + node.left = leftRotate(node.left as Node.AVLNode) + return rightRotate(node) + } + } + if (balance < -1){ + if (balanceFactor(node.right as Node.AVLNode) <= 0){ + return leftRotate(node) + } else { + node.right = rightRotate(node.right as Node.AVLNode) + return leftRotate(node) + } + } + return node + } + + private fun findMin(node : Node.AVLNode) : Node.AVLNode{ + return if (node.left != null) findMin(node.left as Node.AVLNode) else node + } + + private fun removeMin(node : Node.AVLNode) : Node.AVLNode{ + if (node.left == null){ + return node.right as Node.AVLNode + } + node.left = removeMin(node.left as Node.AVLNode) + return balanceAVLTree(node) + } + + private fun balanceAVLTree(node : Node.AVLNode) : Node.AVLNode{ + fixHeight(node) + if (balanceFactor(node) == 2){ + if (balanceFactor(node.right as Node.AVLNode?) < 0){ + node.right = rightRotate(node.right as Node.AVLNode); + } + return leftRotate(node); + } + if (balanceFactor(node) == -2){ + if (balanceFactor(node.left as Node.AVLNode?) > 0){ + node.left = leftRotate(node.left as Node.AVLNode) + } + return rightRotate(node) + } + return node; + } + + private fun fixHeight(node : Node.AVLNode?) { + node?.height = 1 + maxOf(height(node?.left as Node.AVLNode?), height(node?.right as Node.AVLNode?)) + } + + private fun height(node : Node.AVLNode?) : Int{ + return node?.height ?: 0 + } + + private fun balanceFactor(node : Node.AVLNode?) : Int{ + if (node == null) return 0; + return (height(node.right as Node.AVLNode?) - height(node.left as Node.AVLNode?)); + } + + private fun leftRotate(a : Node.AVLNode) : Node.AVLNode{ + if (a.height != 2) return a; + val b : Node.AVLNode = a.right as Node.AVLNode; + if (b.height == -1) return a; + + a.right = b.left; + if (b.left != null) b.left!!.parent = a; + + b.parent = a.parent + if(a.parent != null){ + if (a.parent!!.left == a){ + a.parent!!.left = b; + } + else{ + a.parent!!.right = b; + } + } + else{ + root = b + } + + b.left = a; + a.parent = b; + + if (b.height == 1){ + a.height = 0 + b.height = 0 + } + else{ + a.height = 1 + b.height = -1 + } + return b; + } + + private fun rightRotate(b : Node.AVLNode) : Node.AVLNode{ + if (b.height != -2) return b; + val a : Node.AVLNode = b.left as Node.AVLNode; + if (a.height == -1) return b; + + b.left = a.right; + if (a.right != null) a.right!!.parent = b; + + a.parent = b.parent + if(b.parent != null){ + if (b.parent!!.left == b){ + b.parent!!.left = a; + } + else{ + b.parent!!.right = a; + } + } + else{ + root = a + } + + a.right = b; + b.parent = a; + + if (a.height == -1){ + a.height = 0 + b.height = 0 + } + else{ + a.height = 1 + b.height = -1 + } + return a; + } +} + + + From 55fa001662b06d210aca0df1d4c511b25855b46a Mon Sep 17 00:00:00 2001 From: VersusXX Date: Tue, 2 Apr 2024 10:03:56 +0300 Subject: [PATCH 37/52] edit: README Added some more information and examples into README --- README.md | 326 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 325 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a15f997..5859a4e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,336 @@ [![MIT License][license-shield]][license-url] - +

Борцы Даня, Саша, Мухаммет

## О проекте Добро пожаловать в библиотеку Kotlin Binary Tree! Эта библиотека разработана для предоставления разработчикам эффективных и гибких реализаций трех различных типов бинарных деревьев: Binary Tree, AVL Tree и Red-Black Tree +## Как пользоватся + +### Инициализация +```kotlin +val binaryTree = BinaryTree() +val redBlackTree = RBTree() +val avlTree = AVLTree() +``` +### Базовые операции + +#### Добавление +```kotlin +val binaryTree = BinaryTree() + +binaryTree.add(1, "A") +binaryTree.add(2, "B") +binaryTree.add(3, "C") + +println(binaryTree) +``` + +#### Поиск +```kotlin +val binaryTree = BinaryTree() + +/* + добавляем ноды +*/ + +// поиск существующего элемента +println(binaryTree.get(1)) +println(binaryTree[3]) +println(binaryTree.getOrDefault(2, "No such element")) + +// поиск не существующего элемента +println(binaryTree.get(4)) +println(binaryTree[5]) +println(binaryTree.getOrDefault(8, "No such element")) +``` +#### Вывод +```kotlin +A +C +B +null +null +No such element +``` + +#### Удаление +```kotlin +val binaryTree = BinaryTree() + +/* + добавляем ноды +*/ + +// удаление существующего элемента +binaryTree.delete(1) + +// удаление не существующего элемента ничего не делает +binaryTree.delete(4) + +println(binaryTree.toString()) +``` + +#### Вывод +```text +[(2: B), (3: C)] +``` + +#### Присваивание +```kotlin +val binaryTree = BinaryTree() + +/* + добавляем ноды +*/ + +println(binaryTree.toString()) + +// присвоить новое значение существующему элементу +binaryTree.set(2, "D") +binaryTree[3] = "E" + +println(binaryTree.toString()) + +// присвоить значение не существующему элементу +binaryTree.set(4, "Y") +binaryTree[5] = "X" + +println(binaryTree.toString()) +``` + +#### Вывод +```text +// изначальный вид +[(1: A), (2: B), (3: C)] + +// после присваивания +[(1: A), (2: D), (3: E)] + +// после присваивания значения не существующему элементу +[(1: A), (2: D), (3: E), (4: Y), (5: X)] +``` + +#### Минимум / Максимум +```kotlin +val binaryTree = BinaryTree() + +binaryTree.add(1, "A") +binaryTree.add(2, "B") +binaryTree.add(3, "C") + +println(binaryTree.min()) +println(binaryTree.max()) +``` + +#### Вывод +```text +(1: A) +(3: C) +``` + +#### Итерирование по дереву + +
+ Итерирование по ключу + + ```kotlin + val binaryTree = BinaryTree() + +binaryTree.add(2, "B") +binaryTree.add(1, "A") +binaryTree.add(3, "C") + +binaryTree.iterator().forEach { + println(it.key) +} + + println(binaryTree.toString()) + ``` +#### Вывод + ```text +1 +2 +3 + ``` +
+ +
+ BST итерирование + + ```kotlin + val binaryTree = BinaryTree() + +binaryTree.add(2, "B") +binaryTree.add(1, "A") +binaryTree.add(3, "C") + +binaryTree.iterateBFS().forEach { + println(it.key) +} + + println(binaryTree.toString()) + ``` +#### Вывод + ```text +2 +1 +3 + ``` +
+ +
+ DFS итерирование + + ```kotlin + val binaryTree = BinaryTree() + +binaryTree.add(2, "B") +binaryTree.add(1, "A") +binaryTree.add(3, "C") + +binaryTree.iterateDFS().forEach { + println(it.key) +} + + println(binaryTree.toString()) + ``` +#### Вывод + ```text +2 +1 +3 + ``` +
+ +#### Соединение двух деревьев +```kotlin +val binaryTree = BinaryTree() +val secondBinaryTree = BinaryTree() + +binaryTree.add(2, "B") +binaryTree.add(1, "A") +binaryTree.add(3, "C") + +secondBinaryTree.add(4, "D") +secondBinaryTree.add(5, "E") +secondBinaryTree.add(6, "F") + + +binaryTree.merge(secondBinaryTree) + +println(binaryTree.toString()) +``` + +#### Вывод + ```text +[(1: A), (2: B), (3: C), (4: D), (5: E), (6: F)] + ``` + +#### Клонирование дерева +```kotlin +val binaryTree = BinaryTree() + +binaryTree.add(2, "B") +binaryTree.add(1, "A") +binaryTree.add(3, "C") + +val cloneTree = binaryTree.clone() + +println(cloneTree.toString()) + +``` + +#### Вывод + ```text +[(1: A), (2: B), (3: C)] + ``` + +### Виды выводов дерева +
+ Итерационный вывод + + ```kotlin + val binaryTree = BinaryTree() + + binaryTree.add(1, "A") + binaryTree.add(2, "B") + binaryTree.add(3, "C") + + println(binaryTree.toString()) + ``` +#### Вывод + ```text + [(1: A), (2: B), (3: C)] + ``` +
+ +
+ Вертикальный вывод рисунком + + ```kotlin + val binaryTree = BinaryTree() + + binaryTree.add(1, "A") + binaryTree.add(2, "B") + binaryTree.add(3, "C") + + println(binaryTree.toString(mode = Tree.TreeStringMode.WIDTH)) + ``` +#### Вывод + ```text + | ┌── (3: C) + | ┌── (2: B) + └── (1: A) + ``` +
+ +
+ Горизонтальный вывод рисунком + + ```kotlin + val binaryTree = BinaryTree() + + binaryTree.add(1, "A") + binaryTree.add(2, "B") + binaryTree.add(3, "C") + + println(binaryTree.toString(mode = Tree.TreeStringMode.WIDTH)) + ``` +#### Вывод + ```text +────────┐ + (1: A) + └─────┐ + (2: B) + └────┐ + (3: C) + ``` +
+
+ Цветной вывод Red-Black Tree +- Добавляйте параметр к дереву + +```kotlin +redBlackTree.setColored(true) +``` + +```kotlin + val redBlackTree = RBTree() + redBlackTree.setColored(true) + + redBlackTree.add(1, "A") + redBlackTree.add(2, "B") + redBlackTree.add(3, "C") + + println(redBlackTree.toString()) + println(redBlackTree.toString(mode = Tree.TreeStringMode.WIDTH)) + println(redBlackTree.toString(mode = Tree.TreeStringMode.HEIGHT)) +``` +
+ ## Создано с использованием - Kotlin 1.9.22: Библиотека написана на Kotlin, используя его лаконичный синтаксис и мощные возможности. From b4b6495b585b3f2a96a7ee8a0f72dc6608dddf2a Mon Sep 17 00:00:00 2001 From: VersusXX Date: Tue, 2 Apr 2024 12:05:52 +0300 Subject: [PATCH 38/52] edit: README information sequesnce Changed the sequence of the Usage examples. Moved them to the bottom. --- README.md | 69 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 5859a4e..a726434 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,41 @@ Добро пожаловать в библиотеку Kotlin Binary Tree! Эта библиотека разработана для предоставления разработчикам эффективных и гибких реализаций трех различных типов бинарных деревьев: Binary Tree, AVL Tree и Red-Black Tree +## Создано с использованием + +- Kotlin 1.9.22: Библиотека написана на Kotlin, используя его лаконичный синтаксис и мощные возможности. +- JDK 21: Построено с использованием Java Development Kit версии 21, обеспечивая совместимость с современными + средами Java и используя последние усовершенствования Java. + +## Главные особенности + +- #### Три Варианта Бинарных Деревьев: Выберите из различных типов бинарных деревьев, чтобы удовлетворить свои конкретные требования: + +- Binary Tree: Фундаментальная структура бинарного дерева, предоставляющая простые возможности вставки, удаления и поиска. +- AVL Tree: Самобалансирующееся бинарное дерево поиска, обеспечивающее оптимальную производительность для операций вставки, удаления и поиска. +- Red-Black Tree: Еще одно самобалансирующееся бинарное дерево поиска с логарифмической высотой, обеспечивающее эффективные операции для больших наборов данных. + +- #### Универсальные Операции: Каждая реализация дерева поддерживает основные операции для управления структурами деревьев: + - Поиск: Быстро находите узлы в дереве на основе ключевых значений. + - Вставка: Добавляйте новые узлы, сохраняя целостность и баланс дерева. + - Удаление: Удаляйте узлы из дерева, не нарушая его структурных свойств. + - Вывод на консоль: Визуализируйте структуру дерева через печать в консоли, помогая в отладке и визуализации задач. + +[//]: # (## Usage) + + + +## Лицензия + +Распространяется по лицензии MIT. См. файл `LICENSE.txt` для получения дополнительной информации. + +## Авторы + +- [AlexandrKudrya](https://github.com/AlexandrKudrya) +- [7gambit7](https://github.com/7gambit7) +- [VersusXX](https://github.com/VersusXX) + + ## Как пользоватся ### Инициализация @@ -331,39 +366,5 @@ redBlackTree.setColored(true) ``` -## Создано с использованием - -- Kotlin 1.9.22: Библиотека написана на Kotlin, используя его лаконичный синтаксис и мощные возможности. -- JDK 21: Построено с использованием Java Development Kit версии 21, обеспечивая совместимость с современными - средами Java и используя последние усовершенствования Java. - -## Главные особенности - -- #### Три Варианта Бинарных Деревьев: Выберите из различных типов бинарных деревьев, чтобы удовлетворить свои конкретные требования: - -- Binary Tree: Фундаментальная структура бинарного дерева, предоставляющая простые возможности вставки, удаления и поиска. -- AVL Tree: Самобалансирующееся бинарное дерево поиска, обеспечивающее оптимальную производительность для операций вставки, удаления и поиска. -- Red-Black Tree: Еще одно самобалансирующееся бинарное дерево поиска с логарифмической высотой, обеспечивающее эффективные операции для больших наборов данных. - -- #### Универсальные Операции: Каждая реализация дерева поддерживает основные операции для управления структурами деревьев: - - Поиск: Быстро находите узлы в дереве на основе ключевых значений. - - Вставка: Добавляйте новые узлы, сохраняя целостность и баланс дерева. - - Удаление: Удаляйте узлы из дерева, не нарушая его структурных свойств. - - Вывод на консоль: Визуализируйте структуру дерева через печать в консоли, помогая в отладке и визуализации задач. - -[//]: # (## Usage) - - - -## Лицензия - -Распространяется по лицензии MIT. См. файл `LICENSE.txt` для получения дополнительной информации. - -## Авторы - -- [AlexandrKudrya](https://github.com/AlexandrKudrya) -- [7gambit7](https://github.com/7gambit7) -- [VersusXX](https://github.com/VersusXX) - [license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge: [license-url]: https://github.com/spbu-coding-2023/trees-11/blob/main/LICENSE.txt \ No newline at end of file From d8776a31a55ed3240f122b219439923e42bf00bc Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Wed, 3 Apr 2024 03:51:35 +0700 Subject: [PATCH 39/52] Little naming and README fixes --- README.md | 56 +++++++++++++++++------------- build.gradle.kts | 5 +++ src/main/kotlin/Main.kt | 13 +++++-- src/main/kotlin/Tree.kt | 30 ++++++++-------- src/test/kotlin/DFSIteratorTest.kt | 4 +-- 5 files changed, 64 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index a726434..90326d4 100644 --- a/README.md +++ b/README.md @@ -29,19 +29,6 @@ [//]: # (## Usage) - - -## Лицензия - -Распространяется по лицензии MIT. См. файл `LICENSE.txt` для получения дополнительной информации. - -## Авторы - -- [AlexandrKudrya](https://github.com/AlexandrKudrya) -- [7gambit7](https://github.com/7gambit7) -- [VersusXX](https://github.com/VersusXX) - - ## Как пользоватся ### Инициализация @@ -61,6 +48,8 @@ binaryTree.add(2, "B") binaryTree.add(3, "C") println(binaryTree) + +// output: [(1: "A"), (2: "B"), (3, "C")] ``` #### Поиск @@ -221,26 +210,33 @@ binaryTree.iterateBFS().forEach { ```kotlin val binaryTree = BinaryTree() +val keys = arrayOf(3, 2, 1, 0, 4, 5) -binaryTree.add(2, "B") -binaryTree.add(1, "A") -binaryTree.add(3, "C") - -binaryTree.iterateDFS().forEach { - println(it.key) -} +keys.forEach { binaryTree.add(it, it.toString()) } - println(binaryTree.toString()) +// Стандартно iterateDFS работает с mode=Tree.ModeDFS.PREORDER +println("PREORDER: ") +binaryTree.iterateDFS().forEach { print(it.key.toString() + " ") } +println("INORDER: ") +binaryTree.iterateDFS(mode=Tree.ModeDFS.INORDER).forEach { print(it.key.toString() + " ") } +println("POSTORDER: ") +binaryTree.iterateDFS(mode=Tree.ModeDFS.POSTORDER).forEach { print(it.key.toString() + " ") } ``` #### Вывод ```text -2 -1 -3 +PREORDER: 3 2 1 0 4 5 +INORDER: 0 1 2 3 4 5 +POSTORDER: 0 1 2 5 4 3 ``` +О методах обхода в глубину — [Tree traversal](https://en.wikipedia.org/wiki/Tree_traversal) + #### Соединение двух деревьев + +Операция соединения двух деверьев доступна, только при условии, что их ключи сравнимы и все ключи основного дерева +меньше всех ключей присоединяемого дерева. + ```kotlin val binaryTree = BinaryTree() val secondBinaryTree = BinaryTree() @@ -366,5 +362,17 @@ redBlackTree.setColored(true) ``` + + +## Лицензия + +Распространяется по лицензии MIT. См. файл `LICENSE.txt` для получения дополнительной информации. + +## Авторы + +- [AlexandrKudrya](https://github.com/AlexandrKudrya) +- [7gambit7](https://github.com/7gambit7) +- [VersusXX](https://github.com/VersusXX) + [license-shield]: https://img.shields.io/github/license/othneildrew/Best-README-Template.svg?style=for-the-badge: [license-url]: https://github.com/spbu-coding-2023/trees-11/blob/main/LICENSE.txt \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bf565ac..7e4ee2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ plugins { kotlin("jvm") version "1.9.22" `java-library` jacoco + application } @@ -36,6 +37,10 @@ tasks.test { ) } + reports { + junitXml.required = true + } + finalizedBy(tasks.jacocoTestReport) } diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 13fc41b..3c7ed99 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,7 +1,14 @@ fun main() { - var tree = BinaryTree() + val binaryTree = BinaryTree() + val keys = arrayOf(3, 2, 1, 0, 4, 5) - tree.add(1, "1") - tree.add(1, "1") + keys.forEach { binaryTree.add(it, it.toString()) } + + // Стандартно iterateDFS работает с mode=Tree.ModeDFS.PREORDER + binaryTree.iterateDFS().forEach { print(it.key.toString() + " ") } + println() + binaryTree.iterateDFS(mode=Tree.ModeDFS.INORDER).forEach { print(it.key.toString() + " ") } + println() + binaryTree.iterateDFS(mode=Tree.ModeDFS.POSTORDER).forEach { print(it.key.toString() + " ") } } \ No newline at end of file diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 6654dae..626ed5b 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -262,42 +262,42 @@ open class Tree , V: Any, T: Node> ( } - enum class ModeDFS {SIMPLE, REVERSE, SYMMETRIC} + enum class ModeDFS { PREORDER, POSTORDER, INORDER} - inner class DFSIterator(tree: Tree, mode: ModeDFS = ModeDFS.SIMPLE): Iterator> { + inner class DFSIterator(tree: Tree, mode: ModeDFS = ModeDFS.PREORDER): Iterator> { private var stack: Queue> = LinkedList() init { when (mode) { - ModeDFS.SIMPLE -> addToStackSimple(tree.root) - ModeDFS.SYMMETRIC -> addToStackSymmetric(tree.root) - ModeDFS.REVERSE -> addToStackSimpleReverse(tree.root) + ModeDFS.PREORDER -> addToStackPreOrder(tree.root) + ModeDFS.INORDER -> addToStackInOrder(tree.root) + ModeDFS.POSTORDER -> addToStackPostOrder(tree.root) } } - private fun addToStackSimple(current: Node?) { + private fun addToStackPreOrder(current: Node?) { if (current != null) { stack.add(current) - this.addToStackSimple(current.left) - this.addToStackSimple(current.right) + this.addToStackPreOrder(current.left) + this.addToStackPreOrder(current.right) } } - private fun addToStackSimpleReverse(current: Node?) { + private fun addToStackPostOrder(current: Node?) { if (current != null) { - this.addToStackSimpleReverse(current.left) - this.addToStackSimpleReverse(current.right) + this.addToStackPostOrder(current.left) + this.addToStackPostOrder(current.right) stack.add(current) } } - private fun addToStackSymmetric(current: Node?) { + private fun addToStackInOrder(current: Node?) { if (current != null) { - this.addToStackSymmetric(current.left) + this.addToStackInOrder(current.left) stack.add(current) - this.addToStackSymmetric(current.right) + this.addToStackInOrder(current.right) } } @@ -319,7 +319,7 @@ open class Tree , V: Any, T: Node> ( return BFSIterator(this) } - fun iterateDFS(mode: ModeDFS = ModeDFS.SIMPLE): DFSIterator { + fun iterateDFS(mode: ModeDFS = ModeDFS.PREORDER): DFSIterator { return DFSIterator(this, mode=mode) } diff --git a/src/test/kotlin/DFSIteratorTest.kt b/src/test/kotlin/DFSIteratorTest.kt index db061cd..0a35981 100644 --- a/src/test/kotlin/DFSIteratorTest.kt +++ b/src/test/kotlin/DFSIteratorTest.kt @@ -35,7 +35,7 @@ class DFSIteratorTest { val result = mutableListOf() val expectedResult = arrayOf(0, 1, 2, 3, 4) keys.forEach { tree.add(it, it.toString()) } - for (i in tree.iterateDFS(mode = Tree.ModeDFS.SYMMETRIC)) result.add(i.key) + for (i in tree.iterateDFS(mode = Tree.ModeDFS.INORDER)) result.add(i.key) for (i in keys.indices) assert(result[i] == expectedResult[i]) } @@ -46,7 +46,7 @@ class DFSIteratorTest { val result = mutableListOf() val expectedResult = arrayOf(0, 1, 4, 3, 2) keys.forEach { tree.add(it, it.toString()) } - for (i in tree.iterateDFS(mode = Tree.ModeDFS.REVERSE)) result.add(i.key) + for (i in tree.iterateDFS(mode = Tree.ModeDFS.POSTORDER)) result.add(i.key) for (i in keys.indices) assert(result[i] == expectedResult[i]) } From b194d6c02b372094374f26d58b66ef7595931807 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Wed, 3 Apr 2024 01:43:00 +0300 Subject: [PATCH 40/52] fix: toStringBeautifulHeight in colored RBTree Fixed a bug that printed colored RBTree vertically with some spacing issues. --- src/main/kotlin/RBTree.kt | 117 ++++++++++++++++++++++++++++++++++++++ src/main/kotlin/Tree.kt | 2 +- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 3ee8ff2..11bd223 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -1,3 +1,6 @@ +import kotlin.math.ceil +import kotlin.math.floor + class RBTree, V : Any>(root: Node.RBNode? = null) : Tree>(root) { override fun add(key: K, value: V) { @@ -249,6 +252,120 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree } } + override fun toStringBeautifulHeight(ofSide: Int): String { + if (this.root == null) return "" + else { + val buffer: StringBuilder = StringBuilder() + + val lines: MutableList> = mutableListOf() + + var level: MutableList?> = mutableListOf() + var next: MutableList?> = mutableListOf() + + level.add(this.root as Node.RBNode?) + + var nodeNumber = 1 + var widtest = 0 + + while (nodeNumber != 0) { + val line: MutableList = mutableListOf() + + nodeNumber = 0 + + for (node in level) { + if (node == null) { + line.add(null) + + next.add(null) + next.add(null) + } else { + val strNode: String = node.toString() + line.addLast(strNode) + + val extra = if (node.color == Node.RBNode.Color.RED) 18 else 0 + + if (strNode.length > widtest + extra) widtest = strNode.length + + next.add(node.left as Node.RBNode?) + next.add(node.right as Node.RBNode?) + + if (node.left != null) nodeNumber++ + if (node.right != null) nodeNumber++ + } + } + + widtest += widtest % 2 + + lines.add(line) + val swap = level + level = next + next = swap + next.clear() + } + + var perpiece: Int = lines[lines.size - 1].size * (widtest + ofSide) + + for (i in 1..perpiece / 2) buffer.append("─") + buffer.append("┐\n") + + for (i in 0.. = lines[i] + + val hpw: Int = floor(perpiece / 2f - 1).toInt() + + if (i > 0) { + for (j in 0...leftRotate() { val newRoot = this.right this.right = newRoot?.left diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 6654dae..440bfd6 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -407,7 +407,7 @@ open class Tree , V: Any, T: Node> ( return buffer } - fun toStringBeautifulHeight(ofSide: Int = 4): String { + open fun toStringBeautifulHeight(ofSide: Int = 4): String { if (this.root == null) return "" else { val buffer: StringBuilder = StringBuilder() From 4e8cf7a8d925c6445b7a3d30d370efa84f3cd6d4 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Wed, 3 Apr 2024 02:15:23 +0300 Subject: [PATCH 41/52] add: colored output example to README Added an example image of colored output of Red-Black tree into README. --- README.md | 7 +++++++ trees_output.jpg | Bin 0 -> 36582 bytes 2 files changed, 7 insertions(+) create mode 100644 trees_output.jpg diff --git a/README.md b/README.md index 90326d4..e902127 100644 --- a/README.md +++ b/README.md @@ -355,11 +355,18 @@ redBlackTree.setColored(true) redBlackTree.add(1, "A") redBlackTree.add(2, "B") redBlackTree.add(3, "C") + redBlackTree.add(4, "D") + redBlackTree.add(5, "E") println(redBlackTree.toString()) println(redBlackTree.toString(mode = Tree.TreeStringMode.WIDTH)) println(redBlackTree.toString(mode = Tree.TreeStringMode.HEIGHT)) ``` + +#### Вывод: + +Alt text + diff --git a/trees_output.jpg b/trees_output.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d18993596610fef0ca50abd7a26d6ddcb9842cf5 GIT binary patch literal 36582 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 HuOt5j>xW7n literal 0 HcmV?d00001 From da9802a24819c249d25ec7d75dd7b7b59c86b4b1 Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Wed, 3 Apr 2024 03:09:54 +0300 Subject: [PATCH 42/52] AVL v.1 --- src/main/kotlin/AVLTree.kt | 294 ++++++++++++++++--------------------- src/main/kotlin/Nodes.kt | 28 +++- 2 files changed, 156 insertions(+), 166 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 68038df..9541112 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,200 +1,166 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tree>(root) { - override fun add(key: K, value: V){ - val node : Node.AVLNode = Node.AVLNode(key, value) - this.addNode(node) - } - override fun addNode(node : Node>){ - super.addNode(node) - balanceAVLTree(node as Node.AVLNode) + override fun add(key: K, value: V) { + this.root = insert(this.root as Node.AVLNode?, key, value) } - override fun merge(tree: Tree>) { - if (this.root != null && tree.root != null) { - require(this.max()!!.key < tree.min()?.key!!) { - "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" - } - } - if (this.root == null) this.root = tree.root - else { - tree.forEach { - this.add(it.key, it.value) - } - } - } + private fun height(node: Node.AVLNode?): Int { + if (node == null) return 0 - override fun max(): Node.AVLNode? { - return if (root == null) null else (root as Node.AVLNode).max() + return node.height } - override fun min(): Node.AVLNode? { - return if (root == null) null else (root as Node.AVLNode).min() + private fun max(a: Int, b: Int): Int { + return if ((a > b)) a else b } - override fun delete(key : K){ - val node = this.getNode(key) as Node.AVLNode? ?: return - deleteNode(node, key) + private fun getBalance(node: Node.AVLNode?): Int { + if (node == null) return 0 + + return height(node.left as Node.AVLNode?) - height(node.right as Node.AVLNode?) } - private fun deleteNode(node : Node.AVLNode?, key : K) : Node.AVLNode? { - if (node == null) return node; - if (key < node.key){ - node.left = deleteNode(node.left as Node.AVLNode, key) - } - else if (key > node.key){ - node.right = deleteNode(node.right as Node.AVLNode, key) - } - else{ - if (node.left == null || node.right == null){ - var tmp : Node.AVLNode? = null - tmp = node.left as Node.AVLNode ?: node.right as Node.AVLNode - if (tmp == null){ - tmp = node - node = null - } else { - node = tmp - } - } - else { - val tmp = findMin(node.right as Node.AVLNode) - node.key = tmp.key - node.right = deleteNode(node.right as Node.AVLNode, tmp.key) - } - } - if (node == null) return node - fixHeight(node) - val balance = balanceFactor(node) - if (balance > 1){ - if (balanceFactor(node.left as Node.AVLNode) >= 0){ - return rightRotate(node) - } else { - node.left = leftRotate(node.left as Node.AVLNode) - return rightRotate(node) - } + private fun insert(node: Node.AVLNode?, key: K, value: V): Node.AVLNode? { + + if (node == null) return Node.AVLNode(key, value) + + if (key < node.key) node.left = insert(node.left as Node.AVLNode?, key, value) + else if (key > node.key) node.right = insert(node.right as Node.AVLNode?, key, value) + else return node + + node.height = 1 + max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?)) + + val balance = getBalance(node) + + if (balance > 1 && key < node.left!!.key) return rightRotate(node) + + if (balance < -1 && key > node.right!!.key) return leftRotate(node) + + if (balance > 1 && key > node.left!!.key) { + node.left = leftRotate(node.left as Node.AVLNode?) + return rightRotate(node) } - if (balance < -1){ - if (balanceFactor(node.right as Node.AVLNode) <= 0){ - return leftRotate(node) - } else { - node.right = rightRotate(node.right as Node.AVLNode) - return leftRotate(node) - } + + if (balance < -1 && key < node.right!!.key) { + node.right = rightRotate(node.right as Node.AVLNode?) + return leftRotate(node) } + return node } - private fun findMin(node : Node.AVLNode) : Node.AVLNode{ - return if (node.left != null) findMin(node.left as Node.AVLNode) else node - } + private fun deleteNode(node: Node.AVLNode?, key: K): Node.AVLNode? { + var root = node + if (root == null) return null - private fun removeMin(node : Node.AVLNode) : Node.AVLNode{ - if (node.left == null){ - return node.right as Node.AVLNode - } - node.left = removeMin(node.left as Node.AVLNode) - return balanceAVLTree(node) - } + if (key < root.key) root.left = deleteNode(root.left as Node.AVLNode?, key) + else if (key > root.key) root.right = deleteNode(root.right as Node.AVLNode?, key) + else { + if ((root.left == null) || (root.right == null)) { + var temp: Node.AVLNode? = null + temp = if (temp === root.left) root.right as Node.AVLNode? + else root.left as Node.AVLNode? - private fun balanceAVLTree(node : Node.AVLNode) : Node.AVLNode{ - fixHeight(node) - if (balanceFactor(node) == 2){ - if (balanceFactor(node.right as Node.AVLNode?) < 0){ - node.right = rightRotate(node.right as Node.AVLNode); + if (temp == null) { + temp = root + root = null + } else root = temp + + } else { + val temp = minValueNode(root.right as Node.AVLNode?) + + root.key = temp!!.key + + root.right = deleteNode(root.right as Node.AVLNode?, temp.key) } - return leftRotate(node); } - if (balanceFactor(node) == -2){ - if (balanceFactor(node.left as Node.AVLNode?) > 0){ - node.left = leftRotate(node.left as Node.AVLNode) - } - return rightRotate(node) + + if (root == null) return root + + (root as Node.AVLNode?)?.height = + max(height(root.left as Node.AVLNode?), height(root.right as Node.AVLNode?)) + 1 + + val balance = getBalance(root) + + if (balance > 1 && getBalance(root.left as Node.AVLNode?) >= 0) return rightRotate(root) + + if (balance > 1 && getBalance(root.left as Node.AVLNode?) < 0) { + root.left = leftRotate(root.left as Node.AVLNode?) + return rightRotate(root) } - return node; - } - private fun fixHeight(node : Node.AVLNode?) { - node?.height = 1 + maxOf(height(node?.left as Node.AVLNode?), height(node?.right as Node.AVLNode?)) + if (balance < -1 && getBalance(root.right as Node.AVLNode?) <= 0) return leftRotate(root) + + if (balance < -1 && getBalance(root.right as Node.AVLNode?) > 0) { + root.right = rightRotate(root.right as Node.AVLNode?) + return leftRotate(root) + } + + return root } - private fun height(node : Node.AVLNode?) : Int{ - return node?.height ?: 0 + private fun minValueNode(node: Node.AVLNode?): Node.AVLNode? { + var current = node + + /* loop down to find the leftmost leaf */ + while (current!!.left != null) current = current.left as Node.AVLNode? + + return current } - private fun balanceFactor(node : Node.AVLNode?) : Int{ - if (node == null) return 0; - return (height(node.right as Node.AVLNode?) - height(node.left as Node.AVLNode?)); + private fun rightRotate(node: Node.AVLNode?): Node.AVLNode { + val x = node!!.left as Node.AVLNode? + val T2 = x!!.right as Node.AVLNode? + + // Perform rotation + x.right = node + node.left = T2 + + // Update heights + node.height = max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?)) + 1 + x.height = max(height(x.left as Node.AVLNode?), height(x.right as Node.AVLNode?)) + 1 + + // Return new root + return x } - private fun leftRotate(a : Node.AVLNode) : Node.AVLNode{ - if (a.height != 2) return a; - val b : Node.AVLNode = a.right as Node.AVLNode; - if (b.height == -1) return a; - - a.right = b.left; - if (b.left != null) b.left!!.parent = a; - - b.parent = a.parent - if(a.parent != null){ - if (a.parent!!.left == a){ - a.parent!!.left = b; - } - else{ - a.parent!!.right = b; - } - } - else{ - root = b - } - - b.left = a; - a.parent = b; - - if (b.height == 1){ - a.height = 0 - b.height = 0 - } - else{ - a.height = 1 - b.height = -1 - } - return b; - } - - private fun rightRotate(b : Node.AVLNode) : Node.AVLNode{ - if (b.height != -2) return b; - val a : Node.AVLNode = b.left as Node.AVLNode; - if (a.height == -1) return b; - - b.left = a.right; - if (a.right != null) a.right!!.parent = b; - - a.parent = b.parent - if(b.parent != null){ - if (b.parent!!.left == b){ - b.parent!!.left = a; - } - else{ - b.parent!!.right = a; - } - } - else{ - root = a - } + private fun leftRotate(node: Node.AVLNode?): Node.AVLNode? { + val y = node!!.right as Node.AVLNode? + val T2 = y!!.left as Node.AVLNode? - a.right = b; - b.parent = a; + y.left = node + node.right = T2 - if (a.height == -1){ - a.height = 0 - b.height = 0 + node.height = max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?)) + 1 + y.height = max(height(y.left as Node.AVLNode?), height(y.right as Node.AVLNode?)) + 1 + + return y + } + + override fun merge(tree: Tree>) { + if (this.root != null && tree.root != null) { + require(this.max()!!.key < tree.min()?.key!!) { + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" + } } - else{ - a.height = 1 - b.height = -1 + if (this.root == null) this.root = tree.root + else { + tree.forEach { + this.add(it.key, it.value) + } } - return a; } -} + override fun max(): Node.AVLNode? { + return if (root == null) null else (root as Node.AVLNode).max() + } + override fun min(): Node.AVLNode? { + return if (root == null) null else (root as Node.AVLNode).min() + } + override fun delete(key: K) { + val node = this.getNode(key) as Node.AVLNode? ?: return + deleteNode(node, key) + } +} \ No newline at end of file diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index c77996b..ce98081 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -77,8 +77,32 @@ open class Node, V, T : Node> internal constructor( left: AVLNode? = null, right: AVLNode? = null, parent: AVLNode? = null, - var height: Int = 0 - ) : Node>(key, value, left, right, parent) + var height: Int = 1 + ) : Node>(key, value, left, right, parent) { + + /*override fun toString(): String { + val rightHeight = if (right != null) (right as AVLNode).height else 0 + val leftHeight = if (left != null) (left as AVLNode).height else 0 + val balance = leftHeight - rightHeight + return "($key: $balance)" + }*/ + fun max(): AVLNode { + var current = this + while (current.right != null) { + current = current.right as AVLNode + } + return current + } + + fun min(): AVLNode { + var current = this + while (current.left != null) { + current = current.left as AVLNode + } + return current + } + + } override fun toString(): String { return "($key: $value)" From 97bb13669e741e917b2ccde7679faa797d99bd72 Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Wed, 3 Apr 2024 08:27:36 +0300 Subject: [PATCH 43/52] add tests for AVL-Tree Nodes --- src/test/kotlin/AVLNodeTest.kt | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/test/kotlin/AVLNodeTest.kt diff --git a/src/test/kotlin/AVLNodeTest.kt b/src/test/kotlin/AVLNodeTest.kt new file mode 100644 index 0000000..9017f95 --- /dev/null +++ b/src/test/kotlin/AVLNodeTest.kt @@ -0,0 +1,54 @@ +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class AVLNodeTest { + private lateinit var tree: AVLTree + + @BeforeEach + fun setup() { + tree = AVLTree() + } + + // max and min tests + @Test + fun `max should return if empty`() { + assert(tree.max()?.key == null) + } + + @Test + fun `min should return if empty`() { + assert(tree.min()?.key == null) + } + + @Test + fun `max should return max key`() { + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + tree.add(4, "D") + tree.add(5, "E") + tree.add(6, "F") + tree.add(7, "G") + tree.add(8, "H") + tree.add(9, "I") + tree.add(10, "J") + + assert(tree.max()?.key == 10) + } + + @Test + fun `min should return min key`() { + tree.add(1, "A") + tree.add(2, "B") + tree.add(3, "C") + tree.add(4, "D") + tree.add(5, "E") + tree.add(6, "F") + tree.add(7, "G") + tree.add(8, "H") + tree.add(9, "I") + tree.add(10, "J") + + assert(tree.min()?.key == 1) + } +} \ No newline at end of file From f08ceaa2db61844cebdacd2f5eb68beba42a787c Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Thu, 4 Apr 2024 08:57:36 +0300 Subject: [PATCH 44/52] fix delete method in avl tree --- src/main/kotlin/AVLTree.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 9541112..2677d73 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -160,7 +160,6 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr } override fun delete(key: K) { - val node = this.getNode(key) as Node.AVLNode? ?: return - deleteNode(node, key) + root = deleteNode(root as Node.AVLNode?, key) } } \ No newline at end of file From 5cb68e7e5cf9f0570d64a7ecc38bf367cbfd6888 Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Thu, 4 Apr 2024 11:52:27 +0300 Subject: [PATCH 45/52] fix one test explanation in binary tree tests --- src/test/kotlin/BinaryTreeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt index d098cbb..bade65e 100644 --- a/src/test/kotlin/BinaryTreeTest.kt +++ b/src/test/kotlin/BinaryTreeTest.kt @@ -643,7 +643,7 @@ class BinaryTreeTest { // 0 0 // | ┌── -1 | ┌── -1 // | ┌── -2 ──> └── -2 - // └── -3 (-2 delete) └── -4 + // └── -3 (-3 delete) └── -4 // └── -4 val keys = arrayOf(0, -3, -2, -4, -1) From 2bb7fb379827eed360b839195a31c145b385ba8d Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Thu, 4 Apr 2024 13:43:27 +0300 Subject: [PATCH 46/52] avl tree 90% test coverage --- src/test/kotlin/AVLTreeTest.kt | 310 +++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 src/test/kotlin/AVLTreeTest.kt diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt new file mode 100644 index 0000000..1623405 --- /dev/null +++ b/src/test/kotlin/AVLTreeTest.kt @@ -0,0 +1,310 @@ +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class AVLTreeTest { + private lateinit var tree: AVLTree + + @BeforeEach + fun setup() { + tree = AVLTree() + } + + @Nested + inner class AddTests { + @Test + fun `Inserting a node into an empty tree`(){ + val key = 1 + val value = "0" + + tree.add(key, value) + assertEquals(tree[key], value) + } + + @Test + fun `Inserting a duplicate node`(){ + tree.add(1, "0") + tree.add(1, "0") + + val root = tree.root + assert(root == tree.getNode(1)) + assert(root?.right == null) + assert(root?.left == null) + } + + @Test + fun `Inserting with left rotate`(){ + val key1 = 0 + val key2 = 1 + val key3 = 2 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + + val root = tree.root + assert(root?.key == key2) + assert(root?.left!!.key == key1) + assert(root.right!!.key == key3) + } + + @Test + fun `Inserting with right rotate`(){ + val key1 = 2 + val key2 = 1 + val key3 = 0 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + + val root = tree.root + assert(root?.key == key2) + assert(root?.left!!.key == key3) + assert(root.right!!.key == key1) + } + + @Test + fun `Inserting with big left rotate (last node)`(){ + val key1 = 2 + val key2 = 1 + val key3 = 5 + val key4 = 3 + val key5 = 6 + val key6 = 4 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + tree.add(key4, key4.toString()) + tree.add(key5, key5.toString()) + tree.add(key6, key6.toString()) + + val root = tree.root + assert(root == tree.getNode(3)) + } + } + + @Nested + inner class MinMaxTests(){ + @Test + fun `Max should return max node`() { + for (i in 1..10) { + tree.add(i, "") + } + assert(tree.max()?.key == 10) + } + + @Test + fun `Max from null tree returns null`(){ + assert(tree.max() == null) + } + + @Test + fun `Min should return min node`() { + for (i in 1..10) { + tree.add(i, "") + } + assert(tree.min()?.key == 1) + } + + @Test + fun `Min from null tree returns null`(){ + assert(tree.min() == null) + } + } + + @Nested + inner class DeleteTests{ + @Test + fun `Deletion of the non-existent node should do nothing`(){ + tree.add(1, "0") + tree.add(2, "890") + + tree.delete(4) + + assert(tree.getNode(1) != null) + assert(tree.getNode(2) != null) + } + + @Test + fun `Deletion of tree's root`(){ + val keyLeft = 0 + val keyRoot = 1 + val keyRight = 2 + + tree.add(keyLeft, keyLeft.toString()) + tree.add(keyRoot, keyRoot.toString()) + tree.add(keyRight, keyRight.toString()) + + tree.delete(1) + + val root = tree.root + assert(tree.getNode(1) == null) + assert(tree.getNode(keyRight) == root) + assert(root?.right == null) + } + + @Test + fun `Deletion of leaf(node with no children)`(){ + val keyRoot = 0 + val key = 1 + val childKey = 2 + tree.add(keyRoot, keyRoot.toString()) + tree.add(key, key.toString()) + tree.add(childKey, childKey.toString()) + + tree.delete(2) + + val root = tree.root + assert(tree.getNode(childKey) == null) + assert(root?.right == null) + assert(root?.left == tree.getNode(0)) + } + + @Test + fun `Deletion of node with one child`(){ + val key1 = 3 + val key2 = 2 + val key3 = 1 + val key4 = 0 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + tree.add(key4, key4.toString()) + + tree.delete(1) + + val root = tree.root + assert(tree.getNode(1) == null) + assert(tree.getNode(0) == root?.left) + } + + @Test + fun `Deletion of node with two children`(){ + val key1 = 2 + val key2 = 1 + val key3 = 5 + val key4 = 3 + val key5 = 6 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + tree.add(key4, key4.toString()) + tree.add(key5, key5.toString()) + + tree.delete(5) + + val root = tree.root + assert(tree.getNode(5) == null) + assert(root?.right == tree.getNode(key5)) + + val newNode = tree.getNode(key5) + val childNewNode = newNode?.left + assert(newNode?.key!! > childNewNode?.key!!) + } + + @Test + fun `Deletion of node with two children, which also have child`(){ + val key1 = 2 + val key2 = 1 + val key3 = 5 + val key4 = 3 + val key5 = 6 + val key6 = 0 + val key7 = 4 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + tree.add(key4, key4.toString()) + tree.add(key5, key5.toString()) + tree.add(key6, key6.toString()) + tree.add(key7, key7.toString()) + + tree.delete(5) + + val newNode = tree.getNode(key7) + val root = tree.root + + assert(tree.getNode(5) == null) + assert(newNode == root?.right) + assert(newNode?.right != null && newNode.left != null) + } + } + + @Nested + inner class MergeTests{ + @Test + fun `Merge should work correctly on 2 empty trees`() { + val secondTree = AVLTree() + + tree.merge(secondTree) + + assert(tree.root == null) + } + + @Test + fun `should merge second tree with empty tree`() { + val secondTree = AVLTree() + secondTree.add(1, "value1") + secondTree.add(2, "value2") + + tree.merge(secondTree) + + assert(secondTree.root == tree.root) + } + + @Test + fun `should merge empty second tree with initial tree`() { + val secondTree = AVLTree() + tree.add(1, "value1") + tree.add(2, "value2") + + tree.merge(secondTree) + + assert(tree.root == tree.root) + } + + @Test + fun `should merge non empty trees`() { + val secondTree = AVLTree() + tree.add(1, "value1") + tree.add(2, "value2") + secondTree.add(3, "value3") + secondTree.add(4, "value4") + + tree.merge(secondTree) + + // Check if the resulting tree contains all elements from both trees + var treeNodeCount = 0 + tree.iterator().forEach { _ -> treeNodeCount++ } + assert("value1" == tree.getNode(1)?.value) + assert("value2" == tree.getNode(2)?.value) + assert("value3" == tree.getNode(3)?.value) + assert("value4" == tree.getNode(4)?.value) + } + + @Test + fun `Merge should throw IllegalArgumentException on attempt to merge tree that isn't bigger than this`() { + val secondTree = AVLTree() + val keys = arrayOf(0, 1, -1) + val secondKeys = arrayOf(3, 4, 0) + + keys.forEach { tree.add(it, it.toString()) } + secondKeys.forEach { secondTree.add(it, it.toString()) } + + val message = + "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" + + val exception = assertFailsWith { + tree.merge(secondTree) + } + assert(exception.message == message) + } + } +} \ No newline at end of file From 97e85f205e52c704256a8cdd2a8ce3e76b3d63e1 Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Thu, 4 Apr 2024 14:04:46 +0300 Subject: [PATCH 47/52] avl tree 93% test coverage add big right rotate test --- src/test/kotlin/AVLTreeTest.kt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt index 1623405..03731d1 100644 --- a/src/test/kotlin/AVLTreeTest.kt +++ b/src/test/kotlin/AVLTreeTest.kt @@ -85,6 +85,26 @@ class AVLTreeTest { val root = tree.root assert(root == tree.getNode(3)) } + + @Test + fun `Inserting with big right rotate (last node)`(){ + val key1 = 5 + val key2 = 6 + val key3 = 4 + val key4 = 2 + val key5 = 3 + val key6 = 1 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + tree.add(key4, key4.toString()) + tree.add(key5, key5.toString()) + tree.add(key6, key6.toString()) + + val root = tree.root + assert(root == tree.getNode(3)) + } } @Nested From 1742d57c8c09fff91866163afc176c24c2eb4cf2 Mon Sep 17 00:00:00 2001 From: Daniil Gambit Date: Thu, 4 Apr 2024 14:29:57 +0300 Subject: [PATCH 48/52] + 1 test avl tree --- src/test/kotlin/AVLTreeTest.kt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt index 03731d1..fcc6972 100644 --- a/src/test/kotlin/AVLTreeTest.kt +++ b/src/test/kotlin/AVLTreeTest.kt @@ -255,6 +255,34 @@ class AVLTreeTest { assert(newNode == root?.right) assert(newNode?.right != null && newNode.left != null) } + + @Test + fun `Deletion of node with two children, which also have child (version 2)`(){ + val key1 = 5 + val key2 = 2 + val key3 = 6 + val key4 = 0 + val key5 = 3 + val key6 = 7 + val key7 = 4 + + tree.add(key1, key1.toString()) + tree.add(key2, key2.toString()) + tree.add(key3, key3.toString()) + tree.add(key4, key4.toString()) + tree.add(key5, key5.toString()) + tree.add(key6, key6.toString()) + tree.add(key7, key7.toString()) + + tree.delete(6) + + val newNode = tree.getNode(key1) + val root = tree.root + + assert(tree.getNode(6) == null) + assert(newNode == root?.right) + assert(newNode?.right != null && newNode.left != null) + } } @Nested From 0fa2f2b8f9bb70f0e2f536fbce8a24c72b144254 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Sat, 6 Apr 2024 12:31:11 +0300 Subject: [PATCH 49/52] add: how to launch project guide --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e902127..7bfa052 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ ## Как пользоватся +### Запуск программы +```shell +./gradlew build +``` ### Инициализация ```kotlin val binaryTree = BinaryTree() From eb00bd6d38c162b259980c22d46493a75f9763dc Mon Sep 17 00:00:00 2001 From: VersusXX Date: Sat, 6 Apr 2024 12:33:57 +0300 Subject: [PATCH 50/52] remove: unecessary min/max methods Removed overriden minimum and maximum methods because they already were inherited by Tree class. --- src/main/kotlin/Nodes.kt | 17 ----------------- src/main/kotlin/RBTree.kt | 10 +--------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index ce98081..a68ed37 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -37,27 +37,10 @@ open class Node, V, T : Node> internal constructor( "($key: $value)" } } - - fun max(): RBNode { - var current = this - while (current.right != null) { - current = current.right as RBNode - } - return current - } - - fun min(): RBNode { - var current = this - while (current.left != null) { - current = current.left as RBNode - } - return current - } val isOnLeft: Boolean get() = this == parent?.left fun sibling(): RBNode? { - if (parent == null) return null if (isOnLeft) return parent!!.right as RBNode? diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 11bd223..2a10fd6 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -66,7 +66,7 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree // 'nodeToDelete' is the root, so assign the value of 'replacementNode' to 'nodeToDelete' and delete 'replacementNode' nodeToDelete.key = replacementNode.key nodeToDelete.right = null - nodeToDelete.left = nodeToDelete.right + nodeToDelete.left = null // Delete 'replacementNode' } else { // Detach 'nodeToDelete' from the tree and move 'replacementNode' up @@ -190,14 +190,6 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree } } - override fun max(): Node.RBNode? { - return if (root == null) null else (root as Node.RBNode).max() - } - - override fun min(): Node.RBNode? { - return if (root == null) null else (root as Node.RBNode).min() - } - fun setColored(value: Boolean) { Node.RBNode.colored = value } From 3f9c953e0eb17a12cfce98f8b31455779be8a110 Mon Sep 17 00:00:00 2001 From: VersusXX Date: Sat, 6 Apr 2024 12:38:28 +0300 Subject: [PATCH 51/52] add: toStringHeight tests for RBTree Also removed unecessary minimum and maximum tests because they just inherit from Tree class. Fixed some too many checks in one assert problem in RBTreeTests --- src/test/kotlin/RBNodeTest.kt | 44 ---- src/test/kotlin/RedBlackTreeTest.kt | 330 ++++++++++++---------------- 2 files changed, 140 insertions(+), 234 deletions(-) diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt index 103f122..25fb2e5 100644 --- a/src/test/kotlin/RBNodeTest.kt +++ b/src/test/kotlin/RBNodeTest.kt @@ -9,50 +9,6 @@ class RBNodeTest { tree = RBTree() } - // max and min tests - @Test - fun `max should return if empty`() { - assert(tree.max()?.value == null) - } - - @Test - fun `min should return if empty`() { - assert(tree.min()?.value == null) - } - - @Test - fun `max should return max value`() { - tree.add(1, "A") - tree.add(2, "B") - tree.add(3, "C") - tree.add(4, "D") - tree.add(5, "E") - tree.add(6, "F") - tree.add(7, "G") - tree.add(8, "H") - tree.add(9, "I") - tree.add(10, "J") - - assert(tree.max()?.value == "J") - } - - @Test - fun `min should return min value`() { - tree.add(1, "A") - tree.add(2, "B") - tree.add(3, "C") - tree.add(4, "D") - tree.add(5, "E") - tree.add(6, "F") - tree.add(7, "G") - tree.add(8, "H") - tree.add(9, "I") - tree.add(10, "J") - - assert(tree.min()?.value == "A") - } - - // toString tests @Test fun `should return null if tree is empty`() { diff --git a/src/test/kotlin/RedBlackTreeTest.kt b/src/test/kotlin/RedBlackTreeTest.kt index ff1c33d..b5851fd 100644 --- a/src/test/kotlin/RedBlackTreeTest.kt +++ b/src/test/kotlin/RedBlackTreeTest.kt @@ -46,34 +46,9 @@ class RedBlackTreeTest { val left = root?.left as Node.RBNode? val right = root?.right as Node.RBNode? - assert( - root != null - && root.key == 56 - && root.color == Node.RBNode.Color.BLACK - && left?.key == 27 - && left.color == Node.RBNode.Color.RED - && right?.key == 85 - && right.color == Node.RBNode.Color.RED - ) - } - } - - @Nested - inner class MinimumMaximumTests { - @Test - fun `max should return max node`() { - for (i in 1..10) { - tree.add(i, "") - } - assert(tree.max()?.key == 10) - } - - @Test - fun `min should return min node`() { - for (i in 1..10) { - tree.add(i, "") - } - assert(tree.min()?.key == 1) + assert(root != null && root.key == 56 && root.color == Node.RBNode.Color.BLACK) + assert(left?.key == 27 && left.color == Node.RBNode.Color.RED) + assert(right?.key == 85 && right.color == Node.RBNode.Color.RED) } } @@ -197,13 +172,10 @@ class RedBlackTreeTest { val parent = addedNode?.parent as Node.RBNode? val uncle = root?.right as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && parent?.color == Node.RBNode.Color.BLACK - && uncle?.color == Node.RBNode.Color.BLACK - && addedNode?.color == Node.RBNode.Color.RED - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(parent?.color == Node.RBNode.Color.BLACK) + assert(uncle?.color == Node.RBNode.Color.BLACK) + assert(addedNode?.color == Node.RBNode.Color.RED) } @Test @@ -224,13 +196,10 @@ class RedBlackTreeTest { val parent = addedNode?.parent as Node.RBNode? val uncle = root?.right as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && parent?.color == Node.RBNode.Color.BLACK - && uncle?.color == Node.RBNode.Color.BLACK - && addedNode?.color == Node.RBNode.Color.RED - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(parent?.color == Node.RBNode.Color.BLACK) + assert(uncle?.color == Node.RBNode.Color.BLACK) + assert(addedNode?.color == Node.RBNode.Color.RED) } @Test @@ -253,13 +222,10 @@ class RedBlackTreeTest { val leftChild = addedNode?.left as Node.RBNode? val rightChild = addedNode?.right as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && addedNode?.color == Node.RBNode.Color.BLACK - && leftChild?.color == Node.RBNode.Color.RED - && rightChild?.color == Node.RBNode.Color.RED - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(addedNode?.color == Node.RBNode.Color.BLACK) + assert(leftChild?.color == Node.RBNode.Color.RED) + assert(rightChild?.color == Node.RBNode.Color.RED) } @Test @@ -283,16 +249,11 @@ class RedBlackTreeTest { val leftChild = addedNode?.left as Node.RBNode? val rightChild = addedNode?.right as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && addedNode?.color == Node.RBNode.Color.BLACK - && leftChild?.color == Node.RBNode.Color.RED - && rightChild?.color == Node.RBNode.Color.RED - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(addedNode?.color == Node.RBNode.Color.BLACK) + assert(leftChild?.color == Node.RBNode.Color.RED) + assert(rightChild?.color == Node.RBNode.Color.RED) } - - } /** @@ -334,14 +295,11 @@ class RedBlackTreeTest { tree.delete(2) val root = tree.root as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 1 - && root.left == null - && root.right == null - && tree.getNode(2) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 1) + assert(root.left == null) + assert(root.right == null) + assert(tree.getNode(2) == null) } @Test @@ -352,14 +310,11 @@ class RedBlackTreeTest { tree.delete(1) val root = tree.root as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 2 - && root.left == null - && root.right == null - && tree.getNode(1) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 2) + assert(root.left == null) + assert(root.right == null) + assert(tree.getNode(1) == null) } @Test @@ -370,14 +325,11 @@ class RedBlackTreeTest { tree.delete(1) val root = tree.root as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 2 - && root.left == null - && root.right == null - && tree.getNode(1) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 2) + assert(root.left == null) + assert(root.right == null) + assert(tree.getNode(1) == null) } @Test @@ -389,14 +341,11 @@ class RedBlackTreeTest { tree.delete(2) val root = tree.root as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 3 - && root.right == null - && root.left?.key == 1 - && tree.getNode(2) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 3) + assert(root.right == null) + assert(root.left?.key == 1) + assert(tree.getNode(2) == null) } @Test @@ -410,14 +359,11 @@ class RedBlackTreeTest { val root = tree.root as Node.RBNode? - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 2 - && root.left?.key == 1 - && root.right?.key == 4 - && tree.getNode(3) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 2) + assert(root.left?.key == 1) + assert(root.right?.key == 4) + assert(tree.getNode(3) == null) } @Test @@ -447,19 +393,16 @@ class RedBlackTreeTest { val rootRightRight = rootRight?.right as Node.RBNode? val rootRightLeft = rootRight?.left as Node.RBNode? // check if the tree is balanced - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 3 - && root.left?.key == 1 - && root.right?.key == 5 - && rootRight?.color == Node.RBNode.Color.RED - && rootRightLeft?.key == 4 - && rootRightLeft.color == Node.RBNode.Color.BLACK - && rootRightRight?.key == 6 - && rootRightRight.color == Node.RBNode.Color.BLACK - && tree.getNode(2) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 3) + assert(root.left?.key == 1) + assert(root.right?.key == 5) + assert(rootRight?.color == Node.RBNode.Color.RED) + assert(rootRightLeft?.key == 4) + assert(rootRightLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootRightRight?.key == 6) + assert(rootRightRight!!.color == Node.RBNode.Color.BLACK) + assert(tree.getNode(2) == null) } @Test @@ -488,20 +431,17 @@ class RedBlackTreeTest { val rootRightRight = rootRight?.right as Node.RBNode? // check if the tree is balanced - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 4 - && rootLeft?.key == 2 - && rootRight?.key == 5 - && rootRight.color == Node.RBNode.Color.BLACK - && rootLeft.color == Node.RBNode.Color.BLACK - && rootLeftLeft?.key == 1 - && rootLeftLeft.color == Node.RBNode.Color.RED - && rootRightRight?.key == 6 - && rootRightRight.color == Node.RBNode.Color.RED - && tree.getNode(3) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 4) + assert(rootLeft?.key == 2) + assert(rootRight?.key == 5) + assert(rootRight!!.color == Node.RBNode.Color.BLACK) + assert(rootLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootLeftLeft?.key == 1) + assert(rootLeftLeft!!.color == Node.RBNode.Color.RED) + assert(rootRightRight?.key == 6) + assert(rootRightRight!!.color == Node.RBNode.Color.RED) + assert(tree.getNode(3) == null) } @Test @@ -532,18 +472,15 @@ class RedBlackTreeTest { val rootLeftRight = rootLeft?.right as Node.RBNode? // check if the tree is balanced - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 53 - && rootLeft?.key == 27 - && rootLeft.color == Node.RBNode.Color.RED - && rootLeftLeft?.key == 17 - && rootLeftLeft.color == Node.RBNode.Color.BLACK - && rootLeftRight?.key == 43 - && rootLeftRight.color == Node.RBNode.Color.BLACK - && tree.getNode(32) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 53) + assert(rootLeft?.key == 27) + assert(rootLeft!!.color == Node.RBNode.Color.RED) + assert(rootLeftLeft?.key == 17) + assert(rootLeftLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootLeftRight?.key == 43) + assert(rootLeftRight!!.color == Node.RBNode.Color.BLACK) + assert(tree.getNode(32) == null) } @Test @@ -575,20 +512,17 @@ class RedBlackTreeTest { val rootRightRightLeft = rootRightRight?.left as Node.RBNode? // check if the tree is balanced - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 53 - && rootRight?.key == 69 - && rootRight.color == Node.RBNode.Color.RED - && rootRightLeft?.key == 62 - && rootRightLeft.color == Node.RBNode.Color.BLACK - && rootRightRight?.key == 90 - && rootRightRight.color == Node.RBNode.Color.BLACK - && rootRightRightLeft?.key == 81 - && rootRightRightLeft.color == Node.RBNode.Color.RED - && tree.getNode(93) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 53) + assert(rootRight?.key == 69) + assert(rootRight!!.color == Node.RBNode.Color.RED) + assert(rootRightLeft?.key == 62) + assert(rootRightLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootRightRight?.key == 90) + assert(rootRightRight!!.color == Node.RBNode.Color.BLACK) + assert(rootRightRightLeft?.key == 81) + assert(rootRightRightLeft!!.color == Node.RBNode.Color.RED) + assert(tree.getNode(93) == null) } @Test @@ -621,26 +555,23 @@ class RedBlackTreeTest { val rootLeftLeft = rootLeft?.left as Node.RBNode? // check if the tree is balanced - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 60 - && rootLeft?.key == 49 - && rootLeft.color == Node.RBNode.Color.RED - && rootLeftLeft?.key == 42 - && rootLeftLeft.color == Node.RBNode.Color.BLACK - && rootLeftRight?.key == 59 - && rootLeftRight.color == Node.RBNode.Color.BLACK - && rootRight?.key == 84 - && rootRight.color == Node.RBNode.Color.RED - && rootRightLeft?.key == 71 - && rootRightLeft.color == Node.RBNode.Color.BLACK - && rootRightRight?.key == 93 - && rootRightRight.color == Node.RBNode.Color.BLACK - && rootRightLeftLeft?.key == 68 - && rootRightLeftLeft.color == Node.RBNode.Color.RED - && tree.getNode(23) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 60) + assert(rootLeft?.key == 49) + assert(rootLeft!!.color == Node.RBNode.Color.RED) + assert(rootLeftLeft?.key == 42) + assert(rootLeftLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootLeftRight?.key == 59) + assert(rootLeftRight!!.color == Node.RBNode.Color.BLACK) + assert(rootRight?.key == 84) + assert(rootRight!!.color == Node.RBNode.Color.RED) + assert(rootRightLeft?.key == 71) + assert(rootRightLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootRightRight?.key == 93) + assert(rootRightRight!!.color == Node.RBNode.Color.BLACK) + assert(rootRightLeftLeft?.key == 68) + assert(rootRightLeftLeft!!.color == Node.RBNode.Color.RED) + assert(tree.getNode(23) == null) } @Test @@ -672,26 +603,45 @@ class RedBlackTreeTest { val rootLeftLeft = rootLeft?.left as Node.RBNode? // check if the tree is balanced - assert( - root != null - && root.color == Node.RBNode.Color.BLACK - && root.key == 45 - && rootLeft?.key == 17 - && rootLeft.color == Node.RBNode.Color.BLACK - && rootLeftLeft?.key == 1 - && rootLeftLeft.color == Node.RBNode.Color.RED - && rootLeftRight?.key == 21 - && rootLeftRight.color == Node.RBNode.Color.RED - && rootRight?.key == 78 - && rootRight.color == Node.RBNode.Color.RED - && rootRightLeft?.key == 69 - && rootRightLeft.color == Node.RBNode.Color.BLACK - && rootRightRight?.key == 88 - && rootRightRight.color == Node.RBNode.Color.BLACK - && tree.getNode(28) == null - && tree.getNode(42) == null - ) + assert(root != null && root.color == Node.RBNode.Color.BLACK) + assert(root!!.key == 45) + assert(rootLeft?.key == 17) + assert(rootLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootLeftLeft?.key == 1) + assert(rootLeftLeft!!.color == Node.RBNode.Color.RED) + assert(rootLeftRight?.key == 21) + assert(rootLeftRight!!.color == Node.RBNode.Color.RED) + assert(rootRight?.key == 78) + assert(rootRight!!.color == Node.RBNode.Color.RED) + assert(rootRightLeft?.key == 69) + assert(rootRightLeft!!.color == Node.RBNode.Color.BLACK) + assert(rootRightRight?.key == 88) + assert(rootRightRight!!.color == Node.RBNode.Color.BLACK) + assert(tree.getNode(28) == null) + assert(tree.getNode(42) == null) } } + @Test + fun `toStringBeautifulHeight should work correctly on empty tree`() { + assert(tree.toStringBeautifulHeight() == "") + } + + @Test + fun `toStringBeautifulHeight should work correctly on sample`() { + // no-child, left, right and 2 child node example + // because it works similar on all this scenarios + val keys = arrayOf(0, 1, -1, 2, -2) + keys.forEach { tree.add(it, it.toString()) } + + val strTree = tree.toStringBeautifulHeight() + val expectedResult = """────────────────────────┐ + (0: 0) + ┌───────────┴───────────┐ + (-1: -1) (1: 1) + ┌─────┘ └─────┐ + (-2: -2) (2: 2) +""" + assert(strTree == expectedResult) + } } From c36c5abe8b7911be08683ecce5732ed3b7bc86ba Mon Sep 17 00:00:00 2001 From: AlexandrKudrya Date: Tue, 9 Apr 2024 21:41:49 +0700 Subject: [PATCH 52/52] Linter used. Also some BinaryTreeTest fix. Adding jacoco-badge-generator badge to README.md --- README.md | 1 + build.gradle.kts | 5 +- settings.gradle.kts | 1 - src/main/kotlin/AVLTree.kt | 63 +++++++---- src/main/kotlin/BinaryTree.kt | 5 +- src/main/kotlin/Main.kt | 7 +- src/main/kotlin/Nodes.kt | 17 ++- src/main/kotlin/RBTree.kt | 57 ++++++---- src/main/kotlin/Tree.kt | 145 +++++++++++++++--------- src/test/kotlin/AVLNodeTest.kt | 2 +- src/test/kotlin/AVLTreeTest.kt | 45 ++++---- src/test/kotlin/BFSIteratorTest.kt | 6 +- src/test/kotlin/BinaryTreeTest.kt | 159 +++++++++++++-------------- src/test/kotlin/ByKeyIteratorTest.kt | 6 +- src/test/kotlin/DFSIteratorTest.kt | 5 +- src/test/kotlin/NodeTest.kt | 7 +- src/test/kotlin/RBNodeTest.kt | 4 +- src/test/kotlin/RedBlackTreeTest.kt | 70 +++++++++--- 18 files changed, 354 insertions(+), 251 deletions(-) diff --git a/README.md b/README.md index 7bfa052..8865121 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![MIT License][license-shield]][license-url] +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/cicirello/jacoco-badge-generator?label=Marketplace&logo=GitHub)](https://github.com/marketplace/actions/jacoco-badge-generator)

Борцы Даня, Саша, Мухаммет

diff --git a/build.gradle.kts b/build.gradle.kts index 7e4ee2f..ce5f22f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,6 @@ plugins { application } - group = "org.example" version = "1.0-SNAPSHOT" @@ -33,7 +32,7 @@ tasks.test { println(" > Failed: ${result.failedTestCount}") println(" > Skipped: ${result.skippedTestCount}") } - }) + }), ) } @@ -47,7 +46,7 @@ tasks.test { tasks.named("jacocoTestReport") { dependsOn(tasks.test) reports { - csv.required = true + csv.required = true xml.required = false html.required = false } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8cebd10..d861336 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,4 +2,3 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } rootProject.name = "temp" - diff --git a/src/main/kotlin/AVLTree.kt b/src/main/kotlin/AVLTree.kt index 2677d73..242fbfc 100644 --- a/src/main/kotlin/AVLTree.kt +++ b/src/main/kotlin/AVLTree.kt @@ -1,6 +1,8 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tree>(root) { - - override fun add(key: K, value: V) { + override fun add( + key: K, + value: V, + ) { this.root = insert(this.root as Node.AVLNode?, key, value) } @@ -10,7 +12,10 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr return node.height } - private fun max(a: Int, b: Int): Int { + private fun max( + a: Int, + b: Int, + ): Int { return if ((a > b)) a else b } @@ -20,13 +25,20 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr return height(node.left as Node.AVLNode?) - height(node.right as Node.AVLNode?) } - private fun insert(node: Node.AVLNode?, key: K, value: V): Node.AVLNode? { - + private fun insert( + node: Node.AVLNode?, + key: K, + value: V, + ): Node.AVLNode? { if (node == null) return Node.AVLNode(key, value) - if (key < node.key) node.left = insert(node.left as Node.AVLNode?, key, value) - else if (key > node.key) node.right = insert(node.right as Node.AVLNode?, key, value) - else return node + if (key < node.key) { + node.left = insert(node.left as Node.AVLNode?, key, value) + } else if (key > node.key) { + node.right = insert(node.right as Node.AVLNode?, key, value) + } else { + return node + } node.height = 1 + max(height(node.left as Node.AVLNode?), height(node.right as Node.AVLNode?)) @@ -49,23 +61,33 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr return node } - private fun deleteNode(node: Node.AVLNode?, key: K): Node.AVLNode? { + private fun deleteNode( + node: Node.AVLNode?, + key: K, + ): Node.AVLNode? { var root = node if (root == null) return null - if (key < root.key) root.left = deleteNode(root.left as Node.AVLNode?, key) - else if (key > root.key) root.right = deleteNode(root.right as Node.AVLNode?, key) - else { + if (key < root.key) { + root.left = deleteNode(root.left as Node.AVLNode?, key) + } else if (key > root.key) { + root.right = deleteNode(root.right as Node.AVLNode?, key) + } else { if ((root.left == null) || (root.right == null)) { var temp: Node.AVLNode? = null - temp = if (temp === root.left) root.right as Node.AVLNode? - else root.left as Node.AVLNode? + temp = + if (temp === root.left) { + root.right as Node.AVLNode? + } else { + root.left as Node.AVLNode? + } if (temp == null) { temp = root root = null - } else root = temp - + } else { + root = temp + } } else { val temp = minValueNode(root.right as Node.AVLNode?) @@ -102,7 +124,7 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr private fun minValueNode(node: Node.AVLNode?): Node.AVLNode? { var current = node - /* loop down to find the leftmost leaf */ + // loop down to find the leftmost leaf while (current!!.left != null) current = current.left as Node.AVLNode? return current @@ -143,8 +165,9 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" } } - if (this.root == null) this.root = tree.root - else { + if (this.root == null) { + this.root = tree.root + } else { tree.forEach { this.add(it.key, it.value) } @@ -162,4 +185,4 @@ class AVLTree, V : Any>(root: Node.AVLNode? = null) : Tr override fun delete(key: K) { root = deleteNode(root as Node.AVLNode?, key) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/BinaryTree.kt b/src/main/kotlin/BinaryTree.kt index bec2658..56be611 100644 --- a/src/main/kotlin/BinaryTree.kt +++ b/src/main/kotlin/BinaryTree.kt @@ -1,3 +1,4 @@ -class BinaryTree, V: Any>(root: Node.BinaryNode? = null): Tree>(root) +class BinaryTree, V : Any>(root: Node.BinaryNode? = null) : + Tree>(root) -// Not sure, that this file is needed, because BinaryTree is just Tree. \ No newline at end of file +// Not sure, that this file is needed, because BinaryTree is just Tree. diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 3c7ed99..fa9b2a2 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,4 +1,3 @@ - fun main() { val binaryTree = BinaryTree() val keys = arrayOf(3, 2, 1, 0, 4, 5) @@ -8,7 +7,7 @@ fun main() { // Стандартно iterateDFS работает с mode=Tree.ModeDFS.PREORDER binaryTree.iterateDFS().forEach { print(it.key.toString() + " ") } println() - binaryTree.iterateDFS(mode=Tree.ModeDFS.INORDER).forEach { print(it.key.toString() + " ") } + binaryTree.iterateDFS(mode = Tree.ModeDFS.INORDER).forEach { print(it.key.toString() + " ") } println() - binaryTree.iterateDFS(mode=Tree.ModeDFS.POSTORDER).forEach { print(it.key.toString() + " ") } -} \ No newline at end of file + binaryTree.iterateDFS(mode = Tree.ModeDFS.POSTORDER).forEach { print(it.key.toString() + " ") } +} diff --git a/src/main/kotlin/Nodes.kt b/src/main/kotlin/Nodes.kt index a68ed37..c7b82cd 100644 --- a/src/main/kotlin/Nodes.kt +++ b/src/main/kotlin/Nodes.kt @@ -3,15 +3,14 @@ open class Node, V, T : Node> internal constructor( var value: V, internal var left: Node? = null, internal var right: Node? = null, - internal var parent: Node? = null + internal var parent: Node? = null, ) { - class BinaryNode, V>( key: K, value: V, left: BinaryNode? = null, right: BinaryNode? = null, - parent: BinaryNode? = null + parent: BinaryNode? = null, ) : Node>(key, value, left, right, parent) class RBNode, V>( @@ -31,12 +30,13 @@ open class Node, V, T : Node> internal constructor( } override fun toString(): String { - return if ( colored && color == Color.RED) { + return if (colored && color == Color.RED) { "${RED_COLOR}($key: $value)${RESET_COLOR}" } else { "($key: $value)" } } + val isOnLeft: Boolean get() = this == parent?.left @@ -50,7 +50,7 @@ open class Node, V, T : Node> internal constructor( fun hasRedChild(): Boolean { return (left != null && (left as RBNode).color == Color.RED) || - (right != null && (right as RBNode).color == Color.RED) + (right != null && (right as RBNode).color == Color.RED) } } @@ -60,9 +60,8 @@ open class Node, V, T : Node> internal constructor( left: AVLNode? = null, right: AVLNode? = null, parent: AVLNode? = null, - var height: Int = 1 + var height: Int = 1, ) : Node>(key, value, left, right, parent) { - /*override fun toString(): String { val rightHeight = if (right != null) (right as AVLNode).height else 0 val leftHeight = if (left != null) (left as AVLNode).height else 0 @@ -84,7 +83,6 @@ open class Node, V, T : Node> internal constructor( } return current } - } override fun toString(): String { @@ -98,5 +96,4 @@ open class Node, V, T : Node> internal constructor( override fun hashCode(): Int { return super.hashCode() } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/RBTree.kt b/src/main/kotlin/RBTree.kt index 2a10fd6..ea5b69d 100644 --- a/src/main/kotlin/RBTree.kt +++ b/src/main/kotlin/RBTree.kt @@ -2,8 +2,10 @@ import kotlin.math.ceil import kotlin.math.floor class RBTree, V : Any>(root: Node.RBNode? = null) : Tree>(root) { - - override fun add(key: K, value: V) { + override fun add( + key: K, + value: V, + ) { val node: Node.RBNode = Node.RBNode(key, value) this.addNode(node) } @@ -19,8 +21,9 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" } } - if (this.root == null) this.root = tree.root - else { + if (this.root == null) { + this.root = tree.root + } else { tree.forEach { this.add(it.key, it.value) } @@ -54,8 +57,11 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree nodeToDelete.sibling()!!.color = Node.RBNode.Color.RED } // Delete 'nodeToDelete' from the tree - if (nodeToDelete.isOnLeft) parentNode!!.left = null - else parentNode!!.right = null + if (nodeToDelete.isOnLeft) { + parentNode!!.left = null + } else { + parentNode!!.right = null + } } return } @@ -70,8 +76,11 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree // Delete 'replacementNode' } else { // Detach 'nodeToDelete' from the tree and move 'replacementNode' up - if (nodeToDelete.isOnLeft) parentNode!!.left = replacementNode - else parentNode!!.right = replacementNode + if (nodeToDelete.isOnLeft) { + parentNode!!.left = replacementNode + } else { + parentNode!!.right = replacementNode + } replacementNode.parent = parentNode @@ -91,7 +100,10 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree deleteNode(replacementNode) } - private fun swapValues(nodeA: Node.RBNode, nodeB: Node.RBNode) { + private fun swapValues( + nodeA: Node.RBNode, + nodeB: Node.RBNode, + ) { val tempKey = nodeA.key val tempValue = nodeA.value @@ -110,16 +122,20 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree val parent = doubleBlackNode.parent as Node.RBNode? // If there is no sibling, the double black is pushed up to its parent - if (sibling == null) fixDoubleBlack(parent) - else { + if (sibling == null) { + fixDoubleBlack(parent) + } else { if (parent == null) return if (sibling.color == Node.RBNode.Color.RED) { // If the sibling is red, perform rotation and color changes parent.color = Node.RBNode.Color.RED sibling.color = Node.RBNode.Color.BLACK - if (sibling.isOnLeft) parent.rightRotate() // Right case - else parent.leftRotate() // Left case + if (sibling.isOnLeft) { + parent.rightRotate() // Right case + } else { + parent.leftRotate() // Left case + } fixDoubleBlack(doubleBlackNode) // Recursively fix double black } else { @@ -159,8 +175,11 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree } else { // If both children of the sibling are black sibling.color = Node.RBNode.Color.RED - if (parent.color == Node.RBNode.Color.BLACK) fixDoubleBlack(parent) - else parent.color = Node.RBNode.Color.BLACK + if (parent.color == Node.RBNode.Color.BLACK) { + fixDoubleBlack(parent) + } else { + parent.color = Node.RBNode.Color.BLACK + } } } } @@ -245,8 +264,9 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree } override fun toStringBeautifulHeight(ofSide: Int): String { - if (this.root == null) return "" - else { + if (this.root == null) { + return "" + } else { val buffer: StringBuilder = StringBuilder() val lines: MutableList> = mutableListOf() @@ -393,5 +413,4 @@ class RBTree, V : Any>(root: Node.RBNode? = null) : Tree newRoot?.right = this this.parent = newRoot } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/Tree.kt b/src/main/kotlin/Tree.kt index 51ffad7..67a986a 100644 --- a/src/main/kotlin/Tree.kt +++ b/src/main/kotlin/Tree.kt @@ -2,13 +2,17 @@ import java.util.* import kotlin.math.ceil import kotlin.math.floor -open class Tree , V: Any, T: Node> ( - var root: Node? = null -): Iterable> { - open fun add(key: K, value: V) { +open class Tree, V : Any, T : Node>( + var root: Node? = null, +) : Iterable> { + open fun add( + key: K, + value: V, + ) { val node: Node = Node(key, value) this.addNode(node) } + open fun addNode(node: Node) { require(this.root?.key != node.key) { throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function") @@ -19,17 +23,27 @@ open class Tree , V: Any, T: Node> ( val root: Node = this.root!! // not-null assertion operator because null check val isLeft: Boolean = root.key > node.key - addNode(node, + addNode( + node, (if (isLeft) root.left else root.right), root, - isLeft) + isLeft, + ) } } - private tailrec fun addNode(node: Node, current: Node?, parent: Node, isLeft: Boolean) { + private tailrec fun addNode( + node: Node, + current: Node?, + parent: Node, + isLeft: Boolean, + ) { if (current == null) { - if (isLeft) parent.left = node - else parent.right = node + if (isLeft) { + parent.left = node + } else { + parent.right = node + } node.parent = parent } else { @@ -37,18 +51,22 @@ open class Tree , V: Any, T: Node> ( throw IllegalArgumentException("Multiple uses of key: ${node.key}. To modify value use set function") } val isAddedLeft: Boolean = current.key > node.key - addNode(node, + addNode( + node, (if (isAddedLeft) current.left else current.right), current, - isAddedLeft) + isAddedLeft, + ) } } - operator fun set(key: K, value: V) { + operator fun set( + key: K, + value: V, + ) { if (!this.isKey(key)) { this.add(key, value) - } - else { + } else { this.getNode(key)!!.value = value // not-null assertion operator because key is exist in tree } @@ -60,18 +78,33 @@ open class Tree , V: Any, T: Node> ( return getNode(key, this.root) } - private tailrec fun getNode(key: K, current: Node?): Node? { - return if (current == null) null - else if (current.key == key) current - else if (current.key > key) getNode(key, current.left) - else getNode(key, current.right) + private tailrec fun getNode( + key: K, + current: Node?, + ): Node? { + return if (current == null) { + null + } else if (current.key == key) { + current + } else if (current.key > key) { + getNode(key, current.left) + } else { + getNode(key, current.right) + } } - fun getOrDefault(key: K, default: V): V { + fun getOrDefault( + key: K, + default: V, + ): V { val node: Node? = getNode(key) return node?.value ?: default } - fun getOrDefaultNode(key: K, default: Node): Node { + + fun getOrDefaultNode( + key: K, + default: Node, + ): Node { return getNode(key, root) ?: default } @@ -151,11 +184,19 @@ open class Tree , V: Any, T: Node> ( return isKey(key, this.root) } - private tailrec fun isKey(key: K, current: Node?): Boolean { - return if (current == null) false - else if (current.key == key) true - else if (current.key > key) isKey(key, current.left) - else isKey(key, current.right) + private tailrec fun isKey( + key: K, + current: Node?, + ): Boolean { + return if (current == null) { + false + } else if (current.key == key) { + true + } else if (current.key > key) { + isKey(key, current.left) + } else { + isKey(key, current.right) + } } open fun min(): Node? { @@ -213,7 +254,7 @@ open class Tree , V: Any, T: Node> ( } inner class ByKeyIterator( - private val tree: Tree + private val tree: Tree, ) : Iterator> { private var current: Node? = tree.min() @@ -228,9 +269,7 @@ open class Tree , V: Any, T: Node> ( } } - - inner class BFSIterator(tree: Tree): Iterator> { - + inner class BFSIterator(tree: Tree) : Iterator> { private var queue: Queue> = LinkedList() init { @@ -259,14 +298,11 @@ open class Tree , V: Any, T: Node> ( override fun next(): Node { return queue.poll() } - } - enum class ModeDFS { PREORDER, POSTORDER, INORDER} - - inner class DFSIterator(tree: Tree, mode: ModeDFS = ModeDFS.PREORDER): Iterator> { - + enum class ModeDFS { PREORDER, POSTORDER, INORDER } + inner class DFSIterator(tree: Tree, mode: ModeDFS = ModeDFS.PREORDER) : Iterator> { private var stack: Queue> = LinkedList() init { @@ -308,7 +344,6 @@ open class Tree , V: Any, T: Node> ( override fun next(): Node { return stack.poll() } - } override fun iterator(): Iterator> { @@ -320,7 +355,7 @@ open class Tree , V: Any, T: Node> ( } fun iterateDFS(mode: ModeDFS = ModeDFS.PREORDER): DFSIterator { - return DFSIterator(this, mode=mode) + return DFSIterator(this, mode = mode) } open fun merge(tree: Tree) { @@ -329,8 +364,11 @@ open class Tree , V: Any, T: Node> ( "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" } } - if (this.root == null) this.root = tree.root - else this.max()!!.right = tree.root + if (this.root == null) { + this.root = tree.root + } else { + this.max()!!.right = tree.root + } } fun split(x: K): Pair, Tree> { @@ -338,8 +376,11 @@ open class Tree , V: Any, T: Node> ( val biggerTree: Tree = Tree() for (i in this.iterateBFS()) { - if (i.key < x) lowerTree.addNode(i) - else biggerTree.addNode(i) + if (i.key < x) { + lowerTree.addNode(i) + } else { + biggerTree.addNode(i) + } } return Pair(lowerTree, biggerTree) } @@ -351,7 +392,7 @@ open class Tree , V: Any, T: Node> ( return clonedTree } - enum class TreeStringMode {NORMAL, WIDTH, HEIGHT} + enum class TreeStringMode { NORMAL, WIDTH, HEIGHT } fun toString(mode: TreeStringMode = TreeStringMode.NORMAL): String { return when (mode) { @@ -365,7 +406,7 @@ open class Tree , V: Any, T: Node> ( val buffer = StringBuilder() buffer.append("[") - this.forEach {buffer.append("${it.toString()}, ")} + this.forEach { buffer.append("$it, ") } val preResult = buffer.removeSuffix(", ").toString() val result = StringBuilder() @@ -376,8 +417,11 @@ open class Tree , V: Any, T: Node> ( } open fun toStringBeautifulWidth(): String { - return if (this.root == null) "" - else this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() + return if (this.root == null) { + "" + } else { + this.toStringBeautifulWidth(StringBuilder(), true, StringBuilder(), this.root!!).toString() + } // not-null assertion operator because null check } @@ -385,8 +429,8 @@ open class Tree , V: Any, T: Node> ( prefix: StringBuilder, isTail: Boolean, buffer: StringBuilder, - current: Node): StringBuilder - { + current: Node, + ): StringBuilder { if (current.right != null) { val newPrefix = StringBuilder() newPrefix.append(prefix) @@ -408,8 +452,9 @@ open class Tree , V: Any, T: Node> ( } open fun toStringBeautifulHeight(ofSide: Int = 4): String { - if (this.root == null) return "" - else { + if (this.root == null) { + return "" + } else { val buffer: StringBuilder = StringBuilder() val lines: MutableList> = mutableListOf() @@ -515,4 +560,4 @@ open class Tree , V: Any, T: Node> ( return buffer.toString() } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/AVLNodeTest.kt b/src/test/kotlin/AVLNodeTest.kt index 9017f95..1917142 100644 --- a/src/test/kotlin/AVLNodeTest.kt +++ b/src/test/kotlin/AVLNodeTest.kt @@ -51,4 +51,4 @@ class AVLNodeTest { assert(tree.min()?.key == 1) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/AVLTreeTest.kt b/src/test/kotlin/AVLTreeTest.kt index fcc6972..1a514ae 100644 --- a/src/test/kotlin/AVLTreeTest.kt +++ b/src/test/kotlin/AVLTreeTest.kt @@ -15,7 +15,7 @@ class AVLTreeTest { @Nested inner class AddTests { @Test - fun `Inserting a node into an empty tree`(){ + fun `Inserting a node into an empty tree`() { val key = 1 val value = "0" @@ -24,7 +24,7 @@ class AVLTreeTest { } @Test - fun `Inserting a duplicate node`(){ + fun `Inserting a duplicate node`() { tree.add(1, "0") tree.add(1, "0") @@ -35,7 +35,7 @@ class AVLTreeTest { } @Test - fun `Inserting with left rotate`(){ + fun `Inserting with left rotate`() { val key1 = 0 val key2 = 1 val key3 = 2 @@ -51,7 +51,7 @@ class AVLTreeTest { } @Test - fun `Inserting with right rotate`(){ + fun `Inserting with right rotate`() { val key1 = 2 val key2 = 1 val key3 = 0 @@ -67,7 +67,7 @@ class AVLTreeTest { } @Test - fun `Inserting with big left rotate (last node)`(){ + fun `Inserting with big left rotate (last node)`() { val key1 = 2 val key2 = 1 val key3 = 5 @@ -87,7 +87,7 @@ class AVLTreeTest { } @Test - fun `Inserting with big right rotate (last node)`(){ + fun `Inserting with big right rotate (last node)`() { val key1 = 5 val key2 = 6 val key3 = 4 @@ -108,7 +108,7 @@ class AVLTreeTest { } @Nested - inner class MinMaxTests(){ + inner class MinMaxTests() { @Test fun `Max should return max node`() { for (i in 1..10) { @@ -118,7 +118,7 @@ class AVLTreeTest { } @Test - fun `Max from null tree returns null`(){ + fun `Max from null tree returns null`() { assert(tree.max() == null) } @@ -131,15 +131,15 @@ class AVLTreeTest { } @Test - fun `Min from null tree returns null`(){ + fun `Min from null tree returns null`() { assert(tree.min() == null) } } @Nested - inner class DeleteTests{ + inner class DeleteTests { @Test - fun `Deletion of the non-existent node should do nothing`(){ + fun `Deletion of the non-existent node should do nothing`() { tree.add(1, "0") tree.add(2, "890") @@ -150,7 +150,7 @@ class AVLTreeTest { } @Test - fun `Deletion of tree's root`(){ + fun `Deletion of tree's root`() { val keyLeft = 0 val keyRoot = 1 val keyRight = 2 @@ -168,7 +168,7 @@ class AVLTreeTest { } @Test - fun `Deletion of leaf(node with no children)`(){ + fun `Deletion of leaf(node with no children)`() { val keyRoot = 0 val key = 1 val childKey = 2 @@ -185,7 +185,7 @@ class AVLTreeTest { } @Test - fun `Deletion of node with one child`(){ + fun `Deletion of node with one child`() { val key1 = 3 val key2 = 2 val key3 = 1 @@ -204,7 +204,7 @@ class AVLTreeTest { } @Test - fun `Deletion of node with two children`(){ + fun `Deletion of node with two children`() { val key1 = 2 val key2 = 1 val key3 = 5 @@ -229,7 +229,7 @@ class AVLTreeTest { } @Test - fun `Deletion of node with two children, which also have child`(){ + fun `Deletion of node with two children, which also have child`() { val key1 = 2 val key2 = 1 val key3 = 5 @@ -257,7 +257,7 @@ class AVLTreeTest { } @Test - fun `Deletion of node with two children, which also have child (version 2)`(){ + fun `Deletion of node with two children, which also have child (version 2)`() { val key1 = 5 val key2 = 2 val key3 = 6 @@ -286,7 +286,7 @@ class AVLTreeTest { } @Nested - inner class MergeTests{ + inner class MergeTests { @Test fun `Merge should work correctly on 2 empty trees`() { val secondTree = AVLTree() @@ -349,10 +349,11 @@ class AVLTreeTest { val message = "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" - val exception = assertFailsWith { - tree.merge(secondTree) - } + val exception = + assertFailsWith { + tree.merge(secondTree) + } assert(exception.message == message) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/BFSIteratorTest.kt b/src/test/kotlin/BFSIteratorTest.kt index 5092195..7feb5ec 100644 --- a/src/test/kotlin/BFSIteratorTest.kt +++ b/src/test/kotlin/BFSIteratorTest.kt @@ -1,8 +1,7 @@ -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class BFSIteratorTest{ +class BFSIteratorTest { private lateinit var tree: BinaryTree @BeforeEach @@ -27,5 +26,4 @@ class BFSIteratorTest{ for (i in keys.indices) assert(result[i] == expectedResult[i]) } - -} \ No newline at end of file +} diff --git a/src/test/kotlin/BinaryTreeTest.kt b/src/test/kotlin/BinaryTreeTest.kt index bade65e..541eb4b 100644 --- a/src/test/kotlin/BinaryTreeTest.kt +++ b/src/test/kotlin/BinaryTreeTest.kt @@ -1,4 +1,3 @@ -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import kotlin.test.assertFailsWith @@ -45,8 +44,8 @@ class BinaryTreeTest { assert( tree.root?.key == thirdKey && - tree.root?.left?.key == secondKey && - tree.root?.left?.left?.key == firstKey + tree.root?.left?.key == secondKey && + tree.root?.left?.left?.key == firstKey, ) } @@ -62,8 +61,8 @@ class BinaryTreeTest { assert( tree.root?.key == thirdKey && - tree.root?.left?.key == firstKey && - tree.root?.left?.right?.key == secondKey + tree.root?.left?.key == firstKey && + tree.root?.left?.right?.key == secondKey, ) } @@ -79,8 +78,8 @@ class BinaryTreeTest { assert( tree.root?.key == firstKey && - tree.root?.right?.key == thirdKey && - tree.root?.right?.left?.key == secondKey + tree.root?.right?.key == thirdKey && + tree.root?.right?.left?.key == secondKey, ) } @@ -96,8 +95,8 @@ class BinaryTreeTest { assert( tree.root?.key == firstKey && - tree.root?.right?.key == secondKey && - tree.root?.right?.right?.key == thirdKey + tree.root?.right?.key == secondKey && + tree.root?.right?.right?.key == thirdKey, ) } @@ -126,7 +125,7 @@ class BinaryTreeTest { fun `Get should return value`() { val key = 1 val expectedResult = "A" - tree[key] = expectedResult + tree.add(key, expectedResult) assert(tree[key] contentEquals expectedResult) } @@ -377,6 +376,7 @@ class BinaryTreeTest { assert(tree.root == null) } + @Test fun `Split should work correctly`() { val firstKey = -1 @@ -404,12 +404,12 @@ class BinaryTreeTest { val clonedTree = tree.clone() assert( - tree.getNode(firstKey) == clonedTree.getNode(firstKey) - && tree.getNode(firstKey) !== clonedTree.getNode(firstKey) - && tree.getNode(secondKey) == clonedTree.getNode(secondKey) - && tree.getNode(secondKey) !== clonedTree.getNode(secondKey) - && tree.getNode(thirdKey) == clonedTree.getNode(thirdKey) - && tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey) + tree.getNode(firstKey) == clonedTree.getNode(firstKey) && + tree.getNode(firstKey) !== clonedTree.getNode(firstKey) && + tree.getNode(secondKey) == clonedTree.getNode(secondKey) && + tree.getNode(secondKey) !== clonedTree.getNode(secondKey) && + tree.getNode(thirdKey) == clonedTree.getNode(thirdKey) && + tree.getNode(thirdKey) !== clonedTree.getNode(thirdKey), ) } @@ -489,12 +489,9 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert( - tree.getNode(keyRoot) === root - && root != null - && root.left === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root - ) + assert(tree.getNode(keyRoot) === root) + assert(root != null && root.left === tree.getNode(childKey)) + assert(tree.getNode(childKey)?.parent === root) } @Test @@ -509,12 +506,9 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert( - tree.getNode(keyRoot) === root - && root != null - && root.left === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root - ) + assert(tree.getNode(keyRoot) === root) + assert(root != null && root.left === tree.getNode(childKey)) + assert(tree.getNode(childKey)?.parent === root) } @Test @@ -529,12 +523,9 @@ class BinaryTreeTest { val root = tree.root tree.delete(key) - assert( - tree.getNode(keyRoot) === root - && root != null - && root.right === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root - ) + assert(tree.getNode(keyRoot) === root) + assert(root != null && root.right === tree.getNode(childKey)) + assert(tree.getNode(childKey)?.parent === root) } @Test @@ -550,10 +541,10 @@ class BinaryTreeTest { tree.delete(key) assert( - tree.getNode(keyRoot) === root - && root != null - && root.right === tree.getNode(childKey) - && tree.getNode(childKey)?.parent === root + tree.getNode(keyRoot) === root && + root != null && + root.right === tree.getNode(childKey) && + tree.getNode(childKey)?.parent === root, ) } @@ -571,17 +562,13 @@ class BinaryTreeTest { tree.delete(keyRoot) val root = tree.root - assert( - tree.getNode(keyRight) === root - && root != null - && root.left === tree.getNode(keyLeft) - && tree.getNode(keyLeft)?.parent === root - ) + assert(tree.getNode(keyRight) === root) + assert(root != null && root.left === tree.getNode(keyLeft)) + assert(tree.getNode(keyLeft)?.parent === root) } @Test fun `Delete should work correctly 2 child root where next has not left children`() { - // scenario explanation on picture // // ┌── 3 ┌── 3 @@ -599,17 +586,17 @@ class BinaryTreeTest { tree.delete(keys[0]) val root = tree.root - assert( - root == next - && root != null - && root.left === tree.getNode(keys[1]) - && tree.getNode(keys[1])?.parent === root - && root.right === tree.getNode(keys[2]) - && tree.getNode(keys[2])?.parent === root - && root.right != null - && root.right?.left === tree.getNode(keys[4]) - && tree.getNode(keys[4])?.parent === root.right - ) + if (root != null) { + assert(root == next) + assert(root.left === tree.getNode(keys[1])) + assert(tree.getNode(keys[1])?.parent === root) + assert(root.right === tree.getNode(keys[2])) + assert(tree.getNode(keys[2])?.parent === root) + assert(root.right != null && root.right?.left === tree.getNode(keys[4])) + assert(tree.getNode(keys[4])?.parent === root.right) + } else { + assert(false) + } } @Test @@ -628,13 +615,13 @@ class BinaryTreeTest { tree.delete(keys[2]) val root = tree.root - assert( - root != null - && root.left === tree.getNode(keys[1]) - && tree.getNode(keys[1])?.parent === root - && node == root.right - && root.right?.left === tree.getNode(keys[3]) - ) + if (root != null) { + assert(root.left === tree.getNode(keys[1])) + assert(tree.getNode(keys[1])?.parent === root) + assert(node == root.right && root.right?.left === tree.getNode(keys[3])) + } else { + assert(false) + } } @Test @@ -653,15 +640,16 @@ class BinaryTreeTest { val root = tree.root val node = tree.getNode(keys[2]) - assert( - root != null - && root.left === node - && node?.parent === root - && node.left === tree.getNode(keys[3]) - && tree.getNode(keys[3])?.parent === node - && node.right === tree.getNode(keys[4]) - && tree.getNode(keys[4])?.parent === node - ) + if (root != null && node != null) { + assert(root.left === node) + assert(node.parent === root) + assert(node.left === tree.getNode(keys[3])) + assert(tree.getNode(keys[3])?.parent === node) + assert(node.right === tree.getNode(keys[4])) + assert(tree.getNode(keys[4])?.parent === node) + } else { + assert(false) + } } // Yes, writing test on this function is very easy @@ -734,13 +722,14 @@ class BinaryTreeTest { fun `Add should throw IllegalArgumentException on attempt to add key to root that already exist `() { val key = 1 val message = - "Multiple uses of key: ${key}. To modify value use set function" + "Multiple uses of key: $key. To modify value use set function" tree.add(key, key.toString()) - val exception = assertFailsWith { - tree.add(key, key.toString()) - } + val exception = + assertFailsWith { + tree.add(key, key.toString()) + } assert(exception.message == message) } @@ -749,13 +738,14 @@ class BinaryTreeTest { val keyRoot = 0 val key = 1 val message = - "Multiple uses of key: ${key}. To modify value use set function" + "Multiple uses of key: $key. To modify value use set function" tree.add(keyRoot, keyRoot.toString()) tree.add(key, key.toString()) - val exception = assertFailsWith { - tree.add(key, key.toString()) - } + val exception = + assertFailsWith { + tree.add(key, key.toString()) + } assert(exception.message == message) } @@ -771,9 +761,10 @@ class BinaryTreeTest { val message = "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" - val exception = assertFailsWith { - tree.merge(secondTree) - } + val exception = + assertFailsWith { + tree.merge(secondTree) + } assert(exception.message == message) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/ByKeyIteratorTest.kt b/src/test/kotlin/ByKeyIteratorTest.kt index 386f88e..65fc303 100644 --- a/src/test/kotlin/ByKeyIteratorTest.kt +++ b/src/test/kotlin/ByKeyIteratorTest.kt @@ -1,8 +1,7 @@ -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class ByKeyIteratorTest{ +class ByKeyIteratorTest { private lateinit var tree: BinaryTree @BeforeEach @@ -27,5 +26,4 @@ class ByKeyIteratorTest{ for (i in keys.indices) assert(result[i] == expectedResult[i]) } - -} \ No newline at end of file +} diff --git a/src/test/kotlin/DFSIteratorTest.kt b/src/test/kotlin/DFSIteratorTest.kt index 0a35981..0a0ee34 100644 --- a/src/test/kotlin/DFSIteratorTest.kt +++ b/src/test/kotlin/DFSIteratorTest.kt @@ -1,7 +1,5 @@ -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import kotlin.math.acos class DFSIteratorTest { private lateinit var tree: BinaryTree @@ -50,5 +48,4 @@ class DFSIteratorTest { for (i in keys.indices) assert(result[i] == expectedResult[i]) } - -} \ No newline at end of file +} diff --git a/src/test/kotlin/NodeTest.kt b/src/test/kotlin/NodeTest.kt index 80bfa1c..8051a60 100644 --- a/src/test/kotlin/NodeTest.kt +++ b/src/test/kotlin/NodeTest.kt @@ -1,8 +1,6 @@ -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class NodeTest{ +class NodeTest { @Test fun `Node should be equal if values and keys are equal`() { val key = 15 @@ -25,6 +23,7 @@ class NodeTest{ assert(nodeOne != nodeSecond) } + @Test fun `Node should be equal if keys aren't equal`() { val keyOne = 15 @@ -61,4 +60,4 @@ class NodeTest{ assert(nodeOne != notNode) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/RBNodeTest.kt b/src/test/kotlin/RBNodeTest.kt index 25fb2e5..7b7b250 100644 --- a/src/test/kotlin/RBNodeTest.kt +++ b/src/test/kotlin/RBNodeTest.kt @@ -59,7 +59,6 @@ class RBNodeTest { assert(!(tree.getNode(3) as Node.RBNode).isOnLeft) } - // sibling tests @Test fun `sibling should return null if parent is null`() { @@ -85,7 +84,6 @@ class RBNodeTest { assert((tree.getNode(3) as Node.RBNode).sibling()?.value == "A") } - // hasRedChild tests @Test fun `should return false if has no child`() { @@ -125,4 +123,4 @@ class RBNodeTest { assert((tree.getNode(1) as Node.RBNode).hasRedChild()) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/RedBlackTreeTest.kt b/src/test/kotlin/RedBlackTreeTest.kt index b5851fd..fa3e514 100644 --- a/src/test/kotlin/RedBlackTreeTest.kt +++ b/src/test/kotlin/RedBlackTreeTest.kt @@ -117,9 +117,10 @@ class RedBlackTreeTest { val message = "Merge operation is defined only when attachable tree's keys is always bigger than base tree's keys" - val exception = assertFailsWith { - tree.merge(secondTree) - } + val exception = + assertFailsWith { + tree.merge(secondTree) + } assert(exception.message == message) } } @@ -264,7 +265,6 @@ class RedBlackTreeTest { */ @Nested inner class DeleteNodeTests { - @Test fun `should not find node and return`() { tree.add(1, "A") @@ -457,9 +457,19 @@ class RedBlackTreeTest { // └── (32 Red) └── (27 Red) // | ┌── (27 Red) └── (17 Black) // └── (17 Black) - val array = arrayOf( - 32, 81, 17, 90, 93, 43, 27, 53, 69, 62 - ) + val array = + arrayOf( + 32, + 81, + 17, + 90, + 93, + 43, + 27, + 53, + 69, + 62, + ) for (num in array) { tree.add(num, "$num") @@ -495,9 +505,19 @@ class RedBlackTreeTest { // | ┌── (43 Black) └── (27 Red) // └── (27 Red) └── (17 Black) // └── (17 Black) - val array = arrayOf( - 32, 81, 17, 90, 93, 43, 27, 53, 69, 62 - ) + val array = + arrayOf( + 32, + 81, + 17, + 90, + 93, + 43, + 27, + 53, + 69, + 62, + ) for (num in array) { tree.add(num, "$num") @@ -537,9 +557,18 @@ class RedBlackTreeTest { // | | └── (49 Red) └── (49 Red) // └── (42 Red) └── (42 Black) // └── (23 Black) - val array = arrayOf( - 60, 84, 23, 71, 93, 59, 68, 42, 49 - ) + val array = + arrayOf( + 60, + 84, + 23, + 71, + 93, + 59, + 68, + 42, + 49, + ) for (num in array) { tree.add(num, "$num") } @@ -585,9 +614,18 @@ class RedBlackTreeTest { // | ┌── (21 Red) (delete 28) | ┌── (21 Red) // └── (17 Black) └── (17 Black) // └── (1 Red) └── (1 Red) - val array = arrayOf( - 21, 69, 28, 17, 42, 1, 78, 88, 45, - ) + val array = + arrayOf( + 21, + 69, + 28, + 17, + 42, + 1, + 78, + 88, + 45, + ) for (num in array) { tree.add(num, "$num") }