From c27bd5144fd48c1a30d10f0d230fabb4fd0b3925 Mon Sep 17 00:00:00 2001 From: QuantumNovice <43876848+QuantumNovice@users.noreply.github.com> Date: Thu, 25 Jul 2019 23:38:24 +0500 Subject: [PATCH] in_static_equilibrium checks if a 2D static system is in equilibrium (#1062) * Add files via upload * Add files via upload * Create .a * Add files via upload * Add files via upload * Rename static_solver.py to in_static_equilibrium.py * Delete .a * Update in_static_equilibrium.py * Add files via upload * Add files via upload * Update in_static_equilibrium.py * Add files via upload * Add files via upload * Add files via upload * Add files via upload * pyTests added * Add files via upload * Delete red_black_tree.py * Add files via upload --- .../image_data/2D_problems.JPG | Bin 0 -> 58752 bytes .../image_data/2D_problems_1.JPG | Bin 0 -> 41392 bytes arithmetic_analysis/in_static_equilibrium.py | 89 ++ data_structures/binary_tree/red_black_tree.py | 1376 +++++++++-------- 4 files changed, 800 insertions(+), 665 deletions(-) create mode 100644 arithmetic_analysis/image_data/2D_problems.JPG create mode 100644 arithmetic_analysis/image_data/2D_problems_1.JPG create mode 100644 arithmetic_analysis/in_static_equilibrium.py diff --git a/arithmetic_analysis/image_data/2D_problems.JPG b/arithmetic_analysis/image_data/2D_problems.JPG new file mode 100644 index 0000000000000000000000000000000000000000..8887cf6416856496c3bb725e0e8ca3b517370cc0 GIT binary patch literal 58752 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 literal 0 HcmV?d00001 diff --git a/arithmetic_analysis/image_data/2D_problems_1.JPG b/arithmetic_analysis/image_data/2D_problems_1.JPG new file mode 100644 index 0000000000000000000000000000000000000000..aa9f4536201498e50d1d3d50f88143f700ed32b9 GIT binary patch literal 41392 zcmeFY2UJwewl2Dm&>*2fa%>b3BVfFd~v5+&y#8BuZ& zkk}v@x`{3E_W$qw_C9Z)bH_XTy*u6*ciewLjjmp+R#nZfW>w9eHNWd0*GmBTV;#DV|n9ssBS=71aa&Ff`i8e4aFXK4WeM>l?ROQ)As{1#3y z0l2xdfDpf+03a(5cQ&_huySX8X=P*QD95?q+|J2rXDP?2E2b`}?yP8KYp3GlYNg}z z^qGZ^gN2kOr#zJOrYu|<4s(WCxtp`XVXqwBq~UUR{;XXZoB!jsz#XWptEIKH_Cuw= zm%x6LyYu(HczJp8dx`Knx!MQ_Nl8fw2nq`b3-e)1@VR+Ax|_rK9NpOdUco~vHw#xg zXLma%N7g?oG=J&j;VyT_!^6%}+S=S&?4_lp7~e~C5lcQHAxm>Ub4w8+K1&f1O9?R{ zNnvZDmv{c&yrsoIT6gwvef4MCmKFk5udHBJj_z()G=v212>h+n|Et=>LiCSD|4aC> zGGfa}E4o^lyIVcPCg>l-$tNhmCoKHz{~BU`F;Nj&fqy9{EAR&x|DmJ*YkU6RLJKNu zX(4U#N8g=X|JJ;YmCOIP(SBtI#k$)5CLA{Zw+yyctjZA)5y=Yt#|Hm10)O7YY8>{x zf9NOnP1gS(uD=@jw?O_2uD{^=w-ETZ3jd{Df5G){A@FY%{!6?5H-qb+Hm;Q;*0A-$ zTCmr1fFc0G$0xwY0}a0_#C^Kk!B2@Z&m@FvksDq>wh=DQU%rkCc>ER3Ga+d;UUKPv5}8(#qP#*3RC|-NVxh?(GvC5*ijB5gC=3^foyq z_1*il+`RmPkA+2_imR$?YU}D78k;)1x_f&2`Uk#_jZaKYP5+pgU0y-1uKily*xW)N z93CB?oML{T{UH|)fcrPG{vp}F$VGvb>joYkE*|I)xo~cHVG}L|9{z11f?M~sK;|x# zcZ6RPQawn>sr*93CZdg^e(5@TlZIV%nFIZYXn#uf-xDn8|CMC_5bU3F%>pF2IM~6% zr2yoCGo>i5>>K~vChp9NPs(|=UbE?=YNDPm4vb`-%Z)qly`=KuWYWa{%G1k!`4J&S z0%KobK#SV8q&TQ_K;qv&45%3nzZ+{A-eqz(fzDERR_Xo);wy0C!xL>9#h^NUA(U5} zlAQ|WsHXL7&QrLp9a=YVXemx0i1VxYF~F4@^`BGeV8AWgX%n`_5x!$aZ!rgzkm{Kd z1Ujde2!i6c=qoC1zlk|jM|(D|F!MaEZ;FE5*xyOzlP=Qr8)|$a#BMDzK{kOiKKh6X z(E5+bsGCK0+!4xQgyG=Z3(Iq|7GQZ2|jOPh0K zpJdbfER-H2)V~ff0CsJQXB`V{O6}^-!rr?uPZoS7x|1f7>><~q|1XM4)G@I1!a8_G z9nwPs_FcYIB7HC`Wk8cc&`&0ma29;TIzo^^rKNyR{SCg}m*x~GI>$3_vaki;ccK{* zvM=tIxp80Soy$Sm<_3@(#RcS6i)G!iU0@auHkyF#sPA+V2S`=^lG>5)E3n%3mmPN( zVBE;0ZLV+fowpULPvp>HfNeLV;JNRR0`5mzj{vG?5~(R?ele%1w z1MFu2Hz`Da{NjH8mronhE(`ibH(qA4CBM1_UcC9=o8s1`v$j2D+4=!%}EP zzE$y-s^Ay`Fez3u%AhXEAqSqm^sDhjaP>6+9S_(L);fPcVuYdSBWpT>J0g>>0UQP9 z`fhL}gHs@EXUx|yR_)mYWUy+yj8=H)r@taQ**a&S198_PT|pE2c~ z^=ij|Cm2bbdclk&SS>(_AbNVFKaaAT&ABqh;KUDBLu_}1#4j%zQRY_Ux59g=k(#kZ zBqtI7cmtt@+S}LiyR+XLK$)pmT4B*peOStuJZPlYqci6I3xRhOA)TxCX#2 z(zA69d(X25g9rnKpRU^22k!{0F6pY7+(s{`xL*Si@?^Efc5XRy_TZCn^S80}O81?< zsGi-+CXEFnS?Y@%q&?ayQAmTaS7P*Igib$Gp7SecFXirn*xJX%_CW`vQ}kjEwk$;q`kj1lBn!d^DfruyOuhzWc#R z)Vjx#R^0Usi^Rj9x^jeUbkM6t>b}i(mWm6->{!LlcJQ&EelFihZJc`Lm*CFuS+6=l z&(|GyK3|b3p)imYOC9y~{?vN=v0dU{6pU7%CF!?c8=LMdV^vM(DOS0n<1guKalI5U zc^@$!mVYjAkGk(ZKTY8%x}xLG9-_wG2JMdFPOa4=m^*e%`|}pgO7c zAo&CBHtuWShx0YCF&8B(a1C(!A~nwO6t977!{?CD&ql`ZJfv7_~23!2->k@3Bj!x-ZU-p=eIWp#7 zIex`KVQROxa<42-;T6g)CisXQ9mF%#hG#e1Te9R;InolqAL(Z2#@aNNs$Q3XeChw2 zUxHjo3Og{hKD2=sIQg5jnS#|>-H|I&$GL^i!v|w!>jEUCo=Iw-e~2M_i!-5$^t%UT z%A(&N$~b}4;UqM~HWFQ(_#635u|sG?w*x{oG27iQv^QVW(;x78MRIue}9>tK4j$46k7_G7F>=M`AIxlGhgL_6*tgJek1 z{DZ*Q&nRc=Kv3{_D`4M)UfAnZGpwM6gLOyT)f%bUCe~nE&25_=Kg8oMRr?eiKA{)8 z=fF4YS(w637KiJm`4LI(cL`HAZsOivS_OXZjy0CKb)02IUC;|}2hfl%y!K*fNvzAT zO%M*Eg9s_~u`&hEHdaJQTfJ+>h!m9dPwe>5;5TH1(+XM7cu`!99b!g6F4+jfrl|`T zSkAywnPX?~rEzI3&%3X@#d#bv`bv|1=MN8B+)R=lQQ;i+MO-}R3<=Y~l9p7IiO((+VtzO;Io!PW!sZe>0a8KgxsOaE@X zf}bsJ4SKOh#+1;3CXMDp{yZkQn>O;-qeA`TXY0SLzw{~oKoLn&gM*(SKh!TFy@T4eHT>Jp6yK^ZR2U-Rqw4=AYOc9Z&% zMNFq%6~9F;y{=l0A(!|?xZY6Ng)J8Pc;gXS+>)&qLOH%O-5=P3>3>q!CEW!ESOGcv z@e4O&!^d%l9VQf$7f)W?=J*l&MAbfE{XML#vCoK0)*@{Tqkvr?0Qcu+H)~_EgBRC* zdM@8x1FP}z=Y;APDSntaG=+Ru+sL!xV71Rkj50gGNrIs3&w| z{D;jr7UPijL)3i~ilxa6FL-7)aQkgA>)zg__L>;wj2$wx=T9ib=EUXG%C7VylPXM zrfH()lO-7M9edjtnXs_G37sp>6$4Zlo-06q^&M*@vA!;wI@_WZ5*q5opA7Sj{p#!+ z1ugi4+^0QFce9ppR(uCBD`TWh zKg+&=NMdk|$nw);$QmWf9aa)c5TI!s;B`R|f!vLimaM)?4bGj`CDnQMRE_$y$^B&c z^~_$atXFTK`I;c>09#rIo#HR3YoI%E1NYa}IJvoJfG^!#yx))V;BE-*QLDuA_xf0O zH7T(&Zq=!K7oYH)oHB(wg8N_bORj}mq;12*<_R>2`$`NJXJV-o=pAv3LM?C$vjLa5 zd7hptq7Cs(Zd6t`F-K?~YO11WkU1#1SqUe}fESnYne5T;vVW(WH9l(IzpiHu0A#wUE0Q{+^e8>-u9 zJyvX=3~_fl+o=j}p_ys?Fe!moE9))9Q6J|h!7gpLt#!G~)Bq!t&*qnEG?|mutF5uO zyO;>g;$Ih}NjPvS zr_t&WK&fT8L{mj~gWzpGI9r6eSdR>kN=*+%!hcciyy}v)p2h6^#LpbcSdujOD}Lr ze_{d#b+fej$?v)}*V#^XXSbsdB2~f2wq{>ig~{b zp+*RyX)3K8XBSebsqK5kj5N{gb_3mnw<0ToX z9;BPO=5e=>OAec0HJ}<3&OF_tinujvnV>BrQd$#_esjir-htpnY$FaThWk{sicygL&r7N{`Rh%B!sECaUr?+5}Pa9~*NYTh=rgl`n@qAiMc_=RO_; zRl$Z)|LEXz+GWglA%hlHlktV)x&BZtm~EfYvvExvY%YJQq3O2J93Rp~Jub_<9KFl) zXgbL#)Psmc^$iz=ik5`(L84@b4nF#-W8So!rieWeAG#jxrc}b z42R6PGly3Y#OYeZ(&1jAQ-O;ia>`pk;7;=))Y!G`ycpi1ezuI^J=cxnIPWWDH#?Ec zCJR=?%oW>L+nq|7g2g@qINXZR4hEj#KcVbhbbZ|!`UTv=7OgadfQ-rbFmr{7*LZu zxw~JM|3*;iq0m=d=sRtEAB|m*pMCp{<3LBOs;P;0F0Mn`pa|H(*+q7y%ne(dHfeNt z_)z##tcD5dn@{!>f7xn{s*&}WDz%=t%b|tWQ%q?^MEiluH9!_H|KT@72PWYfxC810 zpZ%`921d1}u$z_XZ^>&QTz>ESMJ%9;4(N>yP{Y((Enm`h&L+>760s*564G-{KwoI~ z-%MoFCK{5O+mJfeAaY2}WJB#prxfJQ+os+UDbY7x|E53}_*7v#=4juLmF%aL|0FziDi(hV^xti-`kO*%Z`1qh9S}jUb+#2~+UR+M~6*7HV zA5D3s8W0t*L+W)6X=dZ(b(o_5)+>D zeKRmC3f>DJf|qA~slO-CB~KP@7!gvvDahBz3$-D~?JF02Di`bBzVqF?a1NvKe6-Li zyjyTfSnTLl`jejk(J=cBI$x%<1Dqci+Lfs!r1}d#8V8`Y8?ucJ?w3#Do04I z+|P#lb{b?wdWgMtysKtZ>YR6rmY2FwQm%4e7s`ZCuf_yZUqEK2rJt63RAFlw-)lR1 zxO#((z+0P4fi*5eM6ugnVq$LP0UYw>MasRBL5Gv(`3D!2OcW>r3TI0MMVtaX3uX;U zF=wKG;fZ-oQ0Om4T%#g8mvFP2-Ldh9B(qBtaLT}y8+h|yk_bXEdj_7>b($t#Tt4NM z@Y^+F&Vk|$T&_tQ6Y!u4H5|Tk1miP?@gfgaZi)ZGm0CUBHW1YW$$!G|s7$t46<{*a zccoL_#-x0!GFLFW|Mm2|H=Y{Kqk@jMD>P7jw^{#SYw>H6HIw^r(Mfqw6wr+}4X%-A zv|Yz;xKYM)VGH3LCgN8IpI0I43k~z26Mq!V>yoabE-Mc&x#q~*v|b!j;vm*gIuOh6 z3UzG%zfWJt6~Ob9K0Kb@$!o3pu>a^TTrPs`jdIbinxbql|0vet$hu+Mv{v^ChJTSb z`>py5Q@v%b^qa5F04E+<{`pjIH7dMwb~3nbQvG%x3XI zvDgVy+oW%4Q^YAM#??s&amsS9amT4QYq0oQEtRo!_4$NHmyMYhZwd0X>{x!K(hM~B zh0K?+Fr!|?FT+d?Cu-ilcN81Us#&0C|4F=O{ae{Q)qaC38!V01=)i6n^?Iq#+bT1s z4y@-zQ*`JqruH5$#bmAm8a^8>j7tJwI7lO9yh~i(TEE|J)ti1nTO_DEb4HegOx@jG zlYkW!_eRzkp??y9`M@Y&>HQvQy*tx)+H5l()m~yJ&ajv4^3ndJa(>3>*CLk9*CZE@BWaQa+f496j~f!`OOZOXlQ}Qs(SZj#(aVa58dJSC$%8 z(&>+rrWSkSOWO?hu5WWBNn-L59x88xz<1i}5#Yumit$>mfXqGuN4}Ch4Te5JH>$oI z)}-@=u6c7>h-?h@reS~F9ep#*uETWx!Mj0*L`w#EJw^pSPi+w-yeRwft|v#P3I?G zDC&~}j_H|-vN1c^0hn2fIaRpSDpOW}{43q(6$m-heguLM6k5K^z-P86|?(9X<= z4f?{NFgH*{EDQW&dx4>Gq!!=J$_<>rsysXA7r(5vc||)`a}5x6Ef>`3A2pO+eP1>5 zN6Er_8RZ;IqJV>N?7*@>vmNm>4!R9pY7Bc1+Oig~26f4}Xe)Q$!?C6Dnh$zqyt~tu zn%xc0A?xXuqP!H;gzQDh~v=z^n9}F zisf@sC*N|NzI^p;_YdmDLvIt))R?o~eS^1}1iE3eQiT}hF3&WUxGKKnY;*F2Q^B7- zaT@-5Q+Zrg+&&b+YFNE&yNMEyT_UA{33PoDHdJ?>=DnNbz3}|m;bsVRLB&RjKl_M) zsNWMA0~4i}Z`oik3r9}MZgSyv_;VQ;&N-M+VRZR(XhN6icO?b_+#`0rh1T`5`%{Na zt_PogIg&Er@vR8)l=j$v-Ns?G{lxLs?Hm)Mf|2*&)8`srC2<3#v97>N+;X^=GFmMB zNA0EfoM!#dQYuB*tl#}Da6>5!yzCW(kSgLkiCu2y@y(Y~je1WHE>7XJJEg(_$F$L7 z+*WSQ{KN!}**d`{2+Lsw2Wv4Na}CZ)e#%TpdNB=EjL_0NrNOuuO!_N)zh=rtb$0oE zfi)f9ZQ068ko~@GHkn@wbzXbCPA~r5t0^_7s<1<-dOFtMT_NX?jXkV7xU@KdV**O; z6HOV3Kio~OSEbJQqVglN{T|3^#7x&^IpC)40_mu(DrzlpW|oJRi>}gIa`2Wadph`Q z9rkpvkrc8V9wusvzktyuR<#}zD6q2$^M04|k_z(9EWV*tj;8`Ud4`oRI7`Ni2K5tp zbuG;fx{FTu^8<-oAEfa33A}e)dB(9bOiCTy36H^(yW5g-h%d>({J0Km5#*5Z zKb3&$GjC}`+MI6ZSFz}y_k|^r*Nznan8YOT{AZKuCRedfi|+Wwbb=i%G0cAi z>z#Wmod52(Dkw-KW(0fE`whx~&`i?9z>2s01w5jkp$@;L<%SR{efun<47|CCY}C=0 z$Mxs4_m^p(5?!SH?Xu8|zm`^NEr0lCy$qb=O(n2wqLTUjb7g^f4y0){RIxL)EF8=#rYc#+tf7wg-BY z2cI9wQGZrtV@puLIsi17w=#)RyVUZ#9JnSuL~F8kxxUwuNf`-#S#PsN+r%BK6R#|W zTnO2amgsHs+-~sJiixh5oK*y*y$BjBW+|_rvc%VbIrjYOUEYeoMS4J2)~WTCQlMN~ zBP8OAcT$#Z`P{BWE>FC=*mQv}xvVNFb|>!q15+{%nU9}BdDELB_FFittCsHBVEhJ5 zC0eAzAU1ip2SPHVoNMx!&2C49k4P5Dy(3KUbbJ~FiaL!bP+e9vcSiT;B)mm+9FW4N zcbSXIu)oEQ;_>bJ`Nc9EuS7_2i*pktAPRb~v(y*DT74f^GFZpkn3WpC%v2O1D>rl8 z&NT6h(arJ^3$p0KxQen1`~tg0RGb}c=hW5~D&uhZ^P0c;k6QrCd)bv%#qKu_5>V#m z=+4s@Cu!AO=c1=PO`mHG9xiMFS%`Ng+H;(qET`;SSdM!GMC_Z&Ht_^R{k z>Ny!3hjuqd;?Tz6A(PV6kssZ1ayA#qOD{M}Rgo|Uwf6B3kKP(SvLTyRKESZ=n24`> zV4Vlc64A$CW`tDLbS=DWrKutDxksJSGaO59K_brVgNCeN|QThPM+_fA>0T#0X`5jx6Ok^@b_Q)gAjx zK@6j-H+5{8#0Xoxc>OSvk5p zMY=6AZ_YS7ZCWkmCo?+pSqfLTr3FO7e*)(RLJPg{X{ltGxCg80+*@a9vl=HM*4!aQ zUzfwca)&0tCXDE^{Y-F}Q%N6_*KUd7hy3`x(gyVOt$C)>jDsr6 z&k`pY83VaX4$Hmqj+sgAFmo%M5g*aB81Qm!a#?T9=g560C!&K_BY{c^`(Zkn{wUxU z9609Y>8^z^BGfR(9(KNHYSHg*UDK3=g)QTnr78JGlAY`AbW**-L>2a7wtK8`woJ0F zT<#uHB!vBU0wF4;bXF>C?9P5X379`g)-k|)iPLlS(#O)T6EA{UMzVvT?Za* zN5>GbZQ_lB4zI}dg(KvPzE{{)!Cxx!w3hT5tiARcGAd+mmzp07l!1gT$V|$bugKHu z**nkn(S(1hEvT$ldSMB^vv$zONhod6>O+c{M;(9}OfiQmeDOj1yzWJdM#+1%JVh73 zVj`_(Bk7lt2G~(R`T25qEJEE4X-W=f?9(=!x1W_R9bK5~C0D0}trh|9?reYu>rpmX zC$~jHw;8jVM!Fx34Mk8MD}%lCt=Lh#(GonNic6RutbPeY%rLORvd$OOOl+{XZsrY8nqgS z3%_UOR_+?#Gb6+c)LXJVu8_c_BKd5(Ok`1Hsrf?*2HOVF_kGH4Rl0)-J2(r;=6PPD zYp1&%7H4@Lj~>mNs#m5PsCj%;K9r?Udhy7D_1>Y%0|`l1OPqy3Xn1ZVf&;d~s6p06 z8{-6Xsx=$_HEjFA!n|R(qhmv8wyp@sZ3{q2#6j;}D5HAwoU)BKPg3hrhu9|_moWiI@G{o}#{a)@`zT(SGKQ_1P(&({)X_&;#5K z>NyL1Y=Xk0lJ48KxKC-@yFBf$)TK74?$81=6ST&b}46e^wPYuRTQ zrzDBz8Bz{ybhmY7IF=?8&|ntZi?%5kWbz{Tx&Rz>k%Yi3T9_`ET$*JsopFx%s8`30 z5HBV!<}_<#?yez&0Ie9xALP%DL83Twdhq^5gwn*tz024D!q0VT@w@bF-M9AU4n7_K zZf7prG#2x&3q|r-pPz)G1w!_0l*U9arSI*oP;gm!>Jf1?WgW-H^H_2cq1)cpLpRRSfKvmWK`0$&u6r6PI(-2~eynev zJaX3`JD9magoL#|1Ei%X0;Lbf8uke>!z-!XpK8_By8Q2qiuP4CSQ_8>^^^98P|&w$ z4NOQWrr@zaXb2}RX8{nV`D~TtKJ#|>mHbwlP$fnOCMsfJhkBaus5rvXNov*QP$`|9 zZOGNj@@O;tc#*7TKjMWeku0pt&g;z0fU7QQh`BueU@55*K9?{RD6_>?K|0Z*ftKio z&?A(Wo$$A%jg#BnCVVaN1K8{y_Wz`alyW@6*?Xp&h%sE@j24ZL5MLm~l(N_^7O`sKa^tuG#wTo4$A1bC<}AO9HUix*Pm-1=J1}z0G-XjmkF9^nyp__`50kS}^roA6rGj6;PWnCWwU1>Cid04s?G%ULL_0l2 zdBxWf%h^vGgyvVb)z|A8h->!Q-2|?fEI|ABhP4z}y7g}0u+m7<%h@@s$1@rulv*!y zYsw(%lSgy(J^A$pmH{GPsH`ks2;ayO4PkQ&B2Zx7&cTEtHJA}H7^nQZ1-^wE7522f zv#{sp*p<3oF|p8bbV%<0dD)k2uytbM$xGs$;buC9&E5 zR?yC;S+yy2yb4vohu77jr_TkDt?eP5hRmewYBJ4JH6fos@nSC*G3+Vtf`FPE=c7q^0)~w}kkQ=4%Ou zT)!G2r!?$=v&^XFD{}bo6=i%_lV_gjhy85Rs)fVhw7?sEm&>*7OV58|-C5q#G1Dty zWhxQC18d)dA7rM_|}n+FQbwVO=pEK9_;sFrNE-9}}__S7`Bh2FvS zde!{Gvvsc=9iED^Tuf z%Nc~lJ`sUvK&dI(UA|{|$KU;$acJ=Xo$;d&nmqU&slkF!Lf2K~BgC4kM&12rOz$9{ zHHJJMS4|{*Zl)wmMj%a+)K49~6m3eO?>~_$2=CLZo2ca&ed-9yJD40_olEWj&XzPm zBkq5M9{x*044?zP7))05xO~Le%`(gP^>Xbc>#p7E-5Syu#~ChPFVl!ypln9DGk_y5 z$SYor7%&r9g;>RC(5swzeKF56&giJfUab$)&rQ*@@V?EF>#6y~GKdINj~6Gyz*!&m zONjZK(6`sgo&7n1R;-`C>=ELzbZdfAf9fw@Fkhyx857J~O}d{yN_;WbLX_|FOGVka zIg;h8NDsWP+i;)Q-juk;@3FuwV|aLv1TFb@Z@%x{iHsgxy$>la``=0dBsW|Z+M29L z3iNK|n*~Wv;-ju&QndBU6JFL;)orrU3{(z(GBQg8(Cx84Ls=`t5WwC?Y|MB@y`m@% zepq|bHXd{SiN0i!B48DH#dHlcOB{%8SJ_Qz_mH^G%rz<84>jPpBkMjZ)VHjW_;t$R z-QL~0Fx8c7K$k~}m~-Tq3P;Qw)7R5v6C^9(QSKnT8-(Af1pY3P)f6R@bmpo7Os(lQ zf39wi)xH`deyfwLpGSss{3{S@f&%ww5OKyFLzoc?&5a+rX?ZQG>%(R;W$1i>!f7Ty zk@;A^+shi%c9|lm9fpOl1G)5_t*~c5W?pc^&w_QXfli5>YhZ*?;~MCAhK-GEhB-Ld z0AWLfOOb97D+WBk9fLCMhAD9@s!#WQ~C6Q8izuTul`yt_H|y0CARN>oC} zlKTn}X#@=>b-5z!3N$GSp*Q|sxK-!#wsb0NR>|wPmb0Kx`X{CYQex9KjUxtkx#9y9yk;4*WCew9%8Ij&j#hz;k6o0=8cq7%xE zNGC!oG;8W)~#`~ zV+&t#NYg0{yU89reErP-MzOIigWT?qrq;1nj{G3if;wevPj9e^;vnT2YGq6*^e)6E zc3Ot3>=|lA9yPDQ;y=e=>ngfa-+Y;Ip)c3*djEo`>?uk8%@2j2ld^x#>wE7{xc_cD zZPu``^H}ryI7Q!*G7ouA2MeFD$tfImBAp;5{igMuGDg&u%gU)p6c}XOHh$VkJP%!w z`Cu4w>yfB>qp`*VQVaNEw~tLIg2SWOVXDg5uJ{GdySxb1x1NNmI$umbBy6V5)z<|L z;RlV;Ek*k8xUfEFKD(LlRyx^?UgZm(vn}r0pr1~c0jMV)Y=j`6EHBs9YtlRBV`fVl zph%&Ym!w;&T$gc4f8FDeHdN$)3*kR0tG+_4zu1+@Ua!(~B$RqC8%4YJ$~Oo336CP3 z!er7aXH(j8U;IPC%&ZzGn4M;mQDWltcY}uduq{x~7TvGNUIr;D;m@a_G5}87BOmFb z(Y*-nTXG6l6K^cCuU7u0lCsaB5V2gP7U=UjDF!D!?NpBP=~e%T=%qx!`x6}o{V|y>7#OV2V zDFM(#+Cc?Yv#47*J|du~_0WB%)p)~mKybP&ReR^djyWplsify#~&N?PY~s&Q0JWuahq0!7S|@wZ0@Y zFY2lupVWu zxMGiVcz3M-nn(ZDIB9q|V&+cNe2C3p_q}+bV9Q%Gy$dc=O^aa?Q*Er5skLCR?IGVn ziBrBwk65W$igI${!Nhr;w4dA~-v^u0uBNI!bu=Yjth0v^y5Gz<4CzJB`J@M*1a=kb z&&@tGbcrKO5gRTCwiQ_Jp-flmL$D->1twJM?nT0OfbBVX^|?&SA$xkLOl1u?{gzvw z*q+B@Ia;-7EeA}^oM@{>N~0c?iho(m=pQO>n$7gA1_!&2R|D5Tv`1+sHdF!|tiDlXmsO)1Zq*px zmB}S-K3Q`~wiZ`l**Ms-MkMPFxs7-76RhAc1AA5W%EET1ET>)x_U$t^sA&83;%gmE z(DwreY>l7CU-%l6%o|HG^4_U!IE;oY58M`++t>9%)0$$un9DybYeaZz8_XE%^PVJ$ zK6&voKGoI(%a8!QvMBQrs|(JXng39t&fQBKJY`#!V1!-Z1HWtd7S3$F_(iHTM%AnHVBdEmgv|i;R;h9yf^CB@p@~P~wGY=WqlI z3&zd^$;{ZB>S~u-QmQJgG<6`ApaJ0@@xxiCCm~#E3$3oK881s&)Tj-c!r!E#2+_ij zaWiJnsxHk~QvaC5fx&NZMk zc5IwAn+7Y`8*V&#N2A`Z-my~FcT4eB-%VbZ?9SX6e3n2>+>&)dgmFHmT^nt4!AI)z zYfO#!J*-q#vTA_jk-}}&hh0z38@>#W1rF>jo5q_O7sl$A&~DHHZ5??yfL13oS?KnXW(p4pLsT`?!$P=l}?=l=O*wW zLIXN+g`kSZhZz<+7@Z=F0gO`(<5W`qd4z6vcV(QOpDmS6dki-zcv_}D(^TC(!Ge! zqLSZ{UOW@`IT5X{jYIE^%~Cfec(!y3GKVwKY$@X&T~!S+M0WRa4%yre-b{C zFmd2B0*b|WYVe%Yqia5>y&8S_iVUM>zvRz~B?*+*zq}GbS%31yk_Z?cZf!e}2rv%b zW^;BX_=%J<`*}L#VyeZLQEVbNYxtmEn1A}667FYY(7ei$|8xB#<~zm;rUM1~a!XRQ zeAvKVKQ9YWoK<<+u=*A7Oxo1BcD2Vuh-bamD7RUm`i^d$gk{{%GhNfAtY{Vr^s^?% z*j#(COVNNW^U?t`qO4eg;3Y5wUrO6oA;}lSqX+`~e6q;fOf!_ROY*Gd2bUFxQJ#_fzR4rT(~eJg$CFVlf~o z4@cs1BsfH(Y>+mQJORAX!>7g@NU#f)4Y$yt}XXrWSf< z8Etj<`bf;F{BffUOVoU~J?mTRqgH6oN58(FY|BO~K%f_z=-w=J9J5TvB3TOTg+p{r z=GJMyd(^C0|G-b2E8e6Sm)D67UVWp4^0onCpFgoHQxy|!ITKcxeI7fyFr(Hl zXWGkrfvQ^CupPq({Ev4)$)pu;aU7;{ZwokKg{`{#$^%0Q-U_mG@}S9bu7Kcm9}!^5fWwD@qR^3}@3%`&Fm z#9X#@Ix@*O=7B@^`^tR$_Ofg~88Z6)ZP|>12Y-@(Af(P<6*zv?!3zt`6gmmuK(zS4 zdbx1d_e0WR>yps90c01_llx*>1jF{zw8YYNgHnPjvntFdF-taHQaovA{KY@)eEgG% z9~JzV(^u-_O+l5KWf^f%+hdnwr0f_RxdI7;(*J1o)_EdOtE70!1}xcB&N3~X-ZO?= zi1c;IpV49i^E%GsulQT(U%_aOD`=)D+`Al}vN84_@lAieDE0wRrUbKnsA%NwdWgk5 zZTN-i&EZ0j>lY5AWmn%->9edPvHje*5p;Sk|48yy66Tyb;Ll<`8>F5j44 zTsl07=3Q*d`}2(Esy^BD%U4mtI^83=7YG63(WHqh*m-m_ht%F5#w=H zyJ}e<<;W*s4f<}H>O8=n zPVweGCmsnz6YTsad#|OrJB!dG!>ik?DWd}G*TqKA4z#}7EpTCCW2OQz`BSf9PvtMz zRstB{)jhLVOOoP3)i#r*m1cn3_-n6u+w%cN%r)=~j8uSVj+)#?W|3_NkhT$EQuHot zO1DxI)nB2Fhn=^1afcppmo>a0 zLeTRE|B*^p0!{PFS0K!Uqp>!qI>2feVv$G23z=?{Ze4)oZ}NP=M7qhPb_v}2IrE*PwOiV_tB3dL z^U;u3_wif_vc>scB<^(fo^A6a3}N)l*MAM>S-mJ2EYi_BWjK0B1RTpZWjfz4_Cd^TI2l z0FDqSRwJ%)0@j^(dZ((hY zf`9UdU&6btZV6x>?guOrp9@aaRm`+aox9MfpUpaaVslWlfsdo zNtuev_~+4cT=uKyhf~2H;e*Khw5&)hi;%w^HI+o&b-%#CfChJM-3?|nBrNRBzD@_+ z1kUb89mEG`%1zWVOJJ!+FiDtA=b@;s1aH7pw*=|xDQlkPDWitAL+ptAFg?OONe3-| z!7+WsF!r*+ROVcArD7Q^iev!O>OnCQn*FWn0M1@xTF8D zXkY1OVop4kfimp;?D%f$)4-cN1O6=bXUXe*rV-*2X`vG*o@KN%yK5O~l5Xj{+Rp8v zkm|jPPYx{1XsWcf91jF)+qH+$_~{sxLS!g@WXCF6fu;68aka&MDl^KYa|fxORN!Mw z3*pf#DaDKA9s$%_;+`n`ceP`%^aw;g_Z*!K9qX>F?=L8XcG z8Wj+ws`QQ`O{618S3v26UIRgpULqhM9i&SWsnUB90Vxui1VS%?gc=}(GvDvqXYcjC zYhC+WYp->#z0W!8N3JV#%$Yey#(3r!&$yraxrZ$=@=n<~r`fpYN>jkC4Z*ycuMS4$ z!b#Wf{Pvy$H%G?4Z~Uhe^8ZXHC)z{4sX<1tz`G$4A&ZmsMRq2lug_k##E7S+7ERY5 zL&mAIGF6PHFx_eOo$#npQ>bn=T(=F&cb)~NQFOC@KCJIJqwi$8Jwi;()K8)Z6+>Pl zaQJNXrubO|IoJ9JIOzIXy&GH!ismm+U09~!*)aCzV8n%D-Fv0D*J3?vWkhpq_kB^w zA!`1icd83(Jhsd?K*AG0$XZ5Cy#P7|!yI1crwFjpqAF+$7eIH-Y`%T5#EHo0xtDKNjb#1JB-a-JIll z*jY>rVLKkNKhTP86wE_`-%7Qnew4`z}mHO zI}Xy2&hk&w03Hfo@(39*K~|f+;qDc#zAe4N?LuKgIr0m$&FA5s)?K~hl14cF7rlWgU!tE zIkOlSS#REI8j|vP0e3Om=~rNu zq0;gLVX4EBmkzR-@d9JD0fweOd+?a5n!6Hv+n8-S-fKSn1IsqDr55cL*F{@67l0M| z1*rbjSz!4hvvsZ7GIw*=vDKVk9UDznzEzv3UMB}Hu$~YinZ`XCTnwG7uXC-@o*#6L zaC`plyVz391qd48P>xaQl=Qd&g*MJdqM~yJOHqnkh<%lPU(KbJ4#KUX)q?@ReHjoj zp(X|r_)5xf$X>ZyF#7qsAATu1xDDF(C6U+Ny3FPwv3>8p`J06ioN6F1p1yv(Hq-4` zHF;1+>@;+5RkETyUffe+c~{~21;`^Y>tW?$~J&1$B0D4J$=YZ#WMexq%3&}P*Mc^`CihzgSE z|A^V`LdKY1ugASYMQ1)@SH>A_)~zlLM}%4HBx}>hv1PbD)b^>UMr9VPmY+Su2|cO) zS#eP1X~Wxm6dV4mJG5>ssOKS36VSNAJwJR>o=?bJDOt~^6r~h?ia9*}?)a{oOJ2lL zAvSQ6u)8bvKue;c()>s5xy4dZ5dQIu2*!B!x%_OzE6;(?93se#Sxo4pF- zwU}Y-O6!Y~V}1B?@$5?FPKr*H9vvsw9W^5PRB$H9FVj~GeN&|F0z~F)euabQ%#_&e zKH08c)S>-81TIBdg0curVetxmC2%-v+KFNcdmc2G23jUa^_A8KLwQ`G-;gqB>8FO&}X7Idx<}YGAQgxo7 z%_pk2lObz!?P)_4Q(|e8vI6m@2FM*;y^DXBO%0nJA-o?n5ApYRDtfNvAMoIz6NFWr zzrv+B$2t*b$cQmio`A|@V2{T|OV#*yU+m+jxS6bf$;1dBl7M>~VDRfcYu#T|7PHB5 z5S@lmon-Z`h#-sF_+U=G^U=Ab#DnEa7H=_gxMNgY3s!EEEJX8_7|Roh4r`|0Es z2=)y9JdpD5<>q(}rBRlv}op*}><6IO4rFQ_n1(vbYZi;AtU0FcCH$yGo4krmuROC zV{f1!R;~=|Kp<*Z!I0{r(_JgACl;_PUwBHPOjPcq^ zOq<3qzJV+wb*6dnUiIh0@`d*kCbfr1*CS@WNTlQmnVioOMArkUf)dGa@T z>gfCMBv#BlZUiEXu@B%MXVVCH{<6?D<3$*~V;U@$XiqpZ&G!0NksR^FEGJtkEa(F%1v27gw&wS7A)cih`^FE8 zH&-20-nl;TO=RzyrI42b_2HhTm|w#Uphm(!BjympQ~WH zJA2GW)|AL(W+rRScV3AH5;@dqtvB#;nEDW?yl1hw&#}%|?8M!*yz|NjYCL*-cLbx9 zU3wa}vVJ0P`(|yE^41=cf@_d^Q={qT>YFo0&FdY`vaoV@W2$h}IgkSp(R>?ND?+%j zzikc+)`q9utG^j3xqoF2J$0v_CnF|px^c8wuRL(j-NzPZembsG8#Q_P#rw)s_b=A3 zUu5cor1yMtuAag>)lITP>`=2U{?8k*^c7K!ZyB9D9`$^=hT_sW)+dUS9FZ)Ku8cfw z%{&vF!c`6-%jP--bQj-)=Y3vWZ=TaH!iBN8m0{-4fH zh593$oo^-K`%xg((ig=sfTuAZpt<72GJd8! zeB$*)3{72gca4cBKKtQVW91t^hN8uhw?|UWUtb9%ExSJ*qR68ldKZ7HO=f`#xGF^J zW1=%!Mu}{_jH1tyKnSi}+>n%)odErOZ>DS)emNJJk ziq!d@2lPHRadMREl~BLY!w7-t8fjF$R59`L^+ysE^0TalgfkJMBmllZw338(%dujY z6MDD{61H4UhK*vJSgMP8N>(HYN2fNSheK9f*QLHF$nd>DYRacQaj`~xhf&zP;;=+R1D)2+MxbJu7Xd8tSh z-6v>zy!?<+wXo}f{f4K>pI=AohkvWj(xq@*an2(;bJLGGTP^p2^#B1fDC5UjoP!?D zQ|zl~6uH0unMq64087kGOZqoUYzajZ#A$Qb*32a>dcsI!ZDDnOck9h&+i8!p+tgLR zb>1^w4t^Aq1;}HN^`#NYtuT3u!|AU|)$@!U9Bdql?TdSiFMn6d&aQLo&VV=i_BteE zExO>;J}{KcTaiq8)1RUdqF=v@Cs)E6Z4p7YDL%9>XQ@bQ$~I<4ou(X#x66`u?u#~V zrFa>I-WTQ)}-VI~P>lgpv0-{1!jEM{UU5UmP+YIeLc;YjS@n;%A1A z0pO#1C)_XI1(TicA(L)5Ew)&$9c5rfB5sVY2HZxdB`{aCpK!Vf-YY_`Spb=g$iBKX z2dMjmC9cE0)MPzHHBYIj?p?G`7+H=)pJe;Oo-&mKQieKDF~{f8aOa3own?qo2*ls@ zPEwYbzf~jSvaHxHe{g8mgRTL#;F~o=DQZnne<=%0&Xw`^*p!Pr7VK? z))OxK7@At1Iz++9vQ_ZL(ZMcp#ALhdo|^oRI@SRHZAB zn!T|Y>(K2jcg=}fGwr}=Gtj9cf_SA+FA?35L$%w7QpVY#UXnt`^UX;5Oiu)&mSHvq zF?j0HZl5{shU->m^X?a*r-p?Zbncy)?{AjkpEQ16e8Y6BnM}0`2gg{h>HmNVFD9Y_ zsCK-vH8ZUxgFg4NQThGWFF_I$GLS>Do@^w?kAEe`JbV7eqfH2ZxQ778DH`SyhRd08 z;JgTIgSrb>-*NLA;X@JhCe9nSmBKz3h3=5l@ z$6H~+3q8XnIXIgxp6*ljIka4+RKGjX%NPl^1M+t+SD48X6&MNZcw{XPd>(nOP=6J- z(dJ=Mp=kKkFbzmxBrRAJ5Z9ZO(cR*SsNI6s%%g9?i$L#>R)+7?Za4p#z-2fW$48E@Q~0}=C< z-47)z-cn!%63mKzLfNr;7C1Xt@lc7~WOC=RW}4Taa=6XFiK@g#5*Jb0QR7})=sX3M z#iz>{dkp7{LKywbyQXMGOUm#gw)y%M=96nyvT?p#)cQHpSx}`#J{U*f1WR4`r%jgF z@_aviZ}KDNZl?x*s|HT+eY7EW?PTi}s50hfW>JLDA)LY|cx@ZvP&}|I+m!r_f*KTn z@gcr(O+`_^_LsK-W*7ae6lrS?&hDm#S@$Lwn|!0`eq}irN9Cfx)aN%_J1S#^)kBFz zR{B&wf=~ju-QM}f$#1^m8m?jwNn$7{J{^Kyys+W=L3V#hh-SlF6xUH^+bS`6C?MU% za{Ff3pjMdS*;8cq$V#5~FGL+^*BDH37_CJGeG$?XNHKA!q1S0#Uiw*mom;EQl(+f? zGmX#9`|WEk!>U70xWumc2N<#*J|Jx6!>DQo)0m7`&MPcS=Q3nENonJGryw!c9`gdB zxG2O3eTbEhEN`o6rfX&Wkw@ih-PEvu`yE=Eo|F!rPJ(YCv(ToN#Ds?nHM?R}Q0CFz zrSsG4A-cc3>b{_pb-6yMFj-|5yY&RIp>@VZw_()HA z7;wt_Y7@7=24A6*^f$UBamSnDIyEyJA{+|BdA=;i%QR22KKr>{QAsg*Js@sT=dsZv zuizpI*O*gU3njBs+4t)q{G*?VCs56^3)JDB5DNS~!U$rUyQ=Zbv2yv_y;)UL@K}({ z_ySqyIk=lZAKjgTn8_FS^2?p3ukBY&AH3Ya!F^19->9zW4Uo}qrrAWun7CtA^hYc4Qpp6i`g&@Ik}%06g1K-8 z+e!n7rald{ZTq)07ypH21?6_}4glY?z*L&caB|E*=o;?h>gZwiHO*?R z-=g9vKmc=dIiqT1ykr@X_a~;_Uo!W6i@L#=aACWBe)4fpdeNA)x)kThp;yJ$N+LGi z%IvJiu^Y2l_k5AGK6)rfkGi@OZI7Q<7x! z8Oiw+t#tK)dwbFUGg(US+!{_xbi?EK zIKsa_|L2gRH!Bw`z>-qp#y66a#H;Z1w_ap$sdE)VDn8dZ4T-AmE=+xEd#zTh@o z2u!fVpE~RSf@XD+Aodp4s%9hYjA6^TX8#Ki@Q_w*zHe2sF^TqcEtZIypY^(um{B5I z6>{>-U(!e#avJ(`+e_b&GskCmbZ;gc9z643Fgfk$#Y(PlkwAuiWKQIKKZAJnwA%h8)=~}vpwC>6mROlP=nChX>*1R zKym9$sT#>g(V?*w)Kg=Q1(MWh4CFaY-TMVSx@Jc%j`cHjN zPR#G8BImT$CYksz-q3%vF7*2<9Tq42Gb3mFu=Oqrb;J<(H`sEugZ%CgfSgl$0X*;6CsjG{P3culUc;3BNyC*d_wbiTjntNW zYWJlfG0Aj;t_0%yG&s7?H zdN_QDxcWsbGS+UBgMLg@;5U)zXl(&bZBBjeCsw&6ZqzOIrQDT83uTI-JB<#w4Dm?% z3W9)R4QrMxulAYNBZXcVr_pCqg|_N}*r@Os%h@dN6bZ1olAlmGl(oReX2_pDS*;MI zR>5s&7GC%~(P(WE9&Ymd%r^{n7;I5}o{P^UGzr&#M(Y-lyLpUboCpQ}b93jg1@2Qu z07P7Dw!3jNMFSKvd+fQEBKBh*%7ZCr86yi*@28*0uyzvNrZrJ=wLKaWwDh>6@7jAm zs;e0z7ap)UC8Aco%wVw1!mXBRq{>9*B3TmqIDh%9=-AZNAQ+yAFLO8abBc?^aw=$C zrGK*;r{l`@c%fJ)7-oE6zXp|Eh~??>)^fKwimYIB+IP0`eNL_LJi|~)Px5wx@hrZB z$A1b?DEUMTE(Ve% z=Z9<@?xx(xdWZ06 zonS+9coO|rnMtt*Q7vk`YAxc%XvLwlNi=uuOS^iHr%V}{-L6A)+nHzeC%0B?$bAEg zK6aaSX4w)Q#ZGNx9c)?)TEAg`m-~vO)YCNX{psTy|74^7p;Ic;~b9ttG$cmg=u4b-{Id)tx13F@KsidW6H$6Ps5|i`gRX>CpYols~NONe&q8YTdV7Itcoy0bj?@FRHjPC zw_)R8HApz;uYQZbm8x`RNsdD;WVcxYY7c8BrDfK4U+kq*XXaC6E2_Gep(J4L1&Uqw zmxlU^ibUZHXCDMxZ{upwq_-H3cqRIj5`v^>{-pi$XDy(A==daWp%S0$WOIJfcJQ*Y zU~3f~+_q;_n|-_r#0kQh?g2rT3WLqY96%`I4bNEM=$9u7i18z zY{>lkJoZN!@xMBL7{HI2R_D&YWtZd@pBU7)rPjn3wtQfi4Hd%zY&(;YYef+^qkh$# z#k0Be40nWNxd@L{6=2DtYq1qu0PsdxkenzIulV{0-+4}v*>_*&rOEVh!}~%jSHzkWX{Cl;`Ru<(i$yCq$NUfR8(m zBNIwy^E~`$zWw$2L-)Q^jtwPOFeTJMJPI%U`WQ18P?ZY=jxVcDE!9za9qL9$SKXZh zhPn9A|EW`|&zKVb+(fizfbaVGYTqx8!XO^Nmh%l$W>TWc+V4%nAaf$s&cuPw9<=5G zddKD=cK%KQmxgQ}Xs}X*DP|zDak_ki=PEr0_7q~U*<(^8SnJh7eGa6BA7~wpf7&;7 zu(Z5^!SZ*(h0n7spg**Z5(C0D&xg~g2SvJKaSN3d?#P)pz<71@h+X7FjK-Be58(Gq zk5smRVpYUgAUidU=_RU@Uj}3wCu0SuX-|F9q`w zFK=AK49n?=Nu3=Mp1~J3&^vN~`IfcsDYZmZFfw-zm~awqcYO;vmU?O`EX3g~wLhEI zgK_F<>FXY0sC{>d24Y$I20_jZ^8Q!4C-i$-!Th!7iYUg28<=vuf%x{gQ^I4wWfY($ zjt|+jFfsg-ucA$$*LwU?Zz-}8gLsVP3CHVrgQJS#*u7S&*blc!Uw!c7Tc$WbwS;d6 z-UURTTeB}fK^hmJp2>;HAErS(krr&hDx4d-2+k*C^M6%04CE06;YoB;E`%s=x%c#A==Yc5ng0 zUxE+>;Z;6Ggy2&cfk^xU#AtG$O-eS^3?%YA43Gz;Y=JR7I&a(jkCi&7Bl_P^jZ#0) z)_VF$v?gH^#h&jNHkCV4;mtAuC~bu8?9BI0sz(nbT()BN<#ObOyX)y$$8yKy7gxk~ z7%-D2PEmt#dMHYc-5ztn`ud*&*2#POo{juDsmiQLhipGJc)A?E*#+x5_L(f?xKx}iv+1b%)CDl(A8?3UV zsMeQ+cD5MAhh;Ydq#xSF;cU57fa0ZI+o=1c?D0G0`^h|~2;m}?RiC!Lh?1`{svzRZ z9fKBNKVr}esvtdv%Spc`xhO6`zQHl|h{;coG~E=yQMgvLqmnX?@s^N(>o=Jy9}F;F z$)~{DwQu>4zAb%^2_l-4irU>Qs7rcjyJ$;+o%sS3 zS$RsDewNOp$(>R`m3ITSA={UIU&&v?ubx=QZ#{VB{`? zk(rvzL4k{mVjc@@>q8rc8J>W4|CNx~65+0c)bN`H))w`ygfhd=QtaQ~^nSn31bQ&=jBDQolYqnBN_%gydGe z-KwYN?8gZfXut6D0L&>Bu#tCSEH*nHxQ67Hy%(S(cZA#l7#R3*DH|W6HKSM3UiRW$ z3}xN32tSjAqD0i|%%n#2YwN!}JR5i#@_<^4&_e5zr`Y&vyzEfj#Q#qF^(WR~K;alM zA0!&vY#e^do_Atw)^@!y!?%0`P?$Q*zW}|f+OzCtc`EOVSlM@7_lvWCFO{5F@l*sY ztsTBCw&455l;M3g%rUUm)3uziN&U-Cd(Q~AQXzZhc83Ts9QWVac07E1Y%EfS`b;_U z(t0r-M+h)2)Ob?C_tt!mA@T21KC#ruj#Hawepi>o1t_^;D`b|$Ks-cHpd#?LPi>j& za(PQsu=JvJ4%O+uaH&Dsc!ks2HR)wAA3(_uWOmSs<+!o0cWcnSl;%e@_4>~Pb{}L| zv!16@~5NEeFbeMYnk%PUbgvR{X7hkOEsL6;5CftG@s}-mX(y?%#>dG z2%E{>!el1ln@!PsJBCbry!f*Fv+)^zG$u7m+;>U@^9>55=((Tx_e|r1qwaiq@DHI2 ze+#MD9|?o)-Rb~9UPFsh9d18%-`y^u6Pg#mYPhuW{7ypQlO+{HCFx5sSL2i!S^wjU zB$KHV-S*Xcca~qe*64k{)1DU}+B>S*o_Fc*z2t6wiG9~qUTL`a_U=)@M^k81ICF(? zM_N|3H!9O@-1zJDbT93QkjokM4{^H2O_b{uUMG1HTjpNjI#|)wTA#Fnp-*-@15R`T zwQirNOc3SzeulQpd}>$GH34WxM~#FEhRKoBs( z=&H6liqao-yR-u5MJeSUAOVC7TjM7#-5aByq0(H6IkjvNiHUi}4p#pB!vkxTKYPNk zn0mBp!yP%mRmGKEQlOijZoD41&B4oLs}HSzpOkb|Z{Dr}Nh*CRcPs{HK~zkC?z^`m*``eXUo zsYlL2hMxz6G%bbx(lfp%1M0PtDHeRt9U$Ie@A6NF+kB6^XaHg+7a1X|qU|7e>xaWg zD>JM=6apB$H|$h)}w3+FYXhn%^b1$?{>UBxfgmAF06 zp>L)$V8NEjYq~!V!y-{(M=w{%bn}ZxB;<{=yMGMwL>{QbiN7*Papz4S`zD3Z2N8o_ z_~~dI>`@VHKG;beh{-Xxj#<}z6(1YqRiUYeiml45G3^>BGw7Z-Jse#nIS(U}Aw(^J z$Tl4s?-Yd4{}!$om!wPifIQ#{>g2)ehO1188KC<;qAkcnr#G9KZKXTzM=sywcr2b+ zxzbd}kny@`TEzSWA~bMiY(*6W#@2saUE2Qm>9-~OxSSUrqH|+($}hg>d|p@GP_ujC zd`zfg036JRf0Y#T=M4>kW7FMG*m5zhp3t%lJ7DJfSmE8-wx74n}5&E{+?^hH5MuTwz{&|SA@TA{6}hN;RGG z`s%R{69cMGr%Cw#(Wbx>rNrXDwN3>g$}~dI1&qmnO;rA81B?O<*mwTqfBcyPo&Ml8 zqCb`X6 zzY3lQe8`9Uzu)Xpm0RQ)WPd0FwpPs*7JWB&V0>0P zArheXc*4_6!_(8~kqY^z+?XUzm#d_H#})f#KOCEt3e(vit1a&up3f$|c-Sv_8a476 zBNuPw%RVYDuG>SXM7yUx0_$WYecu?EST#c&TCKbIhF+C;rRiTJz+bzn1~sXaW~cXU z3l*q<9178O@LhJKwP>($ab1O$B*}#U_HnBj*_n(GKZQq?uig@n_1p^c9)^x2J;?Wi z#d>7RcnC&T+^to*w&8+Vh972cXB-`uIY6lIO>n0GVwk=0HejmheVYCykemYe$=nSG zkV$*sNTErLF!;odFf4nQ9OekFRy@n(opX9Ezc&tHb=}xSE6{DPV(I%b21`1Qd^ATz zB1rd4T;o{pb({y0RS^HE*`+^Tx|)LuqS|-fd6xmQf(+YeN$*5;g6-e0^YfnzF++ zzV4~vBlmLXUf`NqNDwu?w$D=99bd}YaU5@s*T6c~0h{dsL7MDW|LRcWztEEY%F_4Y zSB-yJ*mLlP89et`LF`b#o1qsV-dCysxW;ne2b?xBv#^)D8?1b7W26}1pIge?9|2+}%?^yx-pKuKTvue(4X;0vvLGJ$EWv0UY z)DQmSeT{29+@N12(APD#O+bOOG3)}YVxoJvO32LxO*{IpHZ%mRlT&N2m& z%U<%mjIj;CWjP=v($cdU2+z~n6W|G1<#XVo!yH)ma>{#O7E`ujps3;SU^Og`(&YFF zw44{Q%&T(!KR5%|Ju9;;07&%!2oEGTZ1g$X5~<)d-Oy*5x2foUhwMjy&1Dv5i!0@B z`_!p>@-fv{JtOfV=XR{eYsz`c8N^c)nGlSZCcM^84CF&9P|dh>)U?1vndN+sK22OS z5Jj!|7n8jHQBv67dp*!&bahRw6Hqm_!kv9#_#Im3b1&(OWiUL*@qBFrnM|t0yCyzg%Mm(1JlZ`>6zi09IKvHYNdb~_>=m^ z|Cz^8Yv0fKTOaKt+RX^Zq^-!1lSPQ28Kj-u)ztmN-1fg!=lRC@)zM`?Gz+Bz#pJ#( zr3k+FaM#om9{PH`*+JOu2sQpNf%L&_u7#l8hma;`A9??J$%=1Lpw(5(?bubBMn^Y0 zcWH=C6^zf_FT=Aq7t>pGyVgmpyfl4_PuHC3>CHgP`}=H}Q(I$=X+S)ca1BOPi=43; zL+^0!Q$&s5aEkGZ&&t$rR-$w1<&31K@#Bpl()wFFP5is;uV(E3C%u9HkKJ4TV*39A Dx^_5` literal 0 HcmV?d00001 diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py new file mode 100644 index 000000000..48eb6135e --- /dev/null +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -0,0 +1,89 @@ +""" +Checks if a system of forces is in static equilibrium. + +python/black : true +flake8 : passed +mypy : passed +""" + +from numpy import array, cos, sin, radians, cross # type: ignore +from typing import List + + +def polar_force( + magnitude: float, angle: float, radian_mode: bool = False +) -> List[float]: + """ + Resolves force along rectangular components. + (force, angle) => (force_x, force_y) + >>> polar_force(10, 45) + [7.0710678118654755, 7.071067811865475] + >>> polar_force(10, 3.14, radian_mode=True) + [-9.999987317275394, 0.01592652916486828] + """ + if radian_mode: + return [magnitude * cos(angle), magnitude * sin(angle)] + return [magnitude * cos(radians(angle)), magnitude * sin(radians(angle))] + + +def in_static_equilibrium( + forces: array, location: array, eps: float = 10 ** -1 +) -> bool: + """ + Check if a system is in equilibrium. + It takes two numpy.array objects. + forces ==> [ + [force1_x, force1_y], + [force2_x, force2_y], + ....] + location ==> [ + [x1, y1], + [x2, y2], + ....] + >>> force = array([[1, 1], [-1, 2]]) + >>> location = array([[1, 0], [10, 0]]) + >>> in_static_equilibrium(force, location) + False + """ + # summation of moments is zero + moments: array = cross(location, forces) + sum_moments: float = sum(moments) + return abs(sum_moments) < eps + + +if __name__ == "__main__": + # Test to check if it works + forces = array( + [ + polar_force(718.4, 180 - 30), + polar_force(879.54, 45), + polar_force(100, -90) + ]) + + location = array([[0, 0], [0, 0], [0, 0]]) + + assert in_static_equilibrium(forces, location) + + # Problem 1 in image_data/2D_problems.jpg + forces = array( + [ + polar_force(30 * 9.81, 15), + polar_force(215, 180 - 45), + polar_force(264, 90 - 30), + ] + ) + + location = array([[0, 0], [0, 0], [0, 0]]) + + assert in_static_equilibrium(forces, location) + + # Problem in image_data/2D_problems_1.jpg + forces = array([[0, -2000], [0, -1200], [0, 15600], [0, -12400]]) + + location = array([[0, 0], [6, 0], [10, 0], [12, 0]]) + + assert in_static_equilibrium(forces, location) + + import doctest + + doctest.testmod() diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 4ca1301dd..526f5ec27 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -1,665 +1,711 @@ -class RedBlackTree: - """ - A Red-Black tree, which is a self-balancing BST (binary search - tree). - - This tree has similar performance to AVL trees, but the balancing is - less strict, so it will perform faster for writing/deleting nodes - and slower for reading in the average case, though, because they're - both balanced binary search trees, both will get the same asymptotic - perfomance. - - To read more about them, https://en.wikipedia.org/wiki/Red–black_tree - - Unless otherwise specified, all asymptotic runtimes are specified in - terms of the size of the tree. - """ - def __init__(self, label=None, color=0, parent=None, left=None, right=None): - """Initialize a new Red-Black Tree node with the given values: - label: The value associated with this node - color: 0 if black, 1 if red - parent: The parent to this node - left: This node's left child - right: This node's right child - """ - self.label = label - self.parent = parent - self.left = left - self.right = right - self.color = color - - # Here are functions which are specific to red-black trees - - def rotate_left(self): - """Rotate the subtree rooted at this node to the left and - returns the new root to this subtree. - - Perfoming one rotation can be done in O(1). - """ - parent = self.parent - right = self.right - self.right = right.left - if self.right: - self.right.parent = self - self.parent = right - right.left = self - if parent is not None: - if parent.left is self: - parent.left = right - else: - parent.right = right - right.parent = parent - return right - - def rotate_right(self): - """Rotate the subtree rooted at this node to the right and - returns the new root to this subtree. - - Performing one rotation can be done in O(1). - """ - parent = self.parent - left = self.left - self.left = left.right - if self.left: - self.left.parent = self - self.parent = left - left.right = self - if parent is not None: - if parent.right is self: - parent.right = left - else: - parent.left = left - left.parent = parent - return left - - def insert(self, label): - """Inserts label into the subtree rooted at self, performs any - rotations necessary to maintain balance, and then returns the - new root to this subtree (likely self). - - This is guaranteed to run in O(log(n)) time. - """ - if self.label is None: - # Only possible with an empty tree - self.label = label - return self - if self.label == label: - return self - elif self.label > label: - if self.left: - self.left.insert(label) - else: - self.left = RedBlackTree(label, 1, self) - self.left._insert_repair() - else: - if self.right: - self.right.insert(label) - else: - self.right = RedBlackTree(label, 1, self) - self.right._insert_repair() - return self.parent or self - - def _insert_repair(self): - """Repair the coloring from inserting into a tree.""" - if self.parent is None: - # This node is the root, so it just needs to be black - self.color = 0 - elif color(self.parent) == 0: - # If the parent is black, then it just needs to be red - self.color = 1 - else: - uncle = self.parent.sibling - if color(uncle) == 0: - if self.is_left() and self.parent.is_right(): - self.parent.rotate_right() - self.right._insert_repair() - elif self.is_right() and self.parent.is_left(): - self.parent.rotate_left() - self.left._insert_repair() - elif self.is_left(): - self.grandparent.rotate_right() - self.parent.color = 0 - self.parent.right.color = 1 - else: - self.grandparent.rotate_left() - self.parent.color = 0 - self.parent.left.color = 1 - else: - self.parent.color = 0 - uncle.color = 0 - self.grandparent.color = 1 - self.grandparent._insert_repair() - - def remove(self, label): - """Remove label from this tree.""" - if self.label == label: - if self.left and self.right: - # It's easier to balance a node with at most one child, - # so we replace this node with the greatest one less than - # it and remove that. - value = self.left.get_max() - self.label = value - self.left.remove(value) - else: - # This node has at most one non-None child, so we don't - # need to replace - child = self.left or self.right - if self.color == 1: - # This node is red, and its child is black - # The only way this happens to a node with one child - # is if both children are None leaves. - # We can just remove this node and call it a day. - if self.is_left(): - self.parent.left = None - else: - self.parent.right = None - else: - # The node is black - if child is None: - # This node and its child are black - if self.parent is None: - # The tree is now empty - return RedBlackTree(None) - else: - self._remove_repair() - if self.is_left(): - self.parent.left = None - else: - self.parent.right = None - self.parent = None - else: - # This node is black and its child is red - # Move the child node here and make it black - self.label = child.label - self.left = child.left - self.right = child.right - if self.left: - self.left.parent = self - if self.right: - self.right.parent = self - elif self.label > label: - if self.left: - self.left.remove(label) - else: - if self.right: - self.right.remove(label) - return self.parent or self - - def _remove_repair(self): - """Repair the coloring of the tree that may have been messed up.""" - if color(self.sibling) == 1: - self.sibling.color = 0 - self.parent.color = 1 - if self.is_left(): - self.parent.rotate_left() - else: - self.parent.rotate_right() - if color(self.parent) == 0 and color(self.sibling) == 0 \ - and color(self.sibling.left) == 0 \ - and color(self.sibling.right) == 0: - self.sibling.color = 1 - self.parent._remove_repair() - return - if color(self.parent) == 1 and color(self.sibling) == 0 \ - and color(self.sibling.left) == 0 \ - and color(self.sibling.right) == 0: - self.sibling.color = 1 - self.parent.color = 0 - return - if (self.is_left() - and color(self.sibling) == 0 - and color(self.sibling.right) == 0 - and color(self.sibling.left) == 1): - self.sibling.rotate_right() - self.sibling.color = 0 - self.sibling.right.color = 1 - if (self.is_right() - and color(self.sibling) == 0 - and color(self.sibling.right) == 1 - and color(self.sibling.left) == 0): - self.sibling.rotate_left() - self.sibling.color = 0 - self.sibling.left.color = 1 - if (self.is_left() - and color(self.sibling) == 0 - and color(self.sibling.right) == 1): - self.parent.rotate_left() - self.grandparent.color = self.parent.color - self.parent.color = 0 - self.parent.sibling.color = 0 - if (self.is_right() - and color(self.sibling) == 0 - and color(self.sibling.left) == 1): - self.parent.rotate_right() - self.grandparent.color = self.parent.color - self.parent.color = 0 - self.parent.sibling.color = 0 - - def check_color_properties(self): - """Check the coloring of the tree, and return True iff the tree - is colored in a way which matches these five properties: - (wording stolen from wikipedia article) - 1. Each node is either red or black. - 2. The root node is black. - 3. All leaves are black. - 4. If a node is red, then both its children are black. - 5. Every path from any node to all of its descendent NIL nodes - has the same number of black nodes. - - This function runs in O(n) time, because properties 4 and 5 take - that long to check. - """ - # I assume property 1 to hold because there is nothing that can - # make the color be anything other than 0 or 1. - - # Property 2 - if self.color: - # The root was red - print('Property 2') - return False; - - # Property 3 does not need to be checked, because None is assumed - # to be black and is all the leaves. - - # Property 4 - if not self.check_coloring(): - print('Property 4') - return False - - # Property 5 - if self.black_height() is None: - print('Property 5') - return False - # All properties were met - return True - - def check_coloring(self): - """A helper function to recursively check Property 4 of a - Red-Black Tree. See check_color_properties for more info. - """ - if self.color == 1: - if color(self.left) == 1 or color(self.right) == 1: - return False - if self.left and not self.left.check_coloring(): - return False - if self.right and not self.right.check_coloring(): - return False - return True - - def black_height(self): - """Returns the number of black nodes from this node to the - leaves of the tree, or None if there isn't one such value (the - tree is color incorrectly). - """ - if self is None: - # If we're already at a leaf, there is no path - return 1 - left = RedBlackTree.black_height(self.left) - right = RedBlackTree.black_height(self.right) - if left is None or right is None: - # There are issues with coloring below children nodes - return None - if left != right: - # The two children have unequal depths - return None - # Return the black depth of children, plus one if this node is - # black - return left + (1-self.color) - - # Here are functions which are general to all binary search trees - - def __contains__(self, label): - """Search through the tree for label, returning True iff it is - found somewhere in the tree. - - Guaranteed to run in O(log(n)) time. - """ - return self.search(label) is not None - - def search(self, label): - """Search through the tree for label, returning its node if - it's found, and None otherwise. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.label == label: - return self - elif label > self.label: - if self.right is None: - return None - else: - return self.right.search(label) - else: - if self.left is None: - return None - else: - return self.left.search(label) - - def floor(self, label): - """Returns the largest element in this tree which is at most label. - - This method is guaranteed to run in O(log(n)) time.""" - if self.label == label: - return self.label - elif self.label > label: - if self.left: - return self.left.floor(label) - else: - return None - else: - if self.right: - attempt = self.right.floor(label) - if attempt is not None: - return attempt - return self.label - - def ceil(self, label): - """Returns the smallest element in this tree which is at least label. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.label == label: - return self.label - elif self.label < label: - if self.right: - return self.right.ceil(label) - else: - return None - else: - if self.left: - attempt = self.left.ceil(label) - if attempt is not None: - return attempt - return self.label - - def get_max(self): - """Returns the largest element in this tree. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.right: - # Go as far right as possible - return self.right.get_max() - else: - return self.label - - def get_min(self): - """Returns the smallest element in this tree. - - This method is guaranteed to run in O(log(n)) time. - """ - if self.left: - # Go as far left as possible - return self.left.get_min() - else: - return self.label - - @property - def grandparent(self): - """Get the current node's grandparent, or None if it doesn't exist.""" - if self.parent is None: - return None - else: - return self.parent.parent - - @property - def sibling(self): - """Get the current node's sibling, or None if it doesn't exist.""" - if self.parent is None: - return None - elif self.parent.left is self: - return self.parent.right - else: - return self.parent.left - - def is_left(self): - """Returns true iff this node is the left child of its parent.""" - return self.parent and self.parent.left is self - - def is_right(self): - """Returns true iff this node is the right child of its parent.""" - return self.parent and self.parent.right is self - - def __bool__(self): - return True - - def __len__(self): - """ - Return the number of nodes in this tree. - """ - ln = 1 - if self.left: - ln += len(self.left) - if self.right: - ln += len(self.right) - return ln - - def preorder_traverse(self): - yield self.label - if self.left: - yield from self.left.preorder_traverse() - if self.right: - yield from self.right.preorder_traverse() - - def inorder_traverse(self): - if self.left: - yield from self.left.inorder_traverse() - yield self.label - if self.right: - yield from self.right.inorder_traverse() - - - def postorder_traverse(self): - if self.left: - yield from self.left.postorder_traverse() - if self.right: - yield from self.right.postorder_traverse() - yield self.label - - def __repr__(self): - from pprint import pformat - if self.left is None and self.right is None: - return "'%s %s'" % (self.label, (self.color and 'red') or 'blk') - return pformat({'%s %s' % (self.label, (self.color and 'red') or 'blk'): - (self.left, self.right)}, - indent=1) - - def __eq__(self, other): - """Test if two trees are equal.""" - if self.label == other.label: - return self.left == other.left and self.right == other.right - else: - return False - -def color(node): - """Returns the color of a node, allowing for None leaves.""" - if node is None: - return 0 - else: - return node.color - -""" -Code for testing the various functions of the red-black tree. -""" - -def test_rotations(): - """Test that the rotate_left and rotate_right functions work.""" - # Make a tree to test on - tree = RedBlackTree(0) - tree.left = RedBlackTree(-10, parent=tree) - tree.right = RedBlackTree(10, parent=tree) - tree.left.left = RedBlackTree(-20, parent=tree.left) - tree.left.right = RedBlackTree(-5, parent=tree.left) - tree.right.left = RedBlackTree(5, parent=tree.right) - tree.right.right = RedBlackTree(20, parent=tree.right) - # Make the right rotation - left_rot = RedBlackTree(10) - left_rot.left = RedBlackTree(0, parent=left_rot) - left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) - left_rot.left.right = RedBlackTree(5, parent=left_rot.left) - left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) - left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) - left_rot.right = RedBlackTree(20, parent=left_rot) - tree = tree.rotate_left() - if tree != left_rot: - return False - tree = tree.rotate_right() - tree = tree.rotate_right() - # Make the left rotation - right_rot = RedBlackTree(-10) - right_rot.left = RedBlackTree(-20, parent=right_rot) - right_rot.right = RedBlackTree(0, parent=right_rot) - right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) - right_rot.right.right = RedBlackTree(10, parent=right_rot.right) - right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) - right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) - if tree != right_rot: - return False - return True - -def test_insertion_speed(): - """Test that the tree balances inserts to O(log(n)) by doing a lot - of them. - """ - tree = RedBlackTree(-1) - for i in range(300000): - tree = tree.insert(i) - return True - -def test_insert(): - """Test the insert() method of the tree correctly balances, colors, - and inserts. - """ - tree = RedBlackTree(0) - tree.insert(8) - tree.insert(-8) - tree.insert(4) - tree.insert(12) - tree.insert(10) - tree.insert(11) - ans = RedBlackTree(0, 0) - ans.left = RedBlackTree(-8, 0, ans) - ans.right = RedBlackTree(8, 1, ans) - ans.right.left = RedBlackTree(4, 0, ans.right) - ans.right.right = RedBlackTree(11, 0, ans.right) - ans.right.right.left = RedBlackTree(10, 1, ans.right.right) - ans.right.right.right = RedBlackTree(12, 1, ans.right.right) - return tree == ans - -def test_insert_and_search(): - """Tests searching through the tree for values.""" - tree = RedBlackTree(0) - tree.insert(8) - tree.insert(-8) - tree.insert(4) - tree.insert(12) - tree.insert(10) - tree.insert(11) - if 5 in tree or -6 in tree or -10 in tree or 13 in tree: - # Found something not in there - return False - if not (11 in tree and 12 in tree and -8 in tree and 0 in tree): - # Didn't find something in there - return False - return True - -def test_insert_delete(): - """Test the insert() and delete() method of the tree, verifying the - insertion and removal of elements, and the balancing of the tree. - """ - tree = RedBlackTree(0) - tree = tree.insert(-12) - tree = tree.insert(8) - tree = tree.insert(-8) - tree = tree.insert(15) - tree = tree.insert(4) - tree = tree.insert(12) - tree = tree.insert(10) - tree = tree.insert(9) - tree = tree.insert(11) - tree = tree.remove(15) - tree = tree.remove(-12) - tree = tree.remove(9) - if not tree.check_color_properties(): - return False - if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]: - return False - return True - -def test_floor_ceil(): - """Tests the floor and ceiling functions in the tree.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] - for val, floor, ceil in tuples: - if tree.floor(val) != floor or tree.ceil(val) != ceil: - return False - return True - -def test_min_max(): - """Tests the min and max functions in the tree.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - if tree.get_max() != 22 or tree.get_min() != -16: - return False - return True - -def test_tree_traversal(): - """Tests the three different tree traversal functions.""" - tree = RedBlackTree(0) - tree.insert(-16) - tree.insert(16) - tree.insert(8) - tree.insert(24) - tree.insert(20) - tree.insert(22) - if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: - return False - if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: - return False - if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: - return False - return True - -def main(): - if test_rotations(): - print('Rotating right and left works!') - else: - print('Rotating right and left doesn\'t work. :(') - if test_insert(): - print('Inserting works!') - else: - print('Inserting doesn\'t work :(') - if test_insert_and_search(): - print('Searching works!') - else: - print('Searching doesn\'t work :(') - if test_insert_delete(): - print('Deleting works!') - else: - print('Deleting doesn\'t work :(') - if test_floor_ceil(): - print('Floor and ceil work!') - else: - print('Floor and ceil don\'t work :(') - if test_tree_traversal(): - print('Tree traversal works!') - else: - print('Tree traversal doesn\'t work :(') - print('Testing tree balancing...') - print('This should only be a few seconds.') - test_insertion_speed() - print('Done!') - -if __name__ == '__main__': - main() +""" +python/black : true +flake8 : passed +""" + + +class RedBlackTree: + """ + A Red-Black tree, which is a self-balancing BST (binary search + tree). + This tree has similar performance to AVL trees, but the balancing is + less strict, so it will perform faster for writing/deleting nodes + and slower for reading in the average case, though, because they're + both balanced binary search trees, both will get the same asymptotic + perfomance. + To read more about them, https://en.wikipedia.org/wiki/Red–black_tree + Unless otherwise specified, all asymptotic runtimes are specified in + terms of the size of the tree. + """ + + def __init__(self, label=None, color=0, parent=None, left=None, right=None): + """Initialize a new Red-Black Tree node with the given values: + label: The value associated with this node + color: 0 if black, 1 if red + parent: The parent to this node + left: This node's left child + right: This node's right child + """ + self.label = label + self.parent = parent + self.left = left + self.right = right + self.color = color + + # Here are functions which are specific to red-black trees + + def rotate_left(self): + """Rotate the subtree rooted at this node to the left and + returns the new root to this subtree. + Perfoming one rotation can be done in O(1). + """ + parent = self.parent + right = self.right + self.right = right.left + if self.right: + self.right.parent = self + self.parent = right + right.left = self + if parent is not None: + if parent.left == self: + parent.left = right + else: + parent.right = right + right.parent = parent + return right + + def rotate_right(self): + """Rotate the subtree rooted at this node to the right and + returns the new root to this subtree. + Performing one rotation can be done in O(1). + """ + parent = self.parent + left = self.left + self.left = left.right + if self.left: + self.left.parent = self + self.parent = left + left.right = self + if parent is not None: + if parent.right is self: + parent.right = left + else: + parent.left = left + left.parent = parent + return left + + def insert(self, label): + """Inserts label into the subtree rooted at self, performs any + rotations necessary to maintain balance, and then returns the + new root to this subtree (likely self). + This is guaranteed to run in O(log(n)) time. + """ + if self.label is None: + # Only possible with an empty tree + self.label = label + return self + if self.label == label: + return self + elif self.label > label: + if self.left: + self.left.insert(label) + else: + self.left = RedBlackTree(label, 1, self) + self.left._insert_repair() + else: + if self.right: + self.right.insert(label) + else: + self.right = RedBlackTree(label, 1, self) + self.right._insert_repair() + return self.parent or self + + def _insert_repair(self): + """Repair the coloring from inserting into a tree.""" + if self.parent is None: + # This node is the root, so it just needs to be black + self.color = 0 + elif color(self.parent) == 0: + # If the parent is black, then it just needs to be red + self.color = 1 + else: + uncle = self.parent.sibling + if color(uncle) == 0: + if self.is_left() and self.parent.is_right(): + self.parent.rotate_right() + self.right._insert_repair() + elif self.is_right() and self.parent.is_left(): + self.parent.rotate_left() + self.left._insert_repair() + elif self.is_left(): + self.grandparent.rotate_right() + self.parent.color = 0 + self.parent.right.color = 1 + else: + self.grandparent.rotate_left() + self.parent.color = 0 + self.parent.left.color = 1 + else: + self.parent.color = 0 + uncle.color = 0 + self.grandparent.color = 1 + self.grandparent._insert_repair() + + def remove(self, label): + """Remove label from this tree.""" + if self.label == label: + if self.left and self.right: + # It's easier to balance a node with at most one child, + # so we replace this node with the greatest one less than + # it and remove that. + value = self.left.get_max() + self.label = value + self.left.remove(value) + else: + # This node has at most one non-None child, so we don't + # need to replace + child = self.left or self.right + if self.color == 1: + # This node is red, and its child is black + # The only way this happens to a node with one child + # is if both children are None leaves. + # We can just remove this node and call it a day. + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + else: + # The node is black + if child is None: + # This node and its child are black + if self.parent is None: + # The tree is now empty + return RedBlackTree(None) + else: + self._remove_repair() + if self.is_left(): + self.parent.left = None + else: + self.parent.right = None + self.parent = None + else: + # This node is black and its child is red + # Move the child node here and make it black + self.label = child.label + self.left = child.left + self.right = child.right + if self.left: + self.left.parent = self + if self.right: + self.right.parent = self + elif self.label > label: + if self.left: + self.left.remove(label) + else: + if self.right: + self.right.remove(label) + return self.parent or self + + def _remove_repair(self): + """Repair the coloring of the tree that may have been messed up.""" + if color(self.sibling) == 1: + self.sibling.color = 0 + self.parent.color = 1 + if self.is_left(): + self.parent.rotate_left() + else: + self.parent.rotate_right() + if ( + color(self.parent) == 0 + and color(self.sibling) == 0 + and color(self.sibling.left) == 0 + and color(self.sibling.right) == 0 + ): + self.sibling.color = 1 + self.parent._remove_repair() + return + if ( + color(self.parent) == 1 + and color(self.sibling) == 0 + and color(self.sibling.left) == 0 + and color(self.sibling.right) == 0 + ): + self.sibling.color = 1 + self.parent.color = 0 + return + if ( + self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 0 + and color(self.sibling.left) == 1 + ): + self.sibling.rotate_right() + self.sibling.color = 0 + self.sibling.right.color = 1 + if ( + self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + and color(self.sibling.left) == 0 + ): + self.sibling.rotate_left() + self.sibling.color = 0 + self.sibling.left.color = 1 + if ( + self.is_left() + and color(self.sibling) == 0 + and color(self.sibling.right) == 1 + ): + self.parent.rotate_left() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + if ( + self.is_right() + and color(self.sibling) == 0 + and color(self.sibling.left) == 1 + ): + self.parent.rotate_right() + self.grandparent.color = self.parent.color + self.parent.color = 0 + self.parent.sibling.color = 0 + + def check_color_properties(self): + """Check the coloring of the tree, and return True iff the tree + is colored in a way which matches these five properties: + (wording stolen from wikipedia article) + 1. Each node is either red or black. + 2. The root node is black. + 3. All leaves are black. + 4. If a node is red, then both its children are black. + 5. Every path from any node to all of its descendent NIL nodes + has the same number of black nodes. + This function runs in O(n) time, because properties 4 and 5 take + that long to check. + """ + # I assume property 1 to hold because there is nothing that can + # make the color be anything other than 0 or 1. + + # Property 2 + if self.color: + # The root was red + print("Property 2") + return False + + # Property 3 does not need to be checked, because None is assumed + # to be black and is all the leaves. + + # Property 4 + if not self.check_coloring(): + print("Property 4") + return False + + # Property 5 + if self.black_height() is None: + print("Property 5") + return False + # All properties were met + return True + + def check_coloring(self): + """A helper function to recursively check Property 4 of a + Red-Black Tree. See check_color_properties for more info. + """ + if self.color == 1: + if color(self.left) == 1 or color(self.right) == 1: + return False + if self.left and not self.left.check_coloring(): + return False + if self.right and not self.right.check_coloring(): + return False + return True + + def black_height(self): + """Returns the number of black nodes from this node to the + leaves of the tree, or None if there isn't one such value (the + tree is color incorrectly). + """ + if self is None: + # If we're already at a leaf, there is no path + return 1 + left = RedBlackTree.black_height(self.left) + right = RedBlackTree.black_height(self.right) + if left is None or right is None: + # There are issues with coloring below children nodes + return None + if left != right: + # The two children have unequal depths + return None + # Return the black depth of children, plus one if this node is + # black + return left + (1 - self.color) + + # Here are functions which are general to all binary search trees + + def __contains__(self, label): + """Search through the tree for label, returning True iff it is + found somewhere in the tree. + Guaranteed to run in O(log(n)) time. + """ + return self.search(label) is not None + + def search(self, label): + """Search through the tree for label, returning its node if + it's found, and None otherwise. + This method is guaranteed to run in O(log(n)) time. + """ + if self.label == label: + return self + elif label > self.label: + if self.right is None: + return None + else: + return self.right.search(label) + else: + if self.left is None: + return None + else: + return self.left.search(label) + + def floor(self, label): + """Returns the largest element in this tree which is at most label. + This method is guaranteed to run in O(log(n)) time.""" + if self.label == label: + return self.label + elif self.label > label: + if self.left: + return self.left.floor(label) + else: + return None + else: + if self.right: + attempt = self.right.floor(label) + if attempt is not None: + return attempt + return self.label + + def ceil(self, label): + """Returns the smallest element in this tree which is at least label. + This method is guaranteed to run in O(log(n)) time. + """ + if self.label == label: + return self.label + elif self.label < label: + if self.right: + return self.right.ceil(label) + else: + return None + else: + if self.left: + attempt = self.left.ceil(label) + if attempt is not None: + return attempt + return self.label + + def get_max(self): + """Returns the largest element in this tree. + This method is guaranteed to run in O(log(n)) time. + """ + if self.right: + # Go as far right as possible + return self.right.get_max() + else: + return self.label + + def get_min(self): + """Returns the smallest element in this tree. + This method is guaranteed to run in O(log(n)) time. + """ + if self.left: + # Go as far left as possible + return self.left.get_min() + else: + return self.label + + @property + def grandparent(self): + """Get the current node's grandparent, or None if it doesn't exist.""" + if self.parent is None: + return None + else: + return self.parent.parent + + @property + def sibling(self): + """Get the current node's sibling, or None if it doesn't exist.""" + if self.parent is None: + return None + elif self.parent.left is self: + return self.parent.right + else: + return self.parent.left + + def is_left(self): + """Returns true iff this node is the left child of its parent.""" + return self.parent and self.parent.left is self + + def is_right(self): + """Returns true iff this node is the right child of its parent.""" + return self.parent and self.parent.right is self + + def __bool__(self): + return True + + def __len__(self): + """ + Return the number of nodes in this tree. + """ + ln = 1 + if self.left: + ln += len(self.left) + if self.right: + ln += len(self.right) + return ln + + def preorder_traverse(self): + yield self.label + if self.left: + yield from self.left.preorder_traverse() + if self.right: + yield from self.right.preorder_traverse() + + def inorder_traverse(self): + if self.left: + yield from self.left.inorder_traverse() + yield self.label + if self.right: + yield from self.right.inorder_traverse() + + def postorder_traverse(self): + if self.left: + yield from self.left.postorder_traverse() + if self.right: + yield from self.right.postorder_traverse() + yield self.label + + def __repr__(self): + from pprint import pformat + + if self.left is None and self.right is None: + return "'%s %s'" % (self.label, (self.color and "red") or "blk") + return pformat( + { + "%s %s" + % (self.label, (self.color and "red") or "blk"): (self.left, self.right) + }, + indent=1, + ) + + def __eq__(self, other): + """Test if two trees are equal.""" + if self.label == other.label: + return self.left == other.left and self.right == other.right + else: + return False + + +def color(node): + """Returns the color of a node, allowing for None leaves.""" + if node is None: + return 0 + else: + return node.color + + +""" +Code for testing the various +functions of the red-black tree. +""" + + +def test_rotations(): + """Test that the rotate_left and rotate_right functions work.""" + # Make a tree to test on + tree = RedBlackTree(0) + tree.left = RedBlackTree(-10, parent=tree) + tree.right = RedBlackTree(10, parent=tree) + tree.left.left = RedBlackTree(-20, parent=tree.left) + tree.left.right = RedBlackTree(-5, parent=tree.left) + tree.right.left = RedBlackTree(5, parent=tree.right) + tree.right.right = RedBlackTree(20, parent=tree.right) + # Make the right rotation + left_rot = RedBlackTree(10) + left_rot.left = RedBlackTree(0, parent=left_rot) + left_rot.left.left = RedBlackTree(-10, parent=left_rot.left) + left_rot.left.right = RedBlackTree(5, parent=left_rot.left) + left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left) + left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left) + left_rot.right = RedBlackTree(20, parent=left_rot) + tree = tree.rotate_left() + if tree != left_rot: + return False + tree = tree.rotate_right() + tree = tree.rotate_right() + # Make the left rotation + right_rot = RedBlackTree(-10) + right_rot.left = RedBlackTree(-20, parent=right_rot) + right_rot.right = RedBlackTree(0, parent=right_rot) + right_rot.right.left = RedBlackTree(-5, parent=right_rot.right) + right_rot.right.right = RedBlackTree(10, parent=right_rot.right) + right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right) + right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right) + if tree != right_rot: + return False + return True + + +def test_insertion_speed(): + """Test that the tree balances inserts to O(log(n)) by doing a lot + of them. + """ + tree = RedBlackTree(-1) + for i in range(300000): + tree = tree.insert(i) + return True + + +def test_insert(): + """Test the insert() method of the tree correctly balances, colors, + and inserts. + """ + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + ans = RedBlackTree(0, 0) + ans.left = RedBlackTree(-8, 0, ans) + ans.right = RedBlackTree(8, 1, ans) + ans.right.left = RedBlackTree(4, 0, ans.right) + ans.right.right = RedBlackTree(11, 0, ans.right) + ans.right.right.left = RedBlackTree(10, 1, ans.right.right) + ans.right.right.right = RedBlackTree(12, 1, ans.right.right) + return tree == ans + + +def test_insert_and_search(): + """Tests searching through the tree for values.""" + tree = RedBlackTree(0) + tree.insert(8) + tree.insert(-8) + tree.insert(4) + tree.insert(12) + tree.insert(10) + tree.insert(11) + if 5 in tree or -6 in tree or -10 in tree or 13 in tree: + # Found something not in there + return False + if not (11 in tree and 12 in tree and -8 in tree and 0 in tree): + # Didn't find something in there + return False + return True + + +def test_insert_delete(): + """Test the insert() and delete() method of the tree, verifying the + insertion and removal of elements, and the balancing of the tree. + """ + tree = RedBlackTree(0) + tree = tree.insert(-12) + tree = tree.insert(8) + tree = tree.insert(-8) + tree = tree.insert(15) + tree = tree.insert(4) + tree = tree.insert(12) + tree = tree.insert(10) + tree = tree.insert(9) + tree = tree.insert(11) + tree = tree.remove(15) + tree = tree.remove(-12) + tree = tree.remove(9) + if not tree.check_color_properties(): + return False + if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]: + return False + return True + + +def test_floor_ceil(): + """Tests the floor and ceiling functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)] + for val, floor, ceil in tuples: + if tree.floor(val) != floor or tree.ceil(val) != ceil: + return False + return True + + +def test_min_max(): + """Tests the min and max functions in the tree.""" + tree = RedBlackTree(0) + tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if tree.get_max() != 22 or tree.get_min() != -16: + return False + return True + + +def test_tree_traversal(): + """Tests the three different tree traversal functions.""" + tree = RedBlackTree(0) + tree = tree.insert(-16) + tree.insert(16) + tree.insert(8) + tree.insert(24) + tree.insert(20) + tree.insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: + return False + return True + + +def test_tree_chaining(): + """Tests the three different tree chaning functions.""" + tree = RedBlackTree(0) + tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22) + if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]: + return False + if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]: + return False + if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]: + return False + return True + + +def print_results(msg: str, passes: bool) -> None: + print(str(msg), "works!" if passes else "doesn't work :(") + + +def pytests(): + assert test_rotations() + assert test_insert() + assert test_insert_and_search() + assert test_insert_delete() + assert test_floor_ceil() + assert test_tree_traversal() + assert test_tree_chaining() + + +def main(): + """ + >>> pytests() + """ + print_results("Rotating right and left", test_rotations()) + + print_results("Inserting", test_insert()) + + print_results("Searching", test_insert_and_search()) + + print_results("Deleting", test_insert_delete()) + + print_results("Floor and ceil", test_floor_ceil()) + + print_results("Tree traversal", test_tree_traversal()) + + print_results("Tree traversal", test_tree_chaining()) + + + print("Testing tree balancing...") + print("This should only be a few seconds.") + test_insertion_speed() + print("Done!") + + +if __name__ == "__main__": + main()